AI-first · Isomorphic · Zero-dependency · Lightweight · Local-first
Skalexships vector search, agent memory, natural language queries, an MCP server, and AES-256-GCM encryption in a single zero-dependency package - no server, no config, no external services. Onenpm install skalexon Node.js, Bun, Deno, browsers, and edge runtimes. All AI capabilities - cosine similarity search, semantic agent memory with compression,db.ask()NLP queries via any LLM, and a one-line MCP server for Claude Desktop and Cursor - are built into the core with zero additional dependencies.
Architecture + fit: all data lives in your process's heap -
db.connect()loads the full dataset for instant, zero-overhead access. Storage adapters control where data persists, not how much fits. Designed for single-process, local-first workloads where the dataset fits in RAM: AI agents, CLI tools, desktop apps, edge workers, offline-first apps. Not a replacement for PostgreSQL or MongoDB for large-scale, multi-process, or distributed systems.
Zero overhead. Maximum reach.
- Zero dependencies: install the package, nothing else. No driver, no ORM, no server process.
- Lightweight, and staying that way: the full core bundle is ~28 KB gzipped. That is a promise, not a snapshot - a hard byte budget is enforced in CI on every push and PR, so Skalex will not quietly bloat.
- Full build matrix: ESM, ESM minified, CJS, CJS minified, browser ESM (
dist/skalex.browser.js, nonode:*imports), UMD/IIFE (dist/skalex.umd.min.js, CDN default) - Runs everywhere: Node.js ≥18, Bun, Deno 2.x, browser (Chrome/Firefox/Safari), edge runtimes. Verified by 1,164 tests (935 unit/integration + 229 cross-runtime smoke) gated by CI on every push and PR
- Pluggable storage:
FsAdapter(Node),LocalStorageAdapter(browser),EncryptedAdapter(AES-256-GCM), or bring your own
Queries that scale with your data.
- Full operator set:
$eq$ne$gt$gte$lt$lte$in$nin$regex$fn$or$and$not - Dot-notation nested field queries
- Secondary field indexes: O(1) lookups via
IndexEngine - Unique constraints, filter pre-sorter for performance
- Compound indexes:
createCollection(name, { indexes: [["field1", "field2"]] }) - Logical operators:
$or,$and,$notfor composable filter conditions
Your data stays clean.
- Zero-dependency schema validation:
type,required,unique,enum - Strict mode:
createCollection(name, { strict: true })rejects unknown fields.onSchemaError: "warn" | "strip"for softer handling - Versioned migrations:
addMigration({ version, up }), auto-run onconnect() - TTL documents:
insertOne(doc, { ttl: "30m" }), swept on connect.defaultTtlper collection.ttlSweepIntervalfor live processes - Transactions: explicit collection/query handles, lazy copy-on-first-write snapshots, configurable timeout, serialised execution, stale proxy detection, collection locking (non-tx writes to tx-touched collections throw
ERR_SKALEX_TX_COLLECTION_LOCKED), configurable deferred-effect error strategy - Change log:
createCollection(name, { changelog: true }), point-in-time restore - Soft deletes:
createCollection(name, { softDelete: true }),col.restore(),{ includeDeleted } - Document versioning:
createCollection(name, { versioning: true }), auto-increments_version - Capped collections:
createCollection(name, { maxDocs: N }), FIFO eviction
Semantic search, built in.
insertOne / insertManywith{ embed: "field" }: auto-embed on insertcollection.search(query, { filter, limit, minScore }): cosine similarity + hybridcollection.similar(id): nearest-neighbour lookupdb.embed(text): direct embedding access- Built-in adapters: OpenAI (
text-embedding-3-small) and Ollama (local, zero cost)
Your database speaks English.
db.ask(collection, nlQuery): translate natural language to a filter via LLM. Results cacheddb.useMemory(sessionId): episodic agent memory withremember,recall,context,compress- Built-in language model adapters: OpenAI, Anthropic, Ollama
db.schema(collection): infer or return declared schema as a plain object
Know exactly what's happening.
collection.count / sum / avg / groupBy: aggregation with optional filter and dot-notationdb.stats(collection?): count, estimated size, average doc sizeslowQueryLogoption +db.slowQueries(): capture slowfindandsearchcalls
React to every change.
collection.watch(filter?, callback?): observe mutations. Callback orAsyncIterableIteratorcollection.watch(filter, { maxBufferSize }): iterator backpressure. Oldest events dropped when buffer is full,iter.droppedreports the countdb.watch(callback): cross-collection global observer. Fires for every mutation across all collections- Events:
{ op, collection, doc, prev? }emitted after every insert, update, delete, restore
AI agents, natively wired.
db.mcp(opts): expose the database as MCP tools for AI agents- Compatible with Claude Desktop, Cursor, and any MCP client
stdiotransport (default) andhttp + SSEtransport- Tools:
find,insert,update,delete,search,ask,schema,collections - Access control:
scopesmap per collection.read/write/admin - Named-predicate allowlist:
db.mcp({ predicates: { isHighValue: (doc) => ... } })lets agents reference server-side$fnpredicates by name without code crossing the wire
Extend anything.
db.use(plugin): register pre/post hooks on all operations- Hooks:
beforeInsert,afterInsert,beforeUpdate,afterUpdate,beforeDelete,afterDelete,afterRestore,beforeFind,afterFind,beforeSearch,afterSearch - All hooks awaited in order. Errors propagate to the caller per the
deferredEffectErrorsstrategy
Full visibility per session.
db.sessionStats(sessionId?): reads, writes, lastActive per sessionsessionoption on all reads and writes: automatic accumulation
Deploy anywhere.
D1Adapter: Cloudflare D1 / Workers edge SQLiteBunSQLiteAdapter: Bun-nativebun:sqlite.:memory:or file pathLibSQLAdapter: LibSQL / Turso client adapter
Built for developers who value their time.
db.transaction(fn): owned in-memory snapshot/rollback. Transaction writes deferred until commit, with adapter-dependent disk atomicitydb.seed(fixtures): idempotent fixture seedingdb.dump()/db.inspect(): snapshot and metadatadb.namespace(id): isolated sub-instances per tenant / userdb.import(path): JSON import. Collection name derived from filenamedb.renameCollection(from, to): in-memory + on-disk renamecollection.upsert(),collection.upsertMany(docs, matchKey),insertOne({ ifNotExists }): safe idempotent writesautoSave: true: persist after every write without{ save: true }on every callencrypt: { key }: AES-256-GCM at-rest encryption, transparent to all callerssessionoption on all reads and writes: audit trail + session statsdebug: true: connect/disconnect logging- ES2024
using:await using db = new Skalex(...)auto-disconnects on scope exit viaSymbol.asyncDispose - Typed error hierarchy:
SkalexError,ValidationError,UniqueConstraintError,TransactionError,PersistenceError,AdapterError,QueryErrorwith stableERR_SKALEX_<SUBSYSTEM>_<SPECIFIC>codes
npm install skalex@alphaThese docs target v4.0.0-alpha.7. This is a prerelease. Pin the exact version and review the migration guide before upgrading.
npm install skalexinstalls the last stable v3 - use@alphato get v4.
Requires Node.js ≥ 18.
Or via CDN (no bundler, no npm - browser direct):
ESM - recommended for real browser apps. Connectors import alongside Skalex:
<script type="module">
import Skalex from "https://cdn.jsdelivr.net/npm/skalex@4.0.0-alpha.7/dist/skalex.browser.js";
import { LocalStorageAdapter } from "https://cdn.jsdelivr.net/npm/skalex@4.0.0-alpha.7/src/connectors/storage/browser.js";
// browser.js also exports EncryptedAdapter for AES-256-GCM at-rest encryption
const db = new Skalex({ adapter: new LocalStorageAdapter({ namespace: "myapp" }) });
await db.connect();
</script>With npm + bundler, use the connectors subpackage:
import Skalex, { Collection, ValidationError, UniqueConstraintError } from 'skalex';
// Scoped barrels (tree-shakeable, recommended)
import { StorageAdapter, FsAdapter, LocalStorageAdapter, EncryptedAdapter,
BunSQLiteAdapter, D1Adapter, LibSQLAdapter } from 'skalex/connectors/storage';
import { EmbeddingAdapter, OpenAIEmbeddingAdapter,
OllamaEmbeddingAdapter } from 'skalex/connectors/embedding';
import { LLMAdapter, OpenAILLMAdapter, AnthropicLLMAdapter,
OllamaLLMAdapter } from 'skalex/connectors/llm';
// Or pull everything from the root barrel
import { StorageAdapter, FsAdapter, EmbeddingAdapter, OpenAIEmbeddingAdapter,
LLMAdapter, OpenAILLMAdapter } from 'skalex/connectors';IIFE - exposes window.Skalex, for quick demos or environments that can't use ESM:
<!-- jsDelivr (recommended) -->
<script src="https://cdn.jsdelivr.net/npm/skalex@4.0.0-alpha.7"></script>
<!-- unpkg -->
<script src="https://unpkg.com/skalex@4.0.0-alpha.7"></script>import Skalex from "skalex";
const db = new Skalex({ path: "./data", format: "json" });
await db.connect();
const users = db.useCollection("users");
const alice = await users.insertOne({ name: "Alice", role: "admin" });
const { docs } = await users.find({ role: "admin" });
await users.updateOne({ name: "Alice" }, { score: { $inc: 10 } });
await users.deleteOne({ name: "Alice" });
await db.disconnect();import Skalex from "skalex";
const db = new Skalex({
path: "./data",
ai: { provider: "openai", apiKey: process.env.OPENAI_KEY },
});
await db.connect();
const articles = db.useCollection("articles");
await articles.insertMany([
{ title: "Intro to Skalex", content: "A zero-dependency JS database..." },
{ title: "Vector search 101", content: "Cosine similarity measures angle between vectors..." },
], { embed: "content" });
const { docs, scores } = await articles.search("how do I set up a JS database?", { limit: 2 });
console.log(docs[0].title); // most relevant result
await db.disconnect();Everything you need to go from zero to production:
tarekraafat.github.io/skalex 📔
See what's shipping next: Roadmap
- Stack Overflow: stackoverflow.com/questions/tagged/skalex
- GitHub Discussions: github.com/TarekRaafat/skalex/discussions
Tarek Raafat
- Email: tarek.m.raafat@gmail.com
- Github: github.com/TarekRaafat
| Project | Description |
|---|---|
| autoComplete.js | Simple, lightweight, pure vanilla JavaScript autocomplete library with zero dependencies |
| Eleva.js | Ultra-lightweight (~2.5KB) pure vanilla JavaScript framework with signal-based reactivity |
For a manual or LLM-assisted upgrade, follow the migration checklist and apply every change required by your starting version.
Transaction and migration callbacks now receive restricted collection handles. Use their collection methods and await each write. Commit owns saving. HTTP clients must satisfy Origin and Host validation. See the migration guide for upgrade requirements and failure behavior.
Reads remain shallow by default. Use collection mutations to change stored data. Use clone: true before editing a returned nested value independently:
const draft = await users.findOne({ _id: id }, { clone: true });
if (draft?.profile) draft.profile.displayName = "Preview";Cloning is optional and may reject uncloneable values. Failed upsert batches can retain completed items, and commit recovery covers bounded synchronous failures. See read ownership and commit recovery for the precise contracts.
Transaction commits persist a recovery marker before batch data. If a batch is interrupted, reconnect reports an incomplete-flush warning and still loads the stored data. This detects possible inconsistency. It does not automatically repair partial writes. After inspection or repair, call await db.acknowledgeIncompleteFlush() outside transactions to clear the warning. See the persistence documentation before recovering storage.
Released under the Apache 2.0 license.
© 2026 Tarek Raafat
Alpha.7 also protects collection lifetimes and transaction save admission. Reacquire collection and memory handles after disconnect, import adapter constructors through connector subpaths, and review the migration guide before upgrading.
