A very simple, naive implementation of a JSON document store with some bitmap indexes in the mix. Module primarily but not exclusively for use with Canvas (https://github.com/canvas-ai/canvas-server)
- LMDB, to-be-replaced by pouchdb or rxdb as the main KV backend (https://www.npmjs.com/package/lmdb)
- Compressed (roaring) bitmaps (https://www.npmjs.com/package/roaring)
- FlexSearch for full-text search (https://www.npmjs.com/package/flexsearch)
- LanceDB (https://www.npmjs.com/package/@lancedb/lancedb)
- Simple LMDB KV store with enforced document schemas (See
./src/schemasfor more details) - Every data abstraction schema (File, Note, Browser tab, Email etc) defines its own set of indexing options
- algorithm/checksum | docID
Example: sha1/4e1243.. => document ID) - timestamp | docID
Example: 20250212082411.1234 => document ID
We could use composite keys and LMDB range queries instead (timestamp/docID => document) but for now this way is more practical.
The following bitmap index prefixes are enforced to organize and filter documents:
internal/- Internal bitmapscontext/- Context path bitmaps, used internally by Canvas (as context tree nodes, context/uuid)data/abstraction/<schema>- Schema type filters (incl subtrees like data/abstraction/file/ext/json)data/mime/<type>data/content/encoding/<encoding>client/os/<os>client/application/<application>client/device/<device-id>client/network/<network-id>-We support storing documents on multiple backends(StoreD), when a canvas application connects from a certain network, not all backends may be reachable(your home NAS from work for example)user/tag/- Generic tag bitmapscustom/- Throw what you need here
SynapsD follows a hybrid API pattern - for better or worse -
Single document operations:
try {
const docId = await db.insertDocument(doc);
// Success case
} catch (error) {
// Error handling
}Available single document operations:
insertDocument(doc, contextSpec, featureBitmapArray)updateDocument(docId, updateData, contextSpec, featureBitmapArray)deleteDocument(docId)getDocument(docId)getDocumentById(id)getDocumentByChecksumString(checksumString)
Batch operations return a result object:
const result = await db.insertDocumentArray(docs);
if (result.failed.length > 0) {
// Handle partial failures
}
// Access successful operations
const successfulIds = result.successful.map(s => s.id);
// Using findDocuments
const result = await db.findDocuments('/some/path', ['feature1'], ['filter1']);
if (result.error) {
console.error('Query failed:', result.error);
} else {
console.log(`Found ${result.count} documents:`, result.data);
}
// Using query
const queryResult = await db.query('some query', ['context1'], ['feature1']);
if (queryResult.error) {
console.error('Query failed:', queryResult.error);
} else {
console.log(`Found ${queryResult.count} documents:`, queryResult.data);
}Result object structure:
interface BatchResult {
successful: Array<{
index: number; // Original array index
id: number; // Document ID
}>;
failed: Array<{
index: number; // Original array index
error: string; // Error message
doc: any; // Original document
}>;
total: number; // Total number of operations
}Available batch operations:
insertDocumentArray(docs, contextSpec, featureBitmapArray)updateDocumentArray(docs, contextSpec, featureBitmapArray)deleteDocumentArray(docIds)getDocumentsByIdArray(ids)getDocumentsByChecksumStringArray(checksums)
Pagination is supported for all queries. Default page size is 100 documents.
Options:
limit(number): page size (default 100)offset(number): starting index (default 0)page(number): 1-based page number (ignored ifoffsetprovided)parse(boolean): parse into schema instances (default true)
Usage:
// First page (implicit): limit=100, offset=0
const docs = await db.findDocuments(contextSpec, featureBitmapArray, [], { limit: 100 });
console.log(docs.length, docs.count); // docs has .count metadata
// Second page via page
const page2 = await db.findDocuments(contextSpec, featureBitmapArray, [], { page: 2, limit: 100 });
// Or using offset directly
const next100 = await db.findDocuments(contextSpec, featureBitmapArray, [], { offset: 100, limit: 100 });Return shape:
type QueryResultArray = Array<any> & { count: number; error: string | null };Available query operations:
findDocuments(contextSpec, featureBitmapArray, filterArray, options)query(query, contextBitmapArray, featureBitmapArray, filterArray)ftsQuery(query, contextBitmapArray, featureBitmapArray, filterArray)
SynapsD uses standard JavaScript Error objects with specific error types:
ValidationError: Document validation failedNotFoundError: Document not foundDuplicateError: Document already existsDatabaseError: General database errors
Example:
try {
await db.insertDocument(doc);
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation error
} else if (error instanceof DatabaseError) {
// Handle database error
}
}- LMDB Documentation
- Node.js Crypto Documentation
- Roaring Bitmaps
- LlamaIndex
- FlexSearch
- LanceDB
- Why-not-indices
Licensed under AGPL-3.0-or-later. See main project LICENSE file.
This project is funded by Augmentd Labs