@monlite/core 0.7.0 → 0.8.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 +38 -0
- package/dist/index.cjs +207 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -1
- package/dist/index.d.ts +99 -1
- package/dist/index.js +207 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -319,6 +319,31 @@ await users.distinct("tags"); // ["a", "b", "c"]
|
|
|
319
319
|
|
|
320
320
|
---
|
|
321
321
|
|
|
322
|
+
## Live queries (reactivity)
|
|
323
|
+
|
|
324
|
+
`collection.watch()` keeps a query result live. The callback fires once
|
|
325
|
+
immediately (`type: "init"`) and again whenever a change **affects this query** —
|
|
326
|
+
matching is **row-level**, so unrelated writes don't trigger a recompute. It also
|
|
327
|
+
fires for changes applied by `@monlite/sync`, so the UI updates when cloud data
|
|
328
|
+
arrives.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const handle = users.watch({ where: { role: "admin" } }, (event) => {
|
|
332
|
+
event.results; // full current result set
|
|
333
|
+
event.added; // docs that just entered the set
|
|
334
|
+
event.removed; // docs that just left
|
|
335
|
+
event.changed; // docs still in the set whose contents changed
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
handle.results; // current results, kept up to date
|
|
339
|
+
handle.stop(); // unsubscribe
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Perfect for Electron/Tauri UIs: bind `handle.results` to your view and it stays
|
|
343
|
+
in sync with every write (local or synced).
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
322
347
|
## Structured collections (native SQL columns)
|
|
323
348
|
|
|
324
349
|
By default a collection is **document mode** — schema-free, every field stored
|
|
@@ -362,6 +387,11 @@ await db.$queryRaw`
|
|
|
362
387
|
Column types: `"TEXT" | "INTEGER" | "REAL" | "BLOB" | "JSON"`. A full column
|
|
363
388
|
definition supports `index`, `unique`, `notNull`, `default`, and `references`.
|
|
364
389
|
|
|
390
|
+
**Migrations are automatic for additive changes.** Re-opening a collection with a
|
|
391
|
+
new declared column adds it (`ALTER TABLE ADD COLUMN`) on declaration — give
|
|
392
|
+
`NOT NULL` columns a `default` so existing rows can be backfilled. Destructive
|
|
393
|
+
changes (rename/drop/type-change) still need a manual migration.
|
|
394
|
+
|
|
365
395
|
### Do I have to care: JSON vs native columns?
|
|
366
396
|
|
|
367
397
|
- **For correctness — no.** Both modes return identical results through the same API.
|
|
@@ -472,6 +502,13 @@ ON users(json_extract(data, '$.address.city'));
|
|
|
472
502
|
|
|
473
503
|
You never think about indexes. Disable with `createDb("./app.db", { autoIndex: false })`.
|
|
474
504
|
|
|
505
|
+
Want to see whether a query uses an index? Ask:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
await users.explain({ where: { "address.city": "Riyadh" } });
|
|
509
|
+
// { sql, usesIndex: true, plan: [{ id, parent, detail }, …] }
|
|
510
|
+
```
|
|
511
|
+
|
|
475
512
|
---
|
|
476
513
|
|
|
477
514
|
## Database management
|
|
@@ -480,6 +517,7 @@ You never think about indexes. Disable with `createDb("./app.db", { autoIndex: f
|
|
|
480
517
|
await db.$collections(); // string[] of collection names
|
|
481
518
|
await db.$drop("users"); // drop a collection and its data
|
|
482
519
|
await db.$dropAll(); // drop everything
|
|
520
|
+
await db.backup("./snapshot.db"); // consistent on-disk snapshot
|
|
483
521
|
await db.$disconnect(); // close the connection
|
|
484
522
|
db.sqlite; // the underlying native driver handle
|
|
485
523
|
db.driverName; // "better-sqlite3" | "node:sqlite"
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,91 @@ function isObjectId(value) {
|
|
|
20
20
|
return typeof value === "string" && /^[0-9a-f]{24}$/i.test(value);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// src/reactive.ts
|
|
24
|
+
var RELEVANCE_PROBE_LIMIT = 500;
|
|
25
|
+
var LiveQuery = class {
|
|
26
|
+
constructor(source, args, cb) {
|
|
27
|
+
this.source = source;
|
|
28
|
+
this.args = args;
|
|
29
|
+
this.cb = cb;
|
|
30
|
+
this.recompute("init");
|
|
31
|
+
}
|
|
32
|
+
source;
|
|
33
|
+
args;
|
|
34
|
+
cb;
|
|
35
|
+
results = [];
|
|
36
|
+
stopped = false;
|
|
37
|
+
ids = /* @__PURE__ */ new Set();
|
|
38
|
+
/** Called by the Reactor with the ids that changed this tick. */
|
|
39
|
+
notify(changedIds) {
|
|
40
|
+
if (this.stopped) return;
|
|
41
|
+
if (!this.isRelevant(changedIds)) return;
|
|
42
|
+
this.recompute("change", changedIds);
|
|
43
|
+
}
|
|
44
|
+
isRelevant(changedIds) {
|
|
45
|
+
for (const id of changedIds) {
|
|
46
|
+
if (this.ids.has(id)) return true;
|
|
47
|
+
}
|
|
48
|
+
if (changedIds.size > RELEVANCE_PROBE_LIMIT) return true;
|
|
49
|
+
const idIn = {
|
|
50
|
+
_id: { in: [...changedIds] }
|
|
51
|
+
};
|
|
52
|
+
const where = this.args.where ? { AND: [this.args.where, idIn] } : idIn;
|
|
53
|
+
return this.source.existsCore(where);
|
|
54
|
+
}
|
|
55
|
+
recompute(type, changedIds) {
|
|
56
|
+
const next = this.source.findManyCore(this.args);
|
|
57
|
+
const nextById = new Map(next.map((d) => [d._id, d]));
|
|
58
|
+
const added = next.filter((d) => !this.ids.has(d._id));
|
|
59
|
+
const removed = this.results.filter((d) => !nextById.has(d._id));
|
|
60
|
+
const changed = changedIds ? next.filter((d) => this.ids.has(d._id) && changedIds.has(d._id)) : [];
|
|
61
|
+
this.results = next;
|
|
62
|
+
this.ids = new Set(nextById.keys());
|
|
63
|
+
this.cb({ type, results: next, added, removed, changed });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var Reactor = class {
|
|
67
|
+
byCollection = /* @__PURE__ */ new Map();
|
|
68
|
+
pending = /* @__PURE__ */ new Map();
|
|
69
|
+
flushScheduled = false;
|
|
70
|
+
hasWatchers(collection) {
|
|
71
|
+
return this.byCollection.has(collection);
|
|
72
|
+
}
|
|
73
|
+
register(collection, lq) {
|
|
74
|
+
let set = this.byCollection.get(collection);
|
|
75
|
+
if (!set) this.byCollection.set(collection, set = /* @__PURE__ */ new Set());
|
|
76
|
+
set.add(lq);
|
|
77
|
+
}
|
|
78
|
+
unregister(collection, lq) {
|
|
79
|
+
const set = this.byCollection.get(collection);
|
|
80
|
+
if (!set) return;
|
|
81
|
+
set.delete(lq);
|
|
82
|
+
if (set.size === 0) this.byCollection.delete(collection);
|
|
83
|
+
}
|
|
84
|
+
/** Record that documents changed; schedule a notification flush. */
|
|
85
|
+
emit(collection, ids) {
|
|
86
|
+
const set = this.byCollection.get(collection);
|
|
87
|
+
if (!set || set.size === 0 || ids.length === 0) return;
|
|
88
|
+
let p = this.pending.get(collection);
|
|
89
|
+
if (!p) this.pending.set(collection, p = /* @__PURE__ */ new Set());
|
|
90
|
+
for (const id of ids) p.add(id);
|
|
91
|
+
if (!this.flushScheduled) {
|
|
92
|
+
this.flushScheduled = true;
|
|
93
|
+
queueMicrotask(() => this.flush());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
flush() {
|
|
97
|
+
this.flushScheduled = false;
|
|
98
|
+
const work = [...this.pending];
|
|
99
|
+
this.pending.clear();
|
|
100
|
+
for (const [collection, ids] of work) {
|
|
101
|
+
const set = this.byCollection.get(collection);
|
|
102
|
+
if (!set) continue;
|
|
103
|
+
for (const lq of [...set]) lq.notify(ids);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
23
108
|
// src/errors.ts
|
|
24
109
|
var MonliteError = class extends Error {
|
|
25
110
|
constructor(message, options) {
|
|
@@ -605,6 +690,7 @@ var Collection = class {
|
|
|
605
690
|
this.columns.add(field);
|
|
606
691
|
if (normalized.type === "JSON") this.jsonColumns.add(field);
|
|
607
692
|
}
|
|
693
|
+
this.ensureTable();
|
|
608
694
|
}
|
|
609
695
|
}
|
|
610
696
|
mon;
|
|
@@ -649,23 +735,15 @@ var Collection = class {
|
|
|
649
735
|
`_id TEXT PRIMARY KEY`,
|
|
650
736
|
`created_at INTEGER NOT NULL`,
|
|
651
737
|
`updated_at INTEGER NOT NULL`,
|
|
652
|
-
`data TEXT NOT NULL DEFAULT '{}'
|
|
738
|
+
`data TEXT NOT NULL DEFAULT '{}'`,
|
|
739
|
+
...this.columnOrder.map((f) => this.columnDdl(f, false))
|
|
653
740
|
];
|
|
654
|
-
for (const field of this.columnOrder) {
|
|
655
|
-
const def = this.columnDefs[field];
|
|
656
|
-
let line = `"${field}" ${sqliteType(def.type)}`;
|
|
657
|
-
if (def.notNull) line += " NOT NULL";
|
|
658
|
-
if (def.unique) line += " UNIQUE";
|
|
659
|
-
if (def.default !== void 0)
|
|
660
|
-
line += ` DEFAULT ${formatDefault(def.default)}`;
|
|
661
|
-
if (def.references) line += ` REFERENCES ${def.references}`;
|
|
662
|
-
lines.push(line);
|
|
663
|
-
}
|
|
664
741
|
this.db.exec(
|
|
665
742
|
`CREATE TABLE IF NOT EXISTS "${this.name}" (
|
|
666
743
|
${lines.join(",\n ")}
|
|
667
744
|
)`
|
|
668
745
|
);
|
|
746
|
+
this.migrateColumns();
|
|
669
747
|
for (const field of this.columnOrder) {
|
|
670
748
|
if (this.columnDefs[field].index) {
|
|
671
749
|
this.db.exec(
|
|
@@ -676,6 +754,39 @@ var Collection = class {
|
|
|
676
754
|
}
|
|
677
755
|
this.initialized = true;
|
|
678
756
|
}
|
|
757
|
+
columnDdl(field, forAlter) {
|
|
758
|
+
const def = this.columnDefs[field];
|
|
759
|
+
let line = `"${field}" ${sqliteType(def.type)}`;
|
|
760
|
+
if (def.notNull) line += " NOT NULL";
|
|
761
|
+
if (def.unique && !forAlter) line += " UNIQUE";
|
|
762
|
+
if (def.default !== void 0)
|
|
763
|
+
line += ` DEFAULT ${formatDefault(def.default)}`;
|
|
764
|
+
if (def.references) line += ` REFERENCES ${def.references}`;
|
|
765
|
+
return line;
|
|
766
|
+
}
|
|
767
|
+
/** Auto-additive migration: add declared columns missing from an existing table. */
|
|
768
|
+
migrateColumns() {
|
|
769
|
+
const existing = new Set(
|
|
770
|
+
this.db.prepare(`PRAGMA table_info("${this.name}")`).all().map((r) => r.name)
|
|
771
|
+
);
|
|
772
|
+
for (const field of this.columnOrder) {
|
|
773
|
+
if (existing.has(field)) continue;
|
|
774
|
+
try {
|
|
775
|
+
this.db.exec(
|
|
776
|
+
`ALTER TABLE "${this.name}" ADD COLUMN ${this.columnDdl(field, true)}`
|
|
777
|
+
);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
throw new MonliteError(
|
|
780
|
+
`Failed to add column "${field}" to "${this.name}": ${err.message}. NOT NULL columns need a default when added to an existing table.`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
if (this.columnDefs[field].unique) {
|
|
784
|
+
this.db.exec(
|
|
785
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS "uq_${this.name}_${field}" ON "${this.name}"("${field}")`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
679
790
|
/* --------------------------- row <-> doc -------------------------- */
|
|
680
791
|
rowToDoc(row) {
|
|
681
792
|
const doc = this.mode === "document" ? JSON.parse(row.data) : JSON.parse(row.data ?? "{}");
|
|
@@ -798,6 +909,7 @@ var Collection = class {
|
|
|
798
909
|
this.ensureTable();
|
|
799
910
|
if (op === "delete") {
|
|
800
911
|
this.db.prepare(`DELETE FROM "${this.name}" WHERE _id = ?`).run(id);
|
|
912
|
+
this.afterWrite([id]);
|
|
801
913
|
return;
|
|
802
914
|
}
|
|
803
915
|
const clean = stripSystem(doc ?? {});
|
|
@@ -807,6 +919,7 @@ var Collection = class {
|
|
|
807
919
|
`INSERT INTO "${this.name}" (_id, data, created_at, updated_at) VALUES (?, ?, ?, ?)
|
|
808
920
|
ON CONFLICT(_id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`
|
|
809
921
|
).run(id, JSON.stringify(clean), createdAt, ts);
|
|
922
|
+
this.afterWrite([id]);
|
|
810
923
|
return;
|
|
811
924
|
}
|
|
812
925
|
const overflow = {};
|
|
@@ -837,6 +950,11 @@ var Collection = class {
|
|
|
837
950
|
this.db.prepare(
|
|
838
951
|
`INSERT INTO "${this.name}" (${colList}) VALUES (${placeholders}) ON CONFLICT(_id) DO UPDATE SET ${updateSet}`
|
|
839
952
|
).run(...values);
|
|
953
|
+
this.afterWrite([id]);
|
|
954
|
+
}
|
|
955
|
+
/** @internal Notify reactivity watchers that documents changed. */
|
|
956
|
+
afterWrite(ids) {
|
|
957
|
+
this.mon.reactor.emit(this.name, ids);
|
|
840
958
|
}
|
|
841
959
|
/* ----------------------------- create ----------------------------- */
|
|
842
960
|
async create(args) {
|
|
@@ -848,26 +966,29 @@ var Collection = class {
|
|
|
848
966
|
recorder?.recordLocal(this.name, row._id, "upsert", row.created_at);
|
|
849
967
|
};
|
|
850
968
|
this.guard(() => recorder ? this.db.transaction(write) : write());
|
|
969
|
+
this.afterWrite([row._id]);
|
|
851
970
|
return row.returned;
|
|
852
971
|
}
|
|
853
972
|
async createMany(args) {
|
|
854
973
|
this.ensureTable();
|
|
855
974
|
const stmt = this.db.prepare(this.insertSql());
|
|
856
975
|
const recorder = this.recorder;
|
|
976
|
+
const ids = [];
|
|
857
977
|
this.guard(
|
|
858
978
|
() => this.db.transaction(() => {
|
|
859
979
|
for (const item of args.data) {
|
|
860
980
|
const row = this.buildInsert(item);
|
|
861
981
|
stmt.run(...row.values);
|
|
862
982
|
recorder?.recordLocal(this.name, row._id, "upsert", row.created_at);
|
|
983
|
+
ids.push(row._id);
|
|
863
984
|
}
|
|
864
985
|
})
|
|
865
986
|
);
|
|
987
|
+
this.afterWrite(ids);
|
|
866
988
|
return { count: args.data.length };
|
|
867
989
|
}
|
|
868
990
|
/* ------------------------------ read ------------------------------ */
|
|
869
|
-
|
|
870
|
-
this.ensureTable();
|
|
991
|
+
buildFindSql(args) {
|
|
871
992
|
const params = [];
|
|
872
993
|
const where = buildWhere(args.where, {
|
|
873
994
|
params,
|
|
@@ -885,9 +1006,29 @@ var Collection = class {
|
|
|
885
1006
|
sql += (args.take != null ? "" : " LIMIT -1") + " OFFSET ?";
|
|
886
1007
|
params.push(args.skip);
|
|
887
1008
|
}
|
|
1009
|
+
return { sql, params };
|
|
1010
|
+
}
|
|
1011
|
+
/** @internal Synchronous core of findMany (used by reactivity). */
|
|
1012
|
+
findManyCore(args = {}) {
|
|
1013
|
+
this.ensureTable();
|
|
1014
|
+
const { sql, params } = this.buildFindSql(args);
|
|
888
1015
|
const rows = this.db.prepare(sql).all(...params);
|
|
889
1016
|
return rows.map((r) => project(this.rowToDoc(r), args.select));
|
|
890
1017
|
}
|
|
1018
|
+
/** @internal Synchronous core of exists (used by reactivity). */
|
|
1019
|
+
existsCore(where) {
|
|
1020
|
+
this.ensureTable();
|
|
1021
|
+
const params = [];
|
|
1022
|
+
const clause = buildWhere(where, {
|
|
1023
|
+
params,
|
|
1024
|
+
onPath: this.trackPath,
|
|
1025
|
+
columns: this.columns
|
|
1026
|
+
});
|
|
1027
|
+
return this.db.prepare(`SELECT 1 FROM "${this.name}" WHERE ${clause} LIMIT 1`).get(...params) != null;
|
|
1028
|
+
}
|
|
1029
|
+
async findMany(args = {}) {
|
|
1030
|
+
return this.findManyCore(args);
|
|
1031
|
+
}
|
|
891
1032
|
async findFirst(args = {}) {
|
|
892
1033
|
const rows = await this.findMany({ ...args, take: 1 });
|
|
893
1034
|
return rows[0] ?? null;
|
|
@@ -904,15 +1045,39 @@ var Collection = class {
|
|
|
904
1045
|
}
|
|
905
1046
|
/** True if at least one document matches. */
|
|
906
1047
|
async exists(where) {
|
|
1048
|
+
return this.existsCore(where);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Subscribe to a live query. The callback fires immediately with the current
|
|
1052
|
+
* results (`type: "init"`) and again whenever a change affects the result set
|
|
1053
|
+
* (row-level: only relevant changes trigger a recompute). Includes changes
|
|
1054
|
+
* applied by `@monlite/sync`.
|
|
1055
|
+
*/
|
|
1056
|
+
watch(args = {}, cb) {
|
|
907
1057
|
this.ensureTable();
|
|
908
|
-
const
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1058
|
+
const lq = new LiveQuery(this, args, cb);
|
|
1059
|
+
const reactor = this.mon.reactor;
|
|
1060
|
+
const name = this.name;
|
|
1061
|
+
reactor.register(name, lq);
|
|
1062
|
+
return {
|
|
1063
|
+
get results() {
|
|
1064
|
+
return lq.results;
|
|
1065
|
+
},
|
|
1066
|
+
stop() {
|
|
1067
|
+
lq.stopped = true;
|
|
1068
|
+
reactor.unregister(name, lq);
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
/** Show SQLite's query plan for a `findMany`, and whether it uses an index. */
|
|
1073
|
+
async explain(args = {}) {
|
|
1074
|
+
this.ensureTable();
|
|
1075
|
+
const { sql, params } = this.buildFindSql(args);
|
|
1076
|
+
const plan = this.db.prepare(`EXPLAIN QUERY PLAN ${sql}`).all(...params);
|
|
1077
|
+
const usesIndex = plan.some(
|
|
1078
|
+
(r) => /USING (COVERING )?INDEX/i.test(r.detail)
|
|
1079
|
+
);
|
|
1080
|
+
return { sql, usesIndex, plan };
|
|
916
1081
|
}
|
|
917
1082
|
async findById(id) {
|
|
918
1083
|
this.ensureTable();
|
|
@@ -969,25 +1134,27 @@ var Collection = class {
|
|
|
969
1134
|
if (!rows.length) return [];
|
|
970
1135
|
const now = Date.now();
|
|
971
1136
|
const recorder = this.recorder;
|
|
972
|
-
|
|
1137
|
+
const out = this.guard(
|
|
973
1138
|
() => this.db.transaction(() => {
|
|
974
|
-
const
|
|
1139
|
+
const result = [];
|
|
975
1140
|
for (const row of rows) {
|
|
976
1141
|
const current = stripSystem(this.rowToDoc(row));
|
|
977
1142
|
const updated = stripSystem(applyUpdate(current, data));
|
|
978
1143
|
const { setSql, values } = this.buildUpdateSet(updated, now);
|
|
979
1144
|
this.db.prepare(`UPDATE "${this.name}" SET ${setSql} WHERE _id = ?`).run(...values, row._id);
|
|
980
1145
|
recorder?.recordLocal(this.name, row._id, "upsert", now);
|
|
981
|
-
|
|
1146
|
+
result.push({
|
|
982
1147
|
...updated,
|
|
983
1148
|
_id: row._id,
|
|
984
1149
|
created_at: row.created_at,
|
|
985
1150
|
updated_at: now
|
|
986
1151
|
});
|
|
987
1152
|
}
|
|
988
|
-
return
|
|
1153
|
+
return result;
|
|
989
1154
|
})
|
|
990
1155
|
);
|
|
1156
|
+
this.afterWrite(out.map((d) => d._id));
|
|
1157
|
+
return out;
|
|
991
1158
|
}
|
|
992
1159
|
async update(args) {
|
|
993
1160
|
return this.runUpdate(args.where, args.data, true)[0] ?? null;
|
|
@@ -997,7 +1164,7 @@ var Collection = class {
|
|
|
997
1164
|
}
|
|
998
1165
|
async upsert(args) {
|
|
999
1166
|
this.ensureTable();
|
|
1000
|
-
|
|
1167
|
+
const result = this.guard(
|
|
1001
1168
|
() => this.db.transaction(() => {
|
|
1002
1169
|
const params = [];
|
|
1003
1170
|
const clause = buildWhere(args.where, {
|
|
@@ -1027,6 +1194,8 @@ var Collection = class {
|
|
|
1027
1194
|
return ins.returned;
|
|
1028
1195
|
})
|
|
1029
1196
|
);
|
|
1197
|
+
this.afterWrite([result._id]);
|
|
1198
|
+
return result;
|
|
1030
1199
|
}
|
|
1031
1200
|
/* ----------------------------- delete ----------------------------- */
|
|
1032
1201
|
runDelete(where, single) {
|
|
@@ -1052,6 +1221,7 @@ var Collection = class {
|
|
|
1052
1221
|
}
|
|
1053
1222
|
})
|
|
1054
1223
|
);
|
|
1224
|
+
this.afterWrite(rows.map((r) => r._id));
|
|
1055
1225
|
return rows.map((r) => this.rowToDoc(r));
|
|
1056
1226
|
}
|
|
1057
1227
|
async delete(args) {
|
|
@@ -1713,6 +1883,8 @@ var Monlite = class {
|
|
|
1713
1883
|
driver;
|
|
1714
1884
|
/** @internal */
|
|
1715
1885
|
autoIndexer;
|
|
1886
|
+
/** @internal Reactivity hub for `collection.watch()`. */
|
|
1887
|
+
reactor = new Reactor();
|
|
1716
1888
|
/** @internal Sync metadata store; present only when `{ sync: true }`. */
|
|
1717
1889
|
$sync;
|
|
1718
1890
|
collections = /* @__PURE__ */ new Map();
|
|
@@ -1847,6 +2019,15 @@ var Monlite = class {
|
|
|
1847
2019
|
async $dropAll() {
|
|
1848
2020
|
for (const name of await this.$collections()) await this.$drop(name);
|
|
1849
2021
|
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Write a consistent on-disk snapshot of the database to `path` (via
|
|
2024
|
+
* `VACUUM INTO`). The destination file must not already exist.
|
|
2025
|
+
*/
|
|
2026
|
+
backup(path) {
|
|
2027
|
+
this.assertOpen();
|
|
2028
|
+
this.driver.exec(`VACUUM INTO '${path.replace(/'/g, "''")}'`);
|
|
2029
|
+
return Promise.resolve();
|
|
2030
|
+
}
|
|
1850
2031
|
/** Close the underlying SQLite connection. */
|
|
1851
2032
|
$disconnect() {
|
|
1852
2033
|
if (!this.closed) {
|