@monlite/core 0.1.0 → 0.2.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 +46 -7
- package/dist/index.cjs +160 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -6
- package/dist/index.d.ts +40 -6
- package/dist/index.js +159 -29
- package/dist/index.js.map +1 -1
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -26,11 +26,18 @@ That's it. No setup. No config. Your data is in `app.db`.
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
npm install @monlite/core
|
|
29
|
-
# or: pnpm add @monlite/core / yarn add @monlite/core
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
monlite
|
|
33
|
-
|
|
31
|
+
**monlite has zero required dependencies.** On **Node 22.5+** it uses the
|
|
32
|
+
built-in [`node:sqlite`](https://nodejs.org/api/sqlite.html) engine out of the
|
|
33
|
+
box. To run on Node 18/20 — or to avoid `node:sqlite`'s experimental warning —
|
|
34
|
+
also install the (optional) native driver:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @monlite/core better-sqlite3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
See [Drivers & zero dependencies](#drivers--zero-dependencies) below.
|
|
34
41
|
|
|
35
42
|
---
|
|
36
43
|
|
|
@@ -65,6 +72,7 @@ const mem = createDb(":memory:"); // in-memory database
|
|
|
65
72
|
|
|
66
73
|
```ts
|
|
67
74
|
const db = createDb("./app.db", {
|
|
75
|
+
driver: "auto", // "auto" | "better-sqlite3" | "node:sqlite" (default: "auto")
|
|
68
76
|
autoIndex: true, // auto-create indexes on hot JSON paths (default: true)
|
|
69
77
|
autoIndexAfter: 10, // create an index after a path is queried N times (default: 10)
|
|
70
78
|
readonly: false, // open read-only (default: false)
|
|
@@ -276,7 +284,9 @@ await db.$transaction((tx) => {
|
|
|
276
284
|
});
|
|
277
285
|
```
|
|
278
286
|
|
|
279
|
-
Need the raw driver? `db.sqlite` is the underlying
|
|
287
|
+
Need the raw driver? `db.sqlite` is the underlying native handle (a
|
|
288
|
+
`better-sqlite3` `Database` or a `node:sqlite` `DatabaseSync`, depending on the
|
|
289
|
+
active backend), and `db.driverName` tells you which one is in use.
|
|
280
290
|
|
|
281
291
|
---
|
|
282
292
|
|
|
@@ -302,9 +312,37 @@ await db.$collections(); // string[] of collection names
|
|
|
302
312
|
await db.$drop("users"); // drop a collection and its data
|
|
303
313
|
await db.$dropAll(); // drop everything
|
|
304
314
|
await db.$disconnect(); // close the connection
|
|
305
|
-
db.sqlite; // the underlying
|
|
315
|
+
db.sqlite; // the underlying native driver handle
|
|
316
|
+
db.driverName; // "better-sqlite3" | "node:sqlite"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Drivers & zero dependencies
|
|
322
|
+
|
|
323
|
+
monlite talks to SQLite through a tiny driver adapter, so it runs on two
|
|
324
|
+
interchangeable backends:
|
|
325
|
+
|
|
326
|
+
| Backend | When it's used | Notes |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| **`node:sqlite`** | Built into Node **22.5+** | **Zero dependencies.** Still flagged experimental by Node, so it prints a one-time `ExperimentalWarning`. |
|
|
329
|
+
| **`better-sqlite3`** | When the package is installed | Battle-tested native driver. Works on Node 18/20/22, no warning. Install it yourself: `npm i better-sqlite3`. |
|
|
330
|
+
|
|
331
|
+
By default (`driver: "auto"`) monlite uses `better-sqlite3` if it's installed,
|
|
332
|
+
otherwise falls back to the built-in `node:sqlite`. Force one explicitly:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
createDb("./app.db", { driver: "node:sqlite" }); // zero-dep (Node 22.5+)
|
|
336
|
+
createDb("./app.db", { driver: "better-sqlite3" }); // native, no warning
|
|
306
337
|
```
|
|
307
338
|
|
|
339
|
+
Both backends pass the exact same test suite, so behavior is identical — pick
|
|
340
|
+
based on your Node version and whether you want the extra dependency.
|
|
341
|
+
|
|
342
|
+
> Want truly zero dependencies on Node 22.5+? Just `npm install @monlite/core`
|
|
343
|
+
> and don't install `better-sqlite3`. To silence the experimental warning,
|
|
344
|
+
> either install `better-sqlite3` or run Node with `--no-warnings`.
|
|
345
|
+
|
|
308
346
|
---
|
|
309
347
|
|
|
310
348
|
## How it works
|
|
@@ -325,8 +363,9 @@ and `updated_at` are real columns. SQLite's built-in `json_extract` /
|
|
|
325
363
|
`json_each` power all document queries. No columns are added per field, so
|
|
326
364
|
there is no schema and no migration — ever.
|
|
327
365
|
|
|
328
|
-
All operations are synchronous under the hood (
|
|
329
|
-
exposed as `async` (they return Promises) for API consistency and
|
|
366
|
+
All operations are synchronous under the hood (both SQLite backends are sync)
|
|
367
|
+
but are exposed as `async` (they return Promises) for API consistency and
|
|
368
|
+
future-proofing.
|
|
330
369
|
|
|
331
370
|
### Notes & limitations
|
|
332
371
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var Database = require('better-sqlite3');
|
|
4
3
|
var crypto = require('crypto');
|
|
4
|
+
var module$1 = require('module');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
9
|
-
|
|
10
|
-
// src/db.ts
|
|
6
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
7
|
+
// src/id.ts
|
|
11
8
|
var PROCESS_UNIQUE = crypto.randomBytes(5);
|
|
12
9
|
var counter = crypto.randomBytes(3).readUIntBE(0, 3);
|
|
13
10
|
function objectId() {
|
|
@@ -445,7 +442,7 @@ var Collection = class {
|
|
|
445
442
|
initialized = false;
|
|
446
443
|
trackPath = (path) => this.mon.autoIndexer.track(this.name, path);
|
|
447
444
|
get db() {
|
|
448
|
-
return this.mon.
|
|
445
|
+
return this.mon.driver;
|
|
449
446
|
}
|
|
450
447
|
ensureTable() {
|
|
451
448
|
if (this.initialized) return;
|
|
@@ -491,13 +488,12 @@ var Collection = class {
|
|
|
491
488
|
const stmt = this.db.prepare(
|
|
492
489
|
`INSERT INTO "${this.name}" (_id, data, created_at, updated_at) VALUES (?, ?, ?, ?)`
|
|
493
490
|
);
|
|
494
|
-
|
|
495
|
-
for (const item of
|
|
491
|
+
this.db.transaction(() => {
|
|
492
|
+
for (const item of args.data) {
|
|
496
493
|
const row = this.prepareInsert(item);
|
|
497
494
|
stmt.run(row._id, row.data, row.created_at, row.updated_at);
|
|
498
495
|
}
|
|
499
496
|
});
|
|
500
|
-
insertAll(args.data);
|
|
501
497
|
return { count: args.data.length };
|
|
502
498
|
}
|
|
503
499
|
/* ------------------------------ read ------------------------------ */
|
|
@@ -550,7 +546,7 @@ var Collection = class {
|
|
|
550
546
|
const stmt = this.db.prepare(
|
|
551
547
|
`UPDATE "${this.name}" SET data = ?, updated_at = ? WHERE _id = ?`
|
|
552
548
|
);
|
|
553
|
-
|
|
549
|
+
return this.db.transaction(() => {
|
|
554
550
|
const out = [];
|
|
555
551
|
for (const row of rows) {
|
|
556
552
|
const current = JSON.parse(row.data);
|
|
@@ -565,7 +561,6 @@ var Collection = class {
|
|
|
565
561
|
}
|
|
566
562
|
return out;
|
|
567
563
|
});
|
|
568
|
-
return txn();
|
|
569
564
|
}
|
|
570
565
|
async update(args) {
|
|
571
566
|
return this.runUpdate(args.where, args.data, true)[0] ?? null;
|
|
@@ -595,10 +590,9 @@ var Collection = class {
|
|
|
595
590
|
const rows = this.db.prepare(selectSql).all(...params);
|
|
596
591
|
if (!rows.length) return [];
|
|
597
592
|
const stmt = this.db.prepare(`DELETE FROM "${this.name}" WHERE _id = ?`);
|
|
598
|
-
|
|
593
|
+
this.db.transaction(() => {
|
|
599
594
|
for (const row of rows) stmt.run(row._id);
|
|
600
595
|
});
|
|
601
|
-
txn();
|
|
602
596
|
return rows.map((r) => this.rowToDoc(r));
|
|
603
597
|
}
|
|
604
598
|
async delete(args) {
|
|
@@ -676,6 +670,134 @@ var AutoIndexer = class {
|
|
|
676
670
|
}
|
|
677
671
|
};
|
|
678
672
|
|
|
673
|
+
// src/driver/better-sqlite3.ts
|
|
674
|
+
var BetterSqlite3Driver = class {
|
|
675
|
+
name = "better-sqlite3";
|
|
676
|
+
raw;
|
|
677
|
+
verbose;
|
|
678
|
+
constructor(BetterSqlite3, filename, options) {
|
|
679
|
+
this.verbose = options.verbose;
|
|
680
|
+
this.raw = new BetterSqlite3(filename, {
|
|
681
|
+
readonly: options.readonly ?? false
|
|
682
|
+
});
|
|
683
|
+
if (!options.readonly && (options.wal ?? true)) {
|
|
684
|
+
this.raw.pragma("journal_mode = WAL");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
exec(sql) {
|
|
688
|
+
this.verbose?.(sql);
|
|
689
|
+
this.raw.exec(sql);
|
|
690
|
+
}
|
|
691
|
+
prepare(sql) {
|
|
692
|
+
this.verbose?.(sql);
|
|
693
|
+
return this.raw.prepare(sql);
|
|
694
|
+
}
|
|
695
|
+
transaction(fn) {
|
|
696
|
+
return this.raw.transaction(fn)();
|
|
697
|
+
}
|
|
698
|
+
close() {
|
|
699
|
+
this.raw.close();
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/driver/node-sqlite.ts
|
|
704
|
+
var NodeSqliteDriver = class {
|
|
705
|
+
name = "node:sqlite";
|
|
706
|
+
raw;
|
|
707
|
+
verbose;
|
|
708
|
+
depth = 0;
|
|
709
|
+
constructor(nodeSqlite, filename, options) {
|
|
710
|
+
this.verbose = options.verbose;
|
|
711
|
+
const { DatabaseSync } = nodeSqlite;
|
|
712
|
+
this.raw = new DatabaseSync(filename, {
|
|
713
|
+
readOnly: options.readonly ?? false
|
|
714
|
+
});
|
|
715
|
+
if (!options.readonly && (options.wal ?? true)) {
|
|
716
|
+
this.raw.exec("PRAGMA journal_mode = WAL");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
exec(sql) {
|
|
720
|
+
this.verbose?.(sql);
|
|
721
|
+
this.raw.exec(sql);
|
|
722
|
+
}
|
|
723
|
+
prepare(sql) {
|
|
724
|
+
this.verbose?.(sql);
|
|
725
|
+
const stmt = this.raw.prepare(sql);
|
|
726
|
+
return {
|
|
727
|
+
run: (...p) => stmt.run(...p),
|
|
728
|
+
get: (...p) => stmt.get(...p),
|
|
729
|
+
all: (...p) => stmt.all(...p)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
transaction(fn) {
|
|
733
|
+
const savepoint = `monlite_sp_${this.depth}`;
|
|
734
|
+
if (this.depth === 0) this.raw.exec("BEGIN");
|
|
735
|
+
else this.raw.exec(`SAVEPOINT ${savepoint}`);
|
|
736
|
+
this.depth++;
|
|
737
|
+
try {
|
|
738
|
+
const result = fn();
|
|
739
|
+
this.depth--;
|
|
740
|
+
if (this.depth === 0) this.raw.exec("COMMIT");
|
|
741
|
+
else this.raw.exec(`RELEASE ${savepoint}`);
|
|
742
|
+
return result;
|
|
743
|
+
} catch (err) {
|
|
744
|
+
this.depth--;
|
|
745
|
+
if (this.depth === 0) this.raw.exec("ROLLBACK");
|
|
746
|
+
else this.raw.exec(`ROLLBACK TO ${savepoint}; RELEASE ${savepoint}`);
|
|
747
|
+
throw err;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
close() {
|
|
751
|
+
this.raw.close();
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// src/driver/index.ts
|
|
756
|
+
var req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
757
|
+
function loadBetterSqlite3() {
|
|
758
|
+
try {
|
|
759
|
+
const mod = req("better-sqlite3");
|
|
760
|
+
return mod?.default ?? mod;
|
|
761
|
+
} catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function loadNodeSqlite() {
|
|
766
|
+
try {
|
|
767
|
+
return req("node:sqlite");
|
|
768
|
+
} catch {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function createDriver(filename, options = {}) {
|
|
773
|
+
const choice = options.driver ?? "auto";
|
|
774
|
+
if (choice === "better-sqlite3") {
|
|
775
|
+
const mod = loadBetterSqlite3();
|
|
776
|
+
if (!mod) {
|
|
777
|
+
throw new MonliteError(
|
|
778
|
+
`driver "better-sqlite3" was requested but the package is not installed. Run \`npm install better-sqlite3\`.`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return new BetterSqlite3Driver(mod, filename, options);
|
|
782
|
+
}
|
|
783
|
+
if (choice === "node:sqlite") {
|
|
784
|
+
const mod = loadNodeSqlite();
|
|
785
|
+
if (!mod) {
|
|
786
|
+
throw new MonliteError(
|
|
787
|
+
`driver "node:sqlite" is unavailable. It requires Node >= 22.5.`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
return new NodeSqliteDriver(mod, filename, options);
|
|
791
|
+
}
|
|
792
|
+
const better = loadBetterSqlite3();
|
|
793
|
+
if (better) return new BetterSqlite3Driver(better, filename, options);
|
|
794
|
+
const node = loadNodeSqlite();
|
|
795
|
+
if (node) return new NodeSqliteDriver(node, filename, options);
|
|
796
|
+
throw new MonliteError(
|
|
797
|
+
`No SQLite driver available. Either install better-sqlite3 (\`npm install better-sqlite3\`) or run on Node >= 22.5 for the built-in node:sqlite backend.`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
679
801
|
// src/db.ts
|
|
680
802
|
function validateName(name) {
|
|
681
803
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
@@ -697,27 +819,33 @@ function buildTagged(strings, values) {
|
|
|
697
819
|
return { sql, params };
|
|
698
820
|
}
|
|
699
821
|
var Monlite = class {
|
|
700
|
-
/** The
|
|
701
|
-
|
|
822
|
+
/** @internal The active SQLite driver. */
|
|
823
|
+
driver;
|
|
702
824
|
/** @internal */
|
|
703
825
|
autoIndexer;
|
|
704
826
|
collections = /* @__PURE__ */ new Map();
|
|
705
827
|
closed = false;
|
|
706
828
|
constructor(filename, options = {}) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
readonly: options.readonly
|
|
710
|
-
|
|
829
|
+
this.driver = createDriver(filename, {
|
|
830
|
+
driver: options.driver,
|
|
831
|
+
readonly: options.readonly,
|
|
832
|
+
wal: options.wal,
|
|
833
|
+
verbose: options.verbose
|
|
711
834
|
});
|
|
712
|
-
if (!options.readonly && (options.wal ?? true)) {
|
|
713
|
-
this.sqlite.pragma("journal_mode = WAL");
|
|
714
|
-
}
|
|
715
835
|
this.autoIndexer = new AutoIndexer(
|
|
716
|
-
this.
|
|
836
|
+
this.driver,
|
|
717
837
|
options.autoIndex ?? true,
|
|
718
838
|
options.autoIndexAfter ?? 10
|
|
719
839
|
);
|
|
720
840
|
}
|
|
841
|
+
/** The underlying native database handle (escape hatch). */
|
|
842
|
+
get sqlite() {
|
|
843
|
+
return this.driver.raw;
|
|
844
|
+
}
|
|
845
|
+
/** Name of the active backend: `"better-sqlite3"` or `"node:sqlite"`. */
|
|
846
|
+
get driverName() {
|
|
847
|
+
return this.driver.name;
|
|
848
|
+
}
|
|
721
849
|
/** Get (or lazily create) a typed collection handle. */
|
|
722
850
|
collection(name) {
|
|
723
851
|
this.assertOpen();
|
|
@@ -733,26 +861,26 @@ var Monlite = class {
|
|
|
733
861
|
$queryRaw(strings, ...values) {
|
|
734
862
|
this.assertOpen();
|
|
735
863
|
const { sql, params } = buildTagged(strings, values);
|
|
736
|
-
return Promise.resolve(this.
|
|
864
|
+
return Promise.resolve(this.driver.prepare(sql).all(...params));
|
|
737
865
|
}
|
|
738
866
|
/** Like {@link $queryRaw} but takes a raw SQL string and positional params. */
|
|
739
867
|
$queryRawUnsafe(sql, ...params) {
|
|
740
868
|
this.assertOpen();
|
|
741
869
|
return Promise.resolve(
|
|
742
|
-
this.
|
|
870
|
+
this.driver.prepare(sql).all(...params.map(bindable))
|
|
743
871
|
);
|
|
744
872
|
}
|
|
745
873
|
/** Tagged-template SQL statement returning the number of affected rows. */
|
|
746
874
|
$executeRaw(strings, ...values) {
|
|
747
875
|
this.assertOpen();
|
|
748
876
|
const { sql, params } = buildTagged(strings, values);
|
|
749
|
-
return Promise.resolve(this.
|
|
877
|
+
return Promise.resolve(this.driver.prepare(sql).run(...params).changes);
|
|
750
878
|
}
|
|
751
879
|
/** Like {@link $executeRaw} but takes a raw SQL string and positional params. */
|
|
752
880
|
$executeRawUnsafe(sql, ...params) {
|
|
753
881
|
this.assertOpen();
|
|
754
882
|
return Promise.resolve(
|
|
755
|
-
this.
|
|
883
|
+
this.driver.prepare(sql).run(...params.map(bindable)).changes
|
|
756
884
|
);
|
|
757
885
|
}
|
|
758
886
|
/**
|
|
@@ -761,13 +889,12 @@ var Monlite = class {
|
|
|
761
889
|
*/
|
|
762
890
|
async $transaction(fn) {
|
|
763
891
|
this.assertOpen();
|
|
764
|
-
|
|
765
|
-
return txn();
|
|
892
|
+
return this.driver.transaction(() => fn(this));
|
|
766
893
|
}
|
|
767
894
|
/** List all collection (table) names. */
|
|
768
895
|
$collections() {
|
|
769
896
|
this.assertOpen();
|
|
770
|
-
const rows = this.
|
|
897
|
+
const rows = this.driver.prepare(
|
|
771
898
|
`SELECT name FROM sqlite_master
|
|
772
899
|
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
773
900
|
ORDER BY name`
|
|
@@ -778,7 +905,7 @@ var Monlite = class {
|
|
|
778
905
|
$drop(name) {
|
|
779
906
|
this.assertOpen();
|
|
780
907
|
validateName(name);
|
|
781
|
-
this.
|
|
908
|
+
this.driver.exec(`DROP TABLE IF EXISTS "${name}"`);
|
|
782
909
|
this.collections.delete(name);
|
|
783
910
|
this.autoIndexer.reset(name);
|
|
784
911
|
return Promise.resolve();
|
|
@@ -791,7 +918,7 @@ var Monlite = class {
|
|
|
791
918
|
$disconnect() {
|
|
792
919
|
if (!this.closed) {
|
|
793
920
|
this.closed = true;
|
|
794
|
-
this.
|
|
921
|
+
this.driver.close();
|
|
795
922
|
}
|
|
796
923
|
return Promise.resolve();
|
|
797
924
|
}
|