@monlite/core 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/dist/index.cjs +76 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +76 -1
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -551,6 +551,40 @@ Write your own against the `MonlitePlugin` interface (`init` / `afterWrite` /
|
|
|
551
551
|
|
|
552
552
|
---
|
|
553
553
|
|
|
554
|
+
## The local backend for AI agents
|
|
555
|
+
|
|
556
|
+
monlite aims to be your **entire local data layer** — one embedded `.db`, one
|
|
557
|
+
install — collapsing the services you'd otherwise run (Mongo, Qdrant, Redis) for
|
|
558
|
+
a local/edge/desktop agent. Documents and vectors are core + a plugin; the
|
|
559
|
+
Redis-style primitives are small companion packages:
|
|
560
|
+
|
|
561
|
+
| Package | Replaces (locally) | Provides |
|
|
562
|
+
|---|---|---|
|
|
563
|
+
| [`@monlite/kv`](https://www.npmjs.com/package/@monlite/kv) | Redis cache | Synchronous `get/set/incr` KV with TTLs |
|
|
564
|
+
| [`@monlite/queue`](https://www.npmjs.com/package/@monlite/queue) | Redis / BullMQ | Durable job queue — retries, backoff, delays, priorities, concurrency |
|
|
565
|
+
| [`@monlite/cron`](https://www.npmjs.com/package/@monlite/cron) | cron / scheduler | Persisted cron schedules; composes with the queue |
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
import { kv } from "@monlite/kv";
|
|
569
|
+
import { createQueue } from "@monlite/queue";
|
|
570
|
+
import { createCron } from "@monlite/cron";
|
|
571
|
+
|
|
572
|
+
const cache = kv(db);
|
|
573
|
+
cache.set("session:42", { user: "ali" }, { ttl: 60_000 });
|
|
574
|
+
|
|
575
|
+
const queue = createQueue(db, { maxAttempts: 3 });
|
|
576
|
+
queue.process("email", async (job) => send(job.payload), { concurrency: 5 });
|
|
577
|
+
queue.add("email", { to: "ali@example.com" });
|
|
578
|
+
|
|
579
|
+
createCron(db).schedule("nightly", "0 0 * * *", () => queue.add("report", {}));
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
These target **local / edge / desktop** runtimes — not a distributed cloud-scale
|
|
583
|
+
Redis/Mongo/Qdrant replacement. For scale, keep the real services and
|
|
584
|
+
[`@monlite/sync`](https://www.npmjs.com/package/@monlite/sync) to them.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
554
588
|
## Drivers & zero dependencies
|
|
555
589
|
|
|
556
590
|
monlite talks to SQLite through a tiny driver adapter, so it runs on two
|
|
@@ -578,6 +612,30 @@ based on your Node version and whether you want the extra dependency.
|
|
|
578
612
|
|
|
579
613
|
---
|
|
580
614
|
|
|
615
|
+
## Encryption at rest
|
|
616
|
+
|
|
617
|
+
Encrypt the whole database file with a key. Install the drop-in cipher driver
|
|
618
|
+
and pass an `encryption` option:
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
npm install better-sqlite3-multiple-ciphers
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
const db = createDb("./secure.db", { encryption: { key: process.env.DB_KEY } });
|
|
626
|
+
// ...use db exactly as normal — everything on disk is encrypted.
|
|
627
|
+
|
|
628
|
+
db.rekey(newKey); // rotate the key
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
- A **wrong or missing key throws `MonliteEncryptionError`** when opening.
|
|
632
|
+
- Optional `cipher` selects the scheme (`"sqlcipher"`, `"chacha20"`,
|
|
633
|
+
`"aes256cbc"`, …); the default is ChaCha20-Poly1305.
|
|
634
|
+
- Encryption requires `better-sqlite3-multiple-ciphers` (a drop-in for
|
|
635
|
+
`better-sqlite3`) and is **not** available on the `node:sqlite` backend.
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
581
639
|
## How it works
|
|
582
640
|
|
|
583
641
|
Every collection is a single SQLite table:
|
|
@@ -610,6 +668,22 @@ future-proofing.
|
|
|
610
668
|
|
|
611
669
|
---
|
|
612
670
|
|
|
671
|
+
## Examples
|
|
672
|
+
|
|
673
|
+
Runnable demos live in [`examples/`](examples/): a notes app (CRUD + full-text
|
|
674
|
+
search + live queries), AI-agent memory (vector + hybrid search), and local-first
|
|
675
|
+
sync. `cd examples && npm install && node notes.mjs`.
|
|
676
|
+
|
|
677
|
+
## Benchmarks
|
|
678
|
+
|
|
679
|
+
[`docs/BENCHMARKS.md`](docs/BENCHMARKS.md) compares monlite to the raw SQLite
|
|
680
|
+
driver, NeDB, and lowdb (`pnpm bench` to reproduce). In short: ~150k–250k
|
|
681
|
+
ops/sec, roughly 2× the raw-driver overhead for the full document API, and it
|
|
682
|
+
**stays flat on indexed reads where JSON-file stores degrade** (lowdb point reads
|
|
683
|
+
are ~15× slower at 10k docs).
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
613
687
|
## License
|
|
614
688
|
|
|
615
689
|
MIT 🌙
|
package/dist/index.cjs
CHANGED
|
@@ -119,6 +119,12 @@ var MonliteQueryError = class extends MonliteError {
|
|
|
119
119
|
this.name = "MonliteQueryError";
|
|
120
120
|
}
|
|
121
121
|
};
|
|
122
|
+
var MonliteEncryptionError = class extends MonliteError {
|
|
123
|
+
constructor(message, options) {
|
|
124
|
+
super(message, options);
|
|
125
|
+
this.name = "MonliteEncryptionError";
|
|
126
|
+
}
|
|
127
|
+
};
|
|
122
128
|
var MonliteConstraintError = class extends MonliteError {
|
|
123
129
|
collection;
|
|
124
130
|
constructor(message, options) {
|
|
@@ -1317,6 +1323,7 @@ var AutoIndexer = class {
|
|
|
1317
1323
|
|
|
1318
1324
|
// src/driver/better-sqlite3.ts
|
|
1319
1325
|
var STMT_CACHE_MAX = 256;
|
|
1326
|
+
var quote = (s) => s.replace(/'/g, "''");
|
|
1320
1327
|
var BetterSqlite3Driver = class {
|
|
1321
1328
|
name = "better-sqlite3";
|
|
1322
1329
|
raw;
|
|
@@ -1327,6 +1334,9 @@ var BetterSqlite3Driver = class {
|
|
|
1327
1334
|
this.raw = new BetterSqlite3(filename, {
|
|
1328
1335
|
readonly: options.readonly ?? false
|
|
1329
1336
|
});
|
|
1337
|
+
if (options.encryption) {
|
|
1338
|
+
this.applyKey(options.encryption.key, options.encryption.cipher);
|
|
1339
|
+
}
|
|
1330
1340
|
this.raw.pragma("foreign_keys = ON");
|
|
1331
1341
|
this.raw.pragma(`busy_timeout = ${options.busyTimeout ?? 5e3}`);
|
|
1332
1342
|
if (!options.readonly && (options.wal ?? true)) {
|
|
@@ -1355,6 +1365,28 @@ var BetterSqlite3Driver = class {
|
|
|
1355
1365
|
transaction(fn) {
|
|
1356
1366
|
return this.raw.transaction(fn)();
|
|
1357
1367
|
}
|
|
1368
|
+
/** Apply the encryption key and verify it by reading the schema. */
|
|
1369
|
+
applyKey(key, cipher) {
|
|
1370
|
+
if (cipher) this.raw.pragma(`cipher='${quote(cipher)}'`);
|
|
1371
|
+
this.raw.pragma(`key='${quote(key)}'`);
|
|
1372
|
+
try {
|
|
1373
|
+
this.raw.exec("SELECT count(*) FROM sqlite_master");
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
this.raw.close();
|
|
1376
|
+
throw new MonliteEncryptionError(
|
|
1377
|
+
"Failed to open the encrypted database: the key is incorrect, or the file is not encrypted.",
|
|
1378
|
+
{ cause: err }
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
rekey(key, cipher) {
|
|
1383
|
+
if (cipher) this.raw.pragma(`cipher='${quote(cipher)}'`);
|
|
1384
|
+
const mode = String(this.raw.pragma("journal_mode", { simple: true }));
|
|
1385
|
+
const wasWal = mode.toLowerCase() === "wal";
|
|
1386
|
+
if (wasWal) this.raw.pragma("journal_mode = DELETE");
|
|
1387
|
+
this.raw.pragma(`rekey='${quote(key)}'`);
|
|
1388
|
+
if (wasWal) this.raw.pragma("journal_mode = WAL");
|
|
1389
|
+
}
|
|
1358
1390
|
close() {
|
|
1359
1391
|
this.cache.clear();
|
|
1360
1392
|
this.raw.close();
|
|
@@ -1370,6 +1402,11 @@ var NodeSqliteDriver = class {
|
|
|
1370
1402
|
cache = /* @__PURE__ */ new Map();
|
|
1371
1403
|
depth = 0;
|
|
1372
1404
|
constructor(nodeSqlite, filename, options) {
|
|
1405
|
+
if (options.encryption) {
|
|
1406
|
+
throw new MonliteError(
|
|
1407
|
+
"Encryption is not supported on the node:sqlite backend. Use better-sqlite3 with the better-sqlite3-multiple-ciphers package."
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1373
1410
|
this.verbose = options.verbose;
|
|
1374
1411
|
const { DatabaseSync } = nodeSqlite;
|
|
1375
1412
|
this.raw = new DatabaseSync(filename, {
|
|
@@ -1445,6 +1482,14 @@ function loadBetterSqlite3() {
|
|
|
1445
1482
|
return null;
|
|
1446
1483
|
}
|
|
1447
1484
|
}
|
|
1485
|
+
function loadCipherSqlite3() {
|
|
1486
|
+
try {
|
|
1487
|
+
const mod = req("better-sqlite3-multiple-ciphers");
|
|
1488
|
+
return mod?.default ?? mod;
|
|
1489
|
+
} catch {
|
|
1490
|
+
return null;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1448
1493
|
function loadNodeSqlite() {
|
|
1449
1494
|
try {
|
|
1450
1495
|
return req("node:sqlite");
|
|
@@ -1454,6 +1499,20 @@ function loadNodeSqlite() {
|
|
|
1454
1499
|
}
|
|
1455
1500
|
function createDriver(filename, options = {}) {
|
|
1456
1501
|
const choice = options.driver ?? "auto";
|
|
1502
|
+
if (options.encryption) {
|
|
1503
|
+
if (choice === "node:sqlite") {
|
|
1504
|
+
throw new MonliteError(
|
|
1505
|
+
`Encryption is not supported on the node:sqlite backend. Use better-sqlite3 with the better-sqlite3-multiple-ciphers package.`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
const cipher = loadCipherSqlite3();
|
|
1509
|
+
if (!cipher) {
|
|
1510
|
+
throw new MonliteError(
|
|
1511
|
+
`Encryption requires the "better-sqlite3-multiple-ciphers" package (a drop-in for better-sqlite3). Run \`npm install better-sqlite3-multiple-ciphers\`.`
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
return new BetterSqlite3Driver(cipher, filename, options);
|
|
1515
|
+
}
|
|
1457
1516
|
if (choice === "better-sqlite3") {
|
|
1458
1517
|
const mod = loadBetterSqlite3();
|
|
1459
1518
|
if (!mod) {
|
|
@@ -1893,6 +1952,7 @@ var Monlite = class {
|
|
|
1893
1952
|
$sync;
|
|
1894
1953
|
collections = /* @__PURE__ */ new Map();
|
|
1895
1954
|
plugins;
|
|
1955
|
+
encrypted;
|
|
1896
1956
|
closed = false;
|
|
1897
1957
|
constructor(filename, options = {}) {
|
|
1898
1958
|
this.driver = createDriver(filename, {
|
|
@@ -1901,8 +1961,10 @@ var Monlite = class {
|
|
|
1901
1961
|
wal: options.wal,
|
|
1902
1962
|
busyTimeout: options.busyTimeout,
|
|
1903
1963
|
allowExtensions: options.allowExtensions,
|
|
1964
|
+
encryption: options.encryption,
|
|
1904
1965
|
verbose: options.verbose
|
|
1905
1966
|
});
|
|
1967
|
+
this.encrypted = options.encryption !== void 0;
|
|
1906
1968
|
this.autoIndexer = new AutoIndexer(
|
|
1907
1969
|
this.driver,
|
|
1908
1970
|
options.autoIndex ?? true,
|
|
@@ -2050,6 +2112,19 @@ var Monlite = class {
|
|
|
2050
2112
|
this.driver.exec(`VACUUM INTO '${path.replace(/'/g, "''")}'`);
|
|
2051
2113
|
return Promise.resolve();
|
|
2052
2114
|
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Rotate the encryption key. Only valid for a database opened with the
|
|
2117
|
+
* `encryption` option; throws otherwise. Pass `cipher` to also change scheme.
|
|
2118
|
+
*/
|
|
2119
|
+
rekey(key, cipher) {
|
|
2120
|
+
this.assertOpen();
|
|
2121
|
+
if (!this.encrypted || !this.driver.rekey) {
|
|
2122
|
+
throw new MonliteError(
|
|
2123
|
+
"rekey() requires a database opened with the `encryption` option."
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
this.driver.rekey(key, cipher);
|
|
2127
|
+
}
|
|
2053
2128
|
/** Close the underlying SQLite connection. */
|
|
2054
2129
|
$disconnect() {
|
|
2055
2130
|
if (!this.closed) {
|
|
@@ -2069,6 +2144,7 @@ function createDb(filename, options) {
|
|
|
2069
2144
|
exports.Collection = Collection;
|
|
2070
2145
|
exports.Monlite = Monlite;
|
|
2071
2146
|
exports.MonliteConstraintError = MonliteConstraintError;
|
|
2147
|
+
exports.MonliteEncryptionError = MonliteEncryptionError;
|
|
2072
2148
|
exports.MonliteError = MonliteError;
|
|
2073
2149
|
exports.MonliteForeignKeyError = MonliteForeignKeyError;
|
|
2074
2150
|
exports.MonliteNotNullError = MonliteNotNullError;
|