@monlite/core 0.6.0 → 0.7.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 +4 -2
- package/dist/index.cjs +129 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +129 -23
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.d.cts
CHANGED
|
@@ -265,8 +265,17 @@ declare class Collection<T = Doc> {
|
|
|
265
265
|
private buildInsert;
|
|
266
266
|
/** Build the `SET` clause + values to persist an updated document. */
|
|
267
267
|
private buildUpdateSet;
|
|
268
|
-
/** Sync store
|
|
268
|
+
/** Sync store for recording local changes (both document and structured). */
|
|
269
269
|
private get recorder();
|
|
270
|
+
/** @internal Read a full document by id (mode-aware), synchronously. */
|
|
271
|
+
getRaw(id: string): WithId<T> | null;
|
|
272
|
+
/**
|
|
273
|
+
* @internal Apply a remote change to storage WITHOUT recording it to the
|
|
274
|
+
* change feed (the sync store records the `remote` feed row itself). Used by
|
|
275
|
+
* `@monlite/sync` so structured collections sync correctly through the same
|
|
276
|
+
* column/overflow split as local writes.
|
|
277
|
+
*/
|
|
278
|
+
applyRemoteWrite(op: "upsert" | "delete", id: string, doc: Record<string, any> | undefined, ts: number): void;
|
|
270
279
|
create(args: CreateArgs<T>): Promise<WithId<T>>;
|
|
271
280
|
createMany(args: CreateManyArgs<T>): Promise<{
|
|
272
281
|
count: number;
|
|
@@ -421,9 +430,10 @@ interface ConflictRow {
|
|
|
421
430
|
*/
|
|
422
431
|
declare class SyncStore {
|
|
423
432
|
private readonly db;
|
|
433
|
+
private readonly mon?;
|
|
424
434
|
readonly nodeId: string;
|
|
425
435
|
private versionSeq;
|
|
426
|
-
constructor(db: Driver, nodeId?: string);
|
|
436
|
+
constructor(db: Driver, nodeId?: string, mon?: Monlite | undefined);
|
|
427
437
|
private init;
|
|
428
438
|
private resolveNodeId;
|
|
429
439
|
/** True if this database tracks sync metadata (always, once constructed). */
|
|
@@ -462,6 +472,7 @@ declare class SyncStore {
|
|
|
462
472
|
private recordConflict;
|
|
463
473
|
conflicts(): ConflictRow[];
|
|
464
474
|
private readDoc;
|
|
475
|
+
private tableExists;
|
|
465
476
|
private ensureCollTable;
|
|
466
477
|
}
|
|
467
478
|
|
package/dist/index.d.ts
CHANGED
|
@@ -265,8 +265,17 @@ declare class Collection<T = Doc> {
|
|
|
265
265
|
private buildInsert;
|
|
266
266
|
/** Build the `SET` clause + values to persist an updated document. */
|
|
267
267
|
private buildUpdateSet;
|
|
268
|
-
/** Sync store
|
|
268
|
+
/** Sync store for recording local changes (both document and structured). */
|
|
269
269
|
private get recorder();
|
|
270
|
+
/** @internal Read a full document by id (mode-aware), synchronously. */
|
|
271
|
+
getRaw(id: string): WithId<T> | null;
|
|
272
|
+
/**
|
|
273
|
+
* @internal Apply a remote change to storage WITHOUT recording it to the
|
|
274
|
+
* change feed (the sync store records the `remote` feed row itself). Used by
|
|
275
|
+
* `@monlite/sync` so structured collections sync correctly through the same
|
|
276
|
+
* column/overflow split as local writes.
|
|
277
|
+
*/
|
|
278
|
+
applyRemoteWrite(op: "upsert" | "delete", id: string, doc: Record<string, any> | undefined, ts: number): void;
|
|
270
279
|
create(args: CreateArgs<T>): Promise<WithId<T>>;
|
|
271
280
|
createMany(args: CreateManyArgs<T>): Promise<{
|
|
272
281
|
count: number;
|
|
@@ -421,9 +430,10 @@ interface ConflictRow {
|
|
|
421
430
|
*/
|
|
422
431
|
declare class SyncStore {
|
|
423
432
|
private readonly db;
|
|
433
|
+
private readonly mon?;
|
|
424
434
|
readonly nodeId: string;
|
|
425
435
|
private versionSeq;
|
|
426
|
-
constructor(db: Driver, nodeId?: string);
|
|
436
|
+
constructor(db: Driver, nodeId?: string, mon?: Monlite | undefined);
|
|
427
437
|
private init;
|
|
428
438
|
private resolveNodeId;
|
|
429
439
|
/** True if this database tracks sync metadata (always, once constructed). */
|
|
@@ -462,6 +472,7 @@ declare class SyncStore {
|
|
|
462
472
|
private recordConflict;
|
|
463
473
|
conflicts(): ConflictRow[];
|
|
464
474
|
private readDoc;
|
|
475
|
+
private tableExists;
|
|
465
476
|
private ensureCollTable;
|
|
466
477
|
}
|
|
467
478
|
|
package/dist/index.js
CHANGED
|
@@ -236,9 +236,7 @@ function cmp(expr, op, v, ctx) {
|
|
|
236
236
|
}
|
|
237
237
|
function inExpr(expr, arr, ctx, negate) {
|
|
238
238
|
if (!Array.isArray(arr)) {
|
|
239
|
-
throw new MonliteQueryError(
|
|
240
|
-
`${negate ? "notIn" : "in"} expects an array`
|
|
241
|
-
);
|
|
239
|
+
throw new MonliteQueryError(`${negate ? "notIn" : "in"} expects an array`);
|
|
242
240
|
}
|
|
243
241
|
if (arr.length === 0) return negate ? "1" : "0";
|
|
244
242
|
const placeholders = arr.map((v) => {
|
|
@@ -368,7 +366,8 @@ function applyUpdate(doc, data) {
|
|
|
368
366
|
}
|
|
369
367
|
const ops = data;
|
|
370
368
|
if (ops.$set) {
|
|
371
|
-
for (const [path, value] of Object.entries(ops.$set))
|
|
369
|
+
for (const [path, value] of Object.entries(ops.$set))
|
|
370
|
+
setPath(next, path, value);
|
|
372
371
|
}
|
|
373
372
|
if (ops.$inc) {
|
|
374
373
|
for (const [path, by] of Object.entries(ops.$inc)) {
|
|
@@ -489,7 +488,11 @@ function buildHaving(having, params, columns) {
|
|
|
489
488
|
if (!selection) continue;
|
|
490
489
|
for (const field of Object.keys(selection)) {
|
|
491
490
|
parts.push(
|
|
492
|
-
...comparisonSql(
|
|
491
|
+
...comparisonSql(
|
|
492
|
+
`${fn}(${fieldExpr(field, columns)})`,
|
|
493
|
+
selection[field],
|
|
494
|
+
params
|
|
495
|
+
)
|
|
493
496
|
);
|
|
494
497
|
}
|
|
495
498
|
}
|
|
@@ -517,7 +520,11 @@ function groupBy(ctx, args) {
|
|
|
517
520
|
groupCols.push({ alias, field });
|
|
518
521
|
});
|
|
519
522
|
selects.push(`COUNT(*) AS agg_count`);
|
|
520
|
-
const { selects: accSelects, cols } = buildAccumulators(
|
|
523
|
+
const { selects: accSelects, cols } = buildAccumulators(
|
|
524
|
+
args,
|
|
525
|
+
ctx.onPath,
|
|
526
|
+
ctx.columns
|
|
527
|
+
);
|
|
521
528
|
selects.push(...accSelects);
|
|
522
529
|
let sql = `SELECT ${selects.join(", ")} FROM "${ctx.table}" WHERE ${where} GROUP BY ${groupExprs.join(", ")}`;
|
|
523
530
|
if (args.having) {
|
|
@@ -646,7 +653,8 @@ var Collection = class {
|
|
|
646
653
|
let line = `"${field}" ${sqliteType(def.type)}`;
|
|
647
654
|
if (def.notNull) line += " NOT NULL";
|
|
648
655
|
if (def.unique) line += " UNIQUE";
|
|
649
|
-
if (def.default !== void 0)
|
|
656
|
+
if (def.default !== void 0)
|
|
657
|
+
line += ` DEFAULT ${formatDefault(def.default)}`;
|
|
650
658
|
if (def.references) line += ` REFERENCES ${def.references}`;
|
|
651
659
|
lines.push(line);
|
|
652
660
|
}
|
|
@@ -710,7 +718,12 @@ var Collection = class {
|
|
|
710
718
|
const now = Date.now();
|
|
711
719
|
const id = input._id != null ? String(input._id) : objectId();
|
|
712
720
|
const doc = stripSystem(input);
|
|
713
|
-
const returned = {
|
|
721
|
+
const returned = {
|
|
722
|
+
...doc,
|
|
723
|
+
_id: id,
|
|
724
|
+
created_at: now,
|
|
725
|
+
updated_at: now
|
|
726
|
+
};
|
|
714
727
|
if (this.mode === "document") {
|
|
715
728
|
return {
|
|
716
729
|
_id: id,
|
|
@@ -762,9 +775,65 @@ var Collection = class {
|
|
|
762
775
|
];
|
|
763
776
|
return { setSql: setParts.join(", "), values };
|
|
764
777
|
}
|
|
765
|
-
/** Sync store
|
|
778
|
+
/** Sync store for recording local changes (both document and structured). */
|
|
766
779
|
get recorder() {
|
|
767
|
-
return this.
|
|
780
|
+
return this.mon.$sync;
|
|
781
|
+
}
|
|
782
|
+
/** @internal Read a full document by id (mode-aware), synchronously. */
|
|
783
|
+
getRaw(id) {
|
|
784
|
+
this.ensureTable();
|
|
785
|
+
const row = this.db.prepare(`SELECT * FROM "${this.name}" WHERE _id = ?`).get(id);
|
|
786
|
+
return row ? this.rowToDoc(row) : null;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* @internal Apply a remote change to storage WITHOUT recording it to the
|
|
790
|
+
* change feed (the sync store records the `remote` feed row itself). Used by
|
|
791
|
+
* `@monlite/sync` so structured collections sync correctly through the same
|
|
792
|
+
* column/overflow split as local writes.
|
|
793
|
+
*/
|
|
794
|
+
applyRemoteWrite(op, id, doc, ts) {
|
|
795
|
+
this.ensureTable();
|
|
796
|
+
if (op === "delete") {
|
|
797
|
+
this.db.prepare(`DELETE FROM "${this.name}" WHERE _id = ?`).run(id);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const clean = stripSystem(doc ?? {});
|
|
801
|
+
const createdAt = typeof doc?.created_at === "number" ? doc.created_at : ts;
|
|
802
|
+
if (this.mode === "document") {
|
|
803
|
+
this.db.prepare(
|
|
804
|
+
`INSERT INTO "${this.name}" (_id, data, created_at, updated_at) VALUES (?, ?, ?, ?)
|
|
805
|
+
ON CONFLICT(_id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`
|
|
806
|
+
).run(id, JSON.stringify(clean), createdAt, ts);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const overflow = {};
|
|
810
|
+
const colValues = {};
|
|
811
|
+
for (const [k, v] of Object.entries(clean)) {
|
|
812
|
+
if (this.columns.has(k)) colValues[k] = v;
|
|
813
|
+
else overflow[k] = v;
|
|
814
|
+
}
|
|
815
|
+
const cols = [
|
|
816
|
+
"_id",
|
|
817
|
+
"created_at",
|
|
818
|
+
"updated_at",
|
|
819
|
+
"data",
|
|
820
|
+
...this.columnOrder
|
|
821
|
+
];
|
|
822
|
+
const values = [
|
|
823
|
+
id,
|
|
824
|
+
createdAt,
|
|
825
|
+
ts,
|
|
826
|
+
JSON.stringify(overflow),
|
|
827
|
+
...this.columnOrder.map(
|
|
828
|
+
(c) => c in colValues ? this.encodeColumn(c, colValues[c]) : null
|
|
829
|
+
)
|
|
830
|
+
];
|
|
831
|
+
const colList = cols.map((c) => `"${c}"`).join(", ");
|
|
832
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
833
|
+
const updateSet = cols.filter((c) => c !== "_id" && c !== "created_at").map((c) => `"${c}" = excluded."${c}"`).join(", ");
|
|
834
|
+
this.db.prepare(
|
|
835
|
+
`INSERT INTO "${this.name}" (${colList}) VALUES (${placeholders}) ON CONFLICT(_id) DO UPDATE SET ${updateSet}`
|
|
836
|
+
).run(...values);
|
|
768
837
|
}
|
|
769
838
|
/* ----------------------------- create ----------------------------- */
|
|
770
839
|
async create(args) {
|
|
@@ -993,7 +1062,12 @@ var Collection = class {
|
|
|
993
1062
|
this.ensureTable();
|
|
994
1063
|
return this.guard(
|
|
995
1064
|
() => aggregate(
|
|
996
|
-
{
|
|
1065
|
+
{
|
|
1066
|
+
db: this.db,
|
|
1067
|
+
table: this.name,
|
|
1068
|
+
onPath: this.trackPath,
|
|
1069
|
+
columns: this.columns
|
|
1070
|
+
},
|
|
997
1071
|
args
|
|
998
1072
|
)
|
|
999
1073
|
);
|
|
@@ -1002,7 +1076,12 @@ var Collection = class {
|
|
|
1002
1076
|
this.ensureTable();
|
|
1003
1077
|
return this.guard(
|
|
1004
1078
|
() => groupBy(
|
|
1005
|
-
{
|
|
1079
|
+
{
|
|
1080
|
+
db: this.db,
|
|
1081
|
+
table: this.name,
|
|
1082
|
+
onPath: this.trackPath,
|
|
1083
|
+
columns: this.columns
|
|
1084
|
+
},
|
|
1006
1085
|
args
|
|
1007
1086
|
)
|
|
1008
1087
|
);
|
|
@@ -1250,12 +1329,14 @@ function stripSystem2(obj) {
|
|
|
1250
1329
|
return rest;
|
|
1251
1330
|
}
|
|
1252
1331
|
var SyncStore = class {
|
|
1253
|
-
constructor(db, nodeId) {
|
|
1332
|
+
constructor(db, nodeId, mon) {
|
|
1254
1333
|
this.db = db;
|
|
1334
|
+
this.mon = mon;
|
|
1255
1335
|
this.init();
|
|
1256
1336
|
this.nodeId = this.resolveNodeId(nodeId);
|
|
1257
1337
|
}
|
|
1258
1338
|
db;
|
|
1339
|
+
mon;
|
|
1259
1340
|
nodeId;
|
|
1260
1341
|
versionSeq = 0;
|
|
1261
1342
|
init() {
|
|
@@ -1290,7 +1371,9 @@ var SyncStore = class {
|
|
|
1290
1371
|
}
|
|
1291
1372
|
resolveNodeId(explicit) {
|
|
1292
1373
|
if (explicit) {
|
|
1293
|
-
this.db.prepare(
|
|
1374
|
+
this.db.prepare(
|
|
1375
|
+
`INSERT OR REPLACE INTO _monlite_meta (key, value) VALUES ('nodeId', ?)`
|
|
1376
|
+
).run(explicit);
|
|
1294
1377
|
return explicit;
|
|
1295
1378
|
}
|
|
1296
1379
|
const row = this.db.prepare(`SELECT value FROM _monlite_meta WHERE key = 'nodeId'`).get();
|
|
@@ -1405,6 +1488,9 @@ var SyncStore = class {
|
|
|
1405
1488
|
);
|
|
1406
1489
|
}
|
|
1407
1490
|
if (winner !== "remote") {
|
|
1491
|
+
if (localVersion !== null) {
|
|
1492
|
+
this.recordLocal(change.collection, change._id, "upsert", Date.now());
|
|
1493
|
+
}
|
|
1408
1494
|
return { applied: false, conflict: localVersion !== null, winner };
|
|
1409
1495
|
}
|
|
1410
1496
|
this.applyData(change);
|
|
@@ -1418,24 +1504,31 @@ var SyncStore = class {
|
|
|
1418
1504
|
change.version,
|
|
1419
1505
|
versionTs(change.version)
|
|
1420
1506
|
);
|
|
1421
|
-
return {
|
|
1507
|
+
return {
|
|
1508
|
+
applied: true,
|
|
1509
|
+
conflict: localVersion !== null,
|
|
1510
|
+
winner: "remote"
|
|
1511
|
+
};
|
|
1422
1512
|
});
|
|
1423
1513
|
}
|
|
1424
1514
|
applyData(change) {
|
|
1425
|
-
const
|
|
1515
|
+
const ts = versionTs(change.version);
|
|
1516
|
+
if (this.mon) {
|
|
1517
|
+
this.mon.collection(change.collection).applyRemoteWrite(change.op, change._id, change.doc, ts);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
const coll = change.collection;
|
|
1426
1521
|
this.ensureCollTable(coll);
|
|
1427
|
-
if (op === "delete") {
|
|
1428
|
-
this.db.prepare(`DELETE FROM "${coll}" WHERE _id = ?`).run(_id);
|
|
1522
|
+
if (change.op === "delete") {
|
|
1523
|
+
this.db.prepare(`DELETE FROM "${coll}" WHERE _id = ?`).run(change._id);
|
|
1429
1524
|
return;
|
|
1430
1525
|
}
|
|
1431
1526
|
const doc = change.doc ?? {};
|
|
1432
|
-
const data = JSON.stringify(stripSystem2(doc));
|
|
1433
|
-
const ts = versionTs(change.version);
|
|
1434
1527
|
const createdAt = typeof doc.created_at === "number" ? doc.created_at : ts;
|
|
1435
1528
|
this.db.prepare(
|
|
1436
1529
|
`INSERT INTO "${coll}" (_id, data, created_at, updated_at) VALUES (?, ?, ?, ?)
|
|
1437
1530
|
ON CONFLICT(_id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`
|
|
1438
|
-
).run(_id,
|
|
1531
|
+
).run(change._id, JSON.stringify(stripSystem2(doc)), createdAt, ts);
|
|
1439
1532
|
}
|
|
1440
1533
|
/**
|
|
1441
1534
|
* Latest change per document with `seq` greater than the given watermark,
|
|
@@ -1492,6 +1585,7 @@ var SyncStore = class {
|
|
|
1492
1585
|
this.db.transaction(() => {
|
|
1493
1586
|
for (const coll of collections) {
|
|
1494
1587
|
assertName(coll);
|
|
1588
|
+
if (!this.tableExists(coll)) continue;
|
|
1495
1589
|
const docs = this.db.prepare(`SELECT _id, updated_at FROM "${coll}"`).all();
|
|
1496
1590
|
for (const d of docs) {
|
|
1497
1591
|
if (this.currentVersion(coll, d._id) !== null) continue;
|
|
@@ -1537,7 +1631,14 @@ var SyncStore = class {
|
|
|
1537
1631
|
this.db.prepare(
|
|
1538
1632
|
`INSERT INTO _monlite_conflicts (coll, doc_id, local_version, remote_version, winner, ts)
|
|
1539
1633
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1540
|
-
).run(
|
|
1634
|
+
).run(
|
|
1635
|
+
coll,
|
|
1636
|
+
id,
|
|
1637
|
+
localVersion,
|
|
1638
|
+
remoteVersion,
|
|
1639
|
+
winner,
|
|
1640
|
+
versionTs(remoteVersion)
|
|
1641
|
+
);
|
|
1541
1642
|
}
|
|
1542
1643
|
conflicts() {
|
|
1543
1644
|
const rows = this.db.prepare(
|
|
@@ -1556,6 +1657,8 @@ var SyncStore = class {
|
|
|
1556
1657
|
/* ------------------------------ helpers ------------------------------- */
|
|
1557
1658
|
readDoc(coll, id) {
|
|
1558
1659
|
assertName(coll);
|
|
1660
|
+
if (this.mon) return this.mon.collection(coll).getRaw(id);
|
|
1661
|
+
if (!this.tableExists(coll)) return null;
|
|
1559
1662
|
const row = this.db.prepare(
|
|
1560
1663
|
`SELECT _id, data, created_at, updated_at FROM "${coll}" WHERE _id = ?`
|
|
1561
1664
|
).get(id);
|
|
@@ -1566,6 +1669,9 @@ var SyncStore = class {
|
|
|
1566
1669
|
doc.updated_at = row.updated_at;
|
|
1567
1670
|
return doc;
|
|
1568
1671
|
}
|
|
1672
|
+
tableExists(name) {
|
|
1673
|
+
return this.db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?`).get(name) != null;
|
|
1674
|
+
}
|
|
1569
1675
|
ensureCollTable(coll) {
|
|
1570
1676
|
assertName(coll);
|
|
1571
1677
|
this.db.exec(
|
|
@@ -1622,7 +1728,7 @@ var Monlite = class {
|
|
|
1622
1728
|
options.autoIndexAfter ?? 10
|
|
1623
1729
|
);
|
|
1624
1730
|
if (options.sync) {
|
|
1625
|
-
this.$sync = new SyncStore(this.driver, options.nodeId);
|
|
1731
|
+
this.$sync = new SyncStore(this.driver, options.nodeId, this);
|
|
1626
1732
|
}
|
|
1627
1733
|
}
|
|
1628
1734
|
/** Stable node id for LWW tie-breaking (only when sync is enabled). */
|