Convert Figma logo to code with AI

valeriansaliou logosonic

🦔 Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

19,790
561
19,790
67

Top Related Projects

A query and indexing engine for Redis, providing secondary indexing, full-text search, vector similarity search and aggregations.

A lightning-fast search API that fits effortlessly into your apps, websites, and workflow

20,388

Open Source alternative to Algolia + Pinecone and an Easier-to-Use alternative to ElasticSearch ⚡ 🔍 ✨ Fast, typo tolerant, in-memory fuzzy Search Engine for building delightful search experiences

ZincSearch . A lightweight alternative to elasticsearch that requires minimal resources, written in Go.

Cloud-native search engine for observability. An open-source alternative to Datadog, Elasticsearch, Loki, and Tempo.

4,181

A full-text search engine in rust

Quick Overview

Sonic is a fast, lightweight, and schema-less search backend written in Rust. It's designed to ingest search texts and identifier tuples, and allows for quickly searching them by any given word. Sonic is optimized for high-volume, real-time indexing and querying.

Pros

  • Extremely fast and efficient, capable of handling high-volume indexing and querying
  • Lightweight and consumes minimal resources
  • Schema-less design allows for flexible data structures
  • Easy to integrate with existing systems via a simple TCP protocol

Cons

  • Limited advanced search features compared to more complex search engines
  • Lacks built-in support for certain operations like fuzzy matching or relevance scoring
  • Documentation could be more comprehensive for advanced use cases
  • Relatively new project, which may lead to potential stability issues or lack of community support

Code Examples

  1. Connecting to Sonic:
use sonic_client::SonicClient;

let client = SonicClient::connect("localhost:1491", "SecretPassword").await?;
  1. Ingesting data:
client.push("collection", "bucket", "object_id", "This is a sample text").await?;
  1. Searching for data:
let results = client.query("collection", "bucket", "sample").await?;
println!("Search results: {:?}", results);
  1. Suggesting autocomplete:
let suggestions = client.suggest("collection", "bucket", "sa", 5).await?;
println!("Autocomplete suggestions: {:?}", suggestions);

Getting Started

  1. Install Sonic:

    git clone https://github.com/valeriansaliou/sonic.git
    cd sonic
    cargo build --release
    
  2. Configure Sonic: Edit config.cfg to set your desired configuration.

  3. Run Sonic:

    ./target/release/sonic -c config.cfg
    
  4. Use a Sonic client library (e.g., sonic-client for Rust) to interact with the server:

    use sonic_client::SonicClient;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let client = SonicClient::connect("localhost:1491", "SecretPassword").await?;
        client.push("collection", "bucket", "object_id", "Sample text").await?;
        let results = client.query("collection", "bucket", "sample").await?;
        println!("Results: {:?}", results);
        Ok(())
    }
    

Competitor Comparisons

A query and indexing engine for Redis, providing secondary indexing, full-text search, vector similarity search and aggregations.

Pros of RediSearch

  • Built on top of Redis, leveraging its speed and scalability
  • Supports complex queries with multiple fields and conditions
  • Offers geospatial search capabilities

Cons of RediSearch

  • Requires Redis as a dependency, increasing system complexity
  • May have higher memory usage due to Redis architecture
  • Learning curve for Redis-specific query syntax

Code Comparison

Sonic (Rust):

let channel = sonic_channel::IngestChannel::start("localhost:1491", "SecretPassword").unwrap();
channel.push("collection", "bucket", "object:1", "Hello World!").unwrap();

RediSearch (Python client):

from redisearch import Client, TextField
client = Client("myIndex")
client.create_index([TextField("title"), TextField("body")])
client.add_document("doc1", title="Hello", body="World")

Both libraries offer simple APIs for indexing and searching, but RediSearch's integration with Redis allows for more complex data structures and query capabilities at the cost of increased setup complexity.

A lightning-fast search API that fits effortlessly into your apps, websites, and workflow

Pros of Meilisearch

  • More feature-rich, offering advanced search capabilities like typo tolerance and highlighting
  • Better documentation and community support
  • Provides a RESTful API out of the box, making integration easier

Cons of Meilisearch

  • Higher resource consumption, especially for large datasets
  • Slower indexing speed compared to Sonic
  • More complex setup and configuration

Code Comparison

Sonic (Rust):

use sonic_channel::*;

let channel = IngestChannel::start("localhost:1491", "SecretPassword").unwrap();
channel.push("collection", "bucket", "object_id", "Hello, World!").unwrap();

Meilisearch (Rust):

use meilisearch_sdk::{client::*, document::*};

let client = Client::new("http://localhost:7700", "masterKey");
let movies = client.index("movies");
movies.add_documents(&[json!({"id": 1, "title": "Carol"})], Some("id")).await?;

Both examples demonstrate basic document indexing, but Meilisearch's API is more verbose and offers more options for document structure and indexing configuration.

20,388

Open Source alternative to Algolia + Pinecone and an Easier-to-Use alternative to ElasticSearch ⚡ 🔍 ✨ Fast, typo tolerant, in-memory fuzzy Search Engine for building delightful search experiences

Pros of Typesense

  • More feature-rich with advanced querying capabilities, including faceting and geo-search
  • Better documentation and easier setup process
  • Actively maintained with frequent updates and improvements

Cons of Typesense

  • Higher resource consumption compared to Sonic
  • Steeper learning curve due to more complex features
  • Potentially slower for simple search operations

Code Comparison

Sonic (search query):

let channel = sonic_channel::IngestChannel::start(
    "localhost:1491",
    "SecretPassword"
).unwrap();

channel.push("collection", "bucket", "object_id", "text to index").unwrap();

Typesense (search query):

const client = new Typesense.Client({
  nodes: [{ host: 'localhost', port: '8108', protocol: 'http' }],
  apiKey: 'xyz'
});

client.collections('books').documents().search({
  q: 'harry potter',
  query_by: 'title,author'
});

Both Sonic and Typesense offer fast and efficient search capabilities, but they cater to different use cases. Sonic is lightweight and focuses on simple, high-speed search operations, while Typesense provides a more comprehensive search solution with advanced features. The choice between the two depends on the specific requirements of your project and the complexity of your search needs.

ZincSearch . A lightweight alternative to elasticsearch that requires minimal resources, written in Go.

Pros of ZincSearch

  • Written in Go, which offers better performance and easier deployment compared to Sonic's Rust implementation
  • Provides a web UI for easier management and visualization of search data
  • Supports full-text search with more advanced querying capabilities

Cons of ZincSearch

  • Larger memory footprint and higher resource usage than Sonic
  • Less mature project with fewer contributors and a shorter development history
  • May be overkill for simple search scenarios where Sonic's lightweight approach is sufficient

Code Comparison

Sonic (configuration example):

[server]
log_level = "info"

[channel.search]
listen = "0.0.0.0:1491"

ZincSearch (configuration example):

server:
  port: 4080
  data_dir: ./data
  auth:
    username: admin
    password: Complexpass#123

Both projects offer simple configuration options, but ZincSearch's YAML format may be more familiar to some users. Sonic's configuration is more focused on network settings, while ZincSearch includes authentication and data storage options out of the box.

Cloud-native search engine for observability. An open-source alternative to Datadog, Elasticsearch, Loki, and Tempo.

Pros of Quickwit

  • Designed for large-scale search and analytics, handling petabytes of data
  • Supports distributed indexing and querying for improved scalability
  • Offers advanced features like aggregations and time series analysis

Cons of Quickwit

  • More complex setup and configuration compared to Sonic
  • Potentially higher resource requirements for smaller-scale applications
  • Steeper learning curve due to more advanced features

Code Comparison

Sonic (search query):

let results = channel.query("default", "movies", "inception").unwrap();

Quickwit (search query):

let search_request = SearchRequest {
    index_id: "movies".to_string(),
    query: "inception".to_string(),
    ..Default::default()
};
let results = client.search(search_request).await?;

Both projects aim to provide fast search capabilities, but they target different use cases. Sonic focuses on lightweight, simple search for smaller applications, while Quickwit is designed for large-scale, distributed search and analytics. The code comparison shows that Quickwit's API is more verbose, reflecting its more advanced features and capabilities.

4,181

A full-text search engine in rust

Pros of Toshi

  • Written in Rust, potentially offering better performance and memory safety
  • Supports more complex queries and aggregations
  • Provides a RESTful API for easier integration

Cons of Toshi

  • Less lightweight and more complex to set up compared to Sonic
  • May have higher resource requirements
  • Fewer language bindings available out-of-the-box

Code Comparison

Sonic (Ingest):

use sonic_channel::*;

let channel = IngestChannel::start("localhost:1491", "SecretPassword").unwrap();
channel.push("collection", "bucket", "object:1", "Hello World!").unwrap();

Toshi (Index):

use toshi::index::{Index, IndexSettings};

let settings = IndexSettings::default();
let mut index = Index::create("my_index", settings).unwrap();
index.add_document(doc!{"id": "1", "text": "Hello World!"}).unwrap();

Both Sonic and Toshi are search backends, but they have different focuses. Sonic is designed to be lightweight and fast, ideal for simple search scenarios. Toshi, on the other hand, offers more advanced features and query capabilities, making it suitable for more complex search requirements.

Sonic is easier to set up and use, with a simpler API and lower resource requirements. It's a good choice for projects that need basic search functionality without the overhead of a full-fledged search engine.

Toshi provides more powerful search capabilities, including complex queries and aggregations. It's built on top of Tantivy, a full-text search engine library in Rust, which gives it more advanced features. However, this comes at the cost of increased complexity and potentially higher resource usage.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Sonic

Test and Build Build and Release dependency status Buy Me A Coffee

Sonic is a fast, lightweight and schema-less search backend. It ingests search texts and identifier tuples that can then be queried against in a microsecond's time.

Sonic can be used as a simple alternative to super-heavy and full-featured search backends such as Elasticsearch in some use-cases. It is capable of normalizing natural language search queries, auto-completing a search query and providing the most relevant results for a query. Sonic is an identifier index, rather than a document index; when queried, it returns IDs that can then be used to refer to the matched documents in an external database.

A strong attention to performance and code cleanliness has been given when designing Sonic. It aims at being crash-free, super-fast and puts minimum strain on server resources (our measurements have shown that Sonic - when under load - responds to search queries in the μs range, eats ~30MB RAM and has a low CPU footprint; see our benchmarks).

Tested at Rust version: rustc 1.74.1 (a28077b28 2023-12-04)

🇫🇷 Crafted in Nantes, France.

:newspaper: The Sonic project was initially announced in a post on my personal journal.

Sonic

« Sonic » is the mascot of the Sonic project. I drew it to look like a psychedelic hipster hedgehog.

Who uses it?

Crisp Scrumpy

👋 You use Sonic and you want to be listed there? Contact me.

Demo

Sonic is integrated in all Crisp search products on the Crisp platform. It is used to index half a billion objects on a $5/mth 1-vCPU SSD cloud server (as of 2019). Crisp users use it to search in their messages, conversations, contacts, helpdesk articles and more.

You can test Sonic live on: Crisp Helpdesk, and get an idea of the speed and relevance of Sonic search results. You can also test search suggestions from there: start typing at least 2 characters for a word, and get suggested a full word (press the tab key to expand suggestion). Both search and suggestions are powered by Sonic.

Demo on Crisp Helpdesk search

Sonic fuzzy search in helpdesk articles at its best. Lookup for any word or group of terms, get results instantly.

Features

  • Search terms are stored in collections, organized in buckets; you may use a single bucket, or a bucket per user on your platform if you need to search in separate indexes.
  • Search results return object identifiers, that can be resolved from an external database if you need to enrich the search results. This makes Sonic a simple word index, that points to identifier results. Sonic doesn't store any direct textual data in its index, but it still holds a word graph for auto-completion and typo corrections.
  • Search query typos are corrected if there are not enough exact-match results for a given word in a search query, Sonic tries to correct the word and tries against alternate words. You're allowed to make mistakes when searching.
  • Insert and remove items in the index; index-altering operations are light and can be committed to the server while it is running. A background tasker handles the job of consolidating the index so that the entries you have pushed or popped are quickly made available for search.
  • Auto-complete any word in real-time via the suggest operation. This helps build a snappy word suggestion feature in your end-user search interface.
  • Full Unicode compatibility on 80+ most spoken languages in the world. Sonic removes useless stop words from any text (eg. 'the' in English), after guessing the text language. This ensures any searched or ingested text is clean before it hits the index; see languages.
  • Simple protocol (Sonic Channel), that let you search your index, manage data ingestion (push in the index, pop from the index, flush a collection, flush a bucket, etc.) and perform administrative actions. Sonic Channel was designed to be lightweight on resources and simple to integrate with; read protocol specification.
  • Easy-to-use libraries, that let you connect to Sonic from your apps; see libraries.

How to use it?

Installation

Sonic is built in Rust. To install it, either download a version from the Sonic releases page, use cargo install or pull the source code from master.

👉 Each release binary comes with an .asc signature file, which can be verified using @valeriansaliou GPG public key: :key:valeriansaliou.gpg.pub.asc.

👉 Install from packages:

Sonic provides pre-built packages for Debian-based systems (Debian, Ubuntu, etc.).

Important: Sonic only provides 64 bits packages targeting Debian 12 for now (codename: bookworm). You might still be able to use them on other Debian versions, as well as Ubuntu (although they rely on a specific glibc version that might not be available on older or newer systems).

First, add the Sonic APT repository (eg. for Debian bookworm):

echo "deb [signed-by=/usr/share/keyrings/valeriansaliou_sonic.gpg] https://packagecloud.io/valeriansaliou/sonic/debian/ bookworm main" > /etc/apt/sources.list.d/valeriansaliou_sonic.list
curl -fsSL https://packagecloud.io/valeriansaliou/sonic/gpgkey | gpg --dearmor -o /usr/share/keyrings/valeriansaliou_sonic.gpg
apt-get update

Then, install the Sonic package:

apt-get install sonic

Then, edit the pre-filled Sonic configuration file:

nano /etc/sonic.cfg

Finally, restart Sonic:

service sonic restart

👉 Install from source:

If you pulled the source code from Git, you can build it using cargo:

cargo build --release

You can find the built binaries in the ./target/release directory.

Install build-essential, clang, libclang-dev, libc6-dev, g++ and llvm-dev to be able to compile the required RocksDB dependency.

Note that the following optional features can be enabled upon building Sonic: allocator-jemalloc, tokenizer-chinese and tokenizer-japanese (some might be already enabled by default).

👉 Install from Cargo:

You can install Sonic directly with cargo install:

cargo install sonic-server

Ensure that your $PATH is properly configured to source the Crates binaries, and then run Sonic using the sonic command.

Install build-essential, clang, libclang-dev, libc6-dev, g++ and llvm-dev to be able to compile the required RocksDB dependency.

👉 Install from Docker Hub:

You might find it convenient to run Sonic via Docker. You can find the pre-built Sonic image on Docker Hub as valeriansaliou/sonic.

First, pull the valeriansaliou/sonic image:

docker pull valeriansaliou/sonic:v1.4.9

Then, seed it a configuration file and run it (replace /path/to/your/sonic/config.cfg with the path to your configuration file):

docker run -p 1491:1491 -v /path/to/your/sonic/config.cfg:/etc/sonic.cfg -v /path/to/your/sonic/store/:/var/lib/sonic/store/ valeriansaliou/sonic:v1.4.9

In the configuration file, ensure that:

  • channel.inet is set to 0.0.0.0:1491 (this lets Sonic be reached from outside the container)
  • store.kv.path is set to /var/lib/sonic/store/kv/ (this lets the external KV store directory be reached by Sonic)
  • store.fst.path is set to /var/lib/sonic/store/fst/ (this lets the external FST store directory be reached by Sonic)

Sonic will be reachable from tcp://localhost:1491.

👉 Install from another source (non-official):

Other installation sources are available:

Note that those sources are non-official, meaning that they are not owned nor maintained by the Sonic project owners. The latest Sonic version available on those sources might be outdated, in comparison to the latest version available through the Sonic project.

Configuration

Use the sample config.cfg configuration file and adjust it to your own environment.

If you are looking to fine-tune your configuration, you may read our detailed configuration documentation.

Run Sonic

Sonic can be run as such:

./sonic -c /path/to/config.cfg

Perform searches and manage objects

Both searches and object management (i.e. data ingestion) is handled via the Sonic Channel protocol only. As we want to keep things simple with Sonic (similarly to how Redis does it), Sonic does not offer a HTTP endpoint or similar; connecting via Sonic Channel is the way to go when you need to interact with the Sonic search database.

Sonic distributes official libraries, that let you integrate Sonic to your apps easily. Click on a library below to see library integration documentation and code.

If you are looking for details on the raw Sonic Channel TCP-based protocol, you can read our detailed protocol documentation. It can prove handy if you are looking to code your own Sonic Channel library.

📦 Sonic Channel Libraries

1️⃣ Official Libraries

Sonic distributes official Sonic integration libraries for your programming language (official means that those libraries have been reviewed and validated by a core maintainer):

2️⃣ Community Libraries

You can find below a list of Sonic integrations provided by the community (many thanks to them!):

ℹ️ Cannot find the library for your programming language? Build your own and be referenced here! (contact me)

Which text languages are supported?

Sonic supports a wide range of languages in its lexing system. If a language is not in this list, you will still be able to push this language to the search index, but stop-words will not be eluded, which could lead to lower-quality search results.

The languages supported by the lexing system are:

  • 🇿🇦 Afrikaans
  • 🇸🇦 Arabic
  • 🇦🇲 Armenian
  • 🇦🇿 Azerbaijani
  • 🇧🇩 Bengali
  • 🇧🇬 Bulgarian
  • 🇲🇲 Burmese
  • 🏳 Catalan
  • 🇨🇳 Chinese (Simplified)
  • 🇹🇼 Chinese (Traditional)
  • 🇭🇷 Croatian
  • 🇨🇿 Czech
  • 🇩🇰 Danish
  • 🇳🇱 Dutch
  • 🇬🇧 English
  • 🏳 Esperanto
  • 🇪🇪 Estonian
  • 🇫🇮 Finnish
  • 🇫🇷 French
  • 🇬🇪 Georgian
  • 🇩🇪 German
  • 🇬🇷 Greek
  • 🇮🇳 Gujarati
  • 🇮🇱 Hebrew
  • 🇮🇳 Hindi
  • 🇭🇺 Hungarian
  • 🇮🇩 Indonesian
  • 🇮🇹 Italian
  • 🇯🇵 Japanese
  • 🇮🇳 Kannada
  • 🇰🇭 Khmer
  • 🇰🇷 Korean
  • 🏳 Latin
  • 🇱🇻 Latvian
  • 🇱🇹 Lithuanian
  • 🇮🇳 Malayalam
  • 🇮🇳 Marathi
  • 🇳🇵 Nepali
  • 🇮🇷 Persian
  • 🇵🇱 Polish
  • 🇵🇹 Portuguese
  • 🇮🇳 Punjabi
  • 🇷🇺 Russian
  • 🇷🇸 Serbian
  • 🇸🇰 Slovak
  • 🇸🇮 Slovene
  • 🇪🇸 Spanish
  • 🇸🇪 Swedish
  • 🇵🇭 Tagalog
  • 🇮🇳 Tamil
  • 🇹🇭 Thai
  • 🇹🇷 Turkish
  • 🇺🇦 Ukrainian
  • 🇵🇰 Urdu
  • 🇻🇳 Vietnamese
  • 🇮🇱 Yiddish
  • 🇿🇦 Zulu

How fast & lightweight is it?

Sonic was built for Crisp from the start. As Crisp was growing and indexing more and more search data into a full-text search SQL database, we decided it was time to switch to a proper search backend system. When reviewing Elasticsearch (ELS) and others, we found those were full-featured heavyweight systems that did not scale well with Crisp's freemium-based cost structure.

At the end, we decided to build our own search backend, designed to be simple and lightweight on resources.

You can run function-level benchmarks with the command: cargo bench --features benchmark

👩‍🔬 Benchmark #1

➡️ Scenario

We performed an extract of all messages from the Crisp team used for Crisp own customer support.

We want to import all those messages into a clean Sonic instance, and then perform searches on the index we built. We will measure the time that Sonic spent executing each operation (ie. each PUSH and QUERY commands over Sonic Channel), and group results per 1,000 operations (this outputs a mean time per 1,000 operations).

➡️ Context

Our benchmark is ran on the following computer:

  • Device: MacBook Pro (Retina, 15-inch, Mid 2014)
  • OS: MacOS 10.14.3
  • Disk: 512GB SSD (formatted under the AFS file system)
  • CPU: 2.5 GHz Intel Core i7
  • RAM: 16 GB 1600 MHz DDR3

Sonic is compiled as following:

  • Sonic version: 1.0.1
  • Rustc version: rustc 1.35.0-nightly (719b0d984 2019-03-13)
  • Compiler flags: release profile (-03 with lto)

Our dataset is as such:

  • Number of objects: ~1,000,000 messages
  • Total size: ~100MB of raw message text (this does not account for identifiers and other metas)

➡️ Scripts

The scripts we used to perform the benchmark are:

  1. PUSH script: sonic-benchmark_batch-push.js
  2. QUERY script: sonic-benchmark_batch-query.js

⏬ Results

Our findings:

  • We imported ~1,000,000 messages of dynamic length (some very long, eg. emails);
  • Once imported, the search index weights 20MB (KV) + 1.4MB (FST) on disk;
  • CPU usage during import averaged 75% of a single CPU core;
  • RAM usage for the Sonic process peaked at 28MB during our benchmark;
  • We used a single Sonic Channel TCP connection, which limits the import to a single thread (we could have load-balanced this across as many Sonic Channel connections as there are CPUs);
  • We get an import RPS approaching 4,000 operations per second (per thread);
  • We get a search query RPS approaching 1,000 operations per second (per thread);
  • On the hyper-threaded 4-cores CPU used, we could have parallelized operations to 8 virtual cores, thus theoretically increasing the import RPS to 32,000 operations / second, while the search query RPS would be increased to 8,000 operations / second (we may be SSD-bound at some point though);

Compared results per operation (on a single object):

We took a sample of 8 results from our batched operations, which produced a total of 1,000 results (1,000,000 items, with 1,000 items batched per measurement report).

This is not very scientific, but it should give you a clear idea of Sonic performances.

Time spent per operation:

OperationAverageBestWorst
PUSH275μs190μs363μs
QUERY880μs852μs1ms

Batch PUSH results as seen from our terminal (from initial index of: 0 objects):

Batch PUSH benchmark

Batch QUERY results as seen from our terminal (on index of: 1,000,000 objects):

Batch QUERY benchmark

Limitations

  • Indexed data limits: Sonic is designed for large search indexes split over thousands of search buckets per collection. An IID (ie. Internal-ID) is stored in the index as a 32 bits number, which theoretically allow up to ~4.2 billion objects to be indexed (ie. OID) per bucket. We've observed storage savings of 30% to 40%, which justifies the trade-off on large databases (versus Sonic using 64 bits IIDs). Also, Sonic only keeps the N most recently pushed results for a given word, in a sliding window way (the sliding window width can be configured).
  • Search query limits: Sonic Natural Language Processing system (NLP) does not work at the sentence-level, for storage compactness reasons (we keep the FST graph shallow as to reduce time and space complexity). It works at the word-level, and is thus able to search per-word and can predict a word based on user input, though it is unable to predict the next word in a sentence.
  • Real-time limits: the FST needs to be rebuilt every time a word is pushed or popped from the bucket graph. As this is quite heavy, Sonic batches rebuild cycles. If you have just pushed a new word to the index and you are not seeing it in the SUGGEST command yet, wait for the next rebuild cycle to kick-in, or force it with TRIGGER consolidate in a control channel.
  • Interoperability limits: The Sonic Channel protocol is the only way to read and write search entries to the Sonic search index. Sonic does not expose any HTTP API. Sonic Channel has been designed with performance and minimal network footprint in mind. If you need to access Sonic from an unsupported programming language, you can either open an issue or look at the reference node-sonic-channel implementation and build it in your target programming language.
  • Hardware limits: Sonic performs the search on the file-system directly; ie. it does not fit the index in RAM. A search query results in a lot of random accesses on the disk, which means that it will be quite slow on old-school HDDs and super-fast on newer SSDs. Do store the Sonic database on SSD-backed file systems only.

:fire: Report A Vulnerability

If you find a vulnerability in Sonic, you are more than welcome to report it directly to @valeriansaliou by sending an encrypted email to valerian@valeriansaliou.name. Do not report vulnerabilities in public GitHub issues, as they may be exploited by malicious people to target production servers running an unpatched Sonic instance.

:warning: You must encrypt your email using @valeriansaliou GPG public key: :key:valeriansaliou.gpg.pub.asc.

NPM DownloadsLast 30 Days