@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 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 uses [`better-sqlite3`](https://github.com/WiseLibs/better-sqlite3) under
33
- the hood (its only runtime dependency). Node 18+ is required.
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 `better-sqlite3` instance.
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 better-sqlite3 instance
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 (better-sqlite3 is sync) but are
329
- exposed as `async` (they return Promises) for API consistency and future-proofing.
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
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
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.sqlite;
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
- const insertAll = this.db.transaction((items) => {
495
- for (const item of items) {
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
- const txn = this.db.transaction(() => {
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
- const txn = this.db.transaction(() => {
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 underlying better-sqlite3 connection (escape hatch). */
701
- sqlite;
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
- const verbose = options.verbose;
708
- this.sqlite = new Database__default.default(filename, {
709
- readonly: options.readonly ?? false,
710
- ...verbose ? { verbose: (msg) => verbose(String(msg)) } : {}
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.sqlite,
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.sqlite.prepare(sql).all(...params));
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.sqlite.prepare(sql).all(...params.map(bindable))
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.sqlite.prepare(sql).run(...params).changes);
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.sqlite.prepare(sql).run(...params.map(bindable)).changes
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
- const txn = this.sqlite.transaction(() => fn(this));
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.sqlite.prepare(
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.sqlite.exec(`DROP TABLE IF EXISTS "${name}"`);
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.sqlite.close();
921
+ this.driver.close();
795
922
  }
796
923
  return Promise.resolve();
797
924
  }