@omegup/msync 0.1.29 → 0.1.31
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/index.d.ts +1 -2
- package/index.esm.js +51 -59
- package/index.js +49 -58
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -327,7 +327,6 @@ type View<V extends Model, K extends StrKey<V>> = {
|
|
|
327
327
|
};
|
|
328
328
|
|
|
329
329
|
declare const val: <T extends rawItem>(val: T) => Expr<T, unknown>;
|
|
330
|
-
declare const afterWriteTime: Expr<Timestamp, unknown>;
|
|
331
330
|
declare const current: Expr<Timestamp, unknown>;
|
|
332
331
|
declare const $let: <T, D, C, V extends RORec<string, jsonItem>>(vars: ExprsExact<V, D, C>, inExpr: Expr<T, D, C & V>) => Expr<T, D, C>;
|
|
333
332
|
declare const nil: Expr<null, unknown>;
|
|
@@ -681,5 +680,5 @@ declare const enablePreAndPostImages: <T extends doc>(coll: Collection<T>) => Pr
|
|
|
681
680
|
declare const prepare: (testName?: string) => Promise<MongoClient$1>;
|
|
682
681
|
declare const makeCol: <T extends ID>(docs: readonly OptionalUnlessRequiredId<T>[], database: Db, name?: string) => Promise<Collection<T>>;
|
|
683
682
|
|
|
684
|
-
export { $accumulator, $and, $countDict, $entries, $eq, $exists, $expr, $getField, $group, $groupId, $groupMerge, $group_, $gt, $gtTs, $gte, $gteTs, $ifNull, $in, $insert, $insertPart, $insertX, $keys, $let, $lookup, $lt, $lte, $map, $map0, $map1, $match, $matchDelta, $merge, $merge2, $mergeId, $mergePart, $merge_, $ne, $nin, $nor, $or, $outerLookup, $pushDict, $rand, $reduce, $replaceWith, $set, $simpleInsert, $simpleMerge, $simpleMergePart, $sum, $type, $unwind, $unwindDelta, Expr, Field, Machine, Type, add,
|
|
683
|
+
export { $accumulator, $and, $countDict, $entries, $eq, $exists, $expr, $getField, $group, $groupId, $groupMerge, $group_, $gt, $gtTs, $gte, $gteTs, $ifNull, $in, $insert, $insertPart, $insertX, $keys, $let, $lookup, $lt, $lte, $map, $map0, $map1, $match, $matchDelta, $merge, $merge2, $mergeId, $mergePart, $merge_, $ne, $nin, $nor, $or, $outerLookup, $pushDict, $rand, $reduce, $replaceWith, $set, $simpleInsert, $simpleMerge, $simpleMergePart, $sum, $type, $unwind, $unwindDelta, Expr, Field, Machine, Type, add, and, anyElementTrue, array, ceil, comp, concat, concatArray, createIndex, ctx, current, dateAdd, dateDiff, dateLt, datePart, dayAndMonthPart, divide, enablePreAndPostImages, eq, eqTyped, except, exprMapVal, field, fieldF, fieldM, filter, filterDefined, first, firstSure, floor, from, func, getWhenMatched, getWhenMatchedForMerge, gt, gte, inArray, isArray, ite, last, log, lt, lte, makeCol, map1, mapVal, max, maxDate, mergeExact, mergeExact0, mergeExpr, mergeObjects, minDate, monthPart, multiply, ne, nil, noop, not, notNull, now, or, pair, prepare, rand, range, regex, root, set, setField, single, size, slice, sortArray, staging, startOf, str, sub, subtract, to, toInt, val, weekPart, wrap, year };
|
|
685
684
|
export type { Accumulators, Arr, AsLiteral, Delta, DeltaAccumulator, DeltaAccumulators, ExactKeys, ExprHKT, Exprs, ExprsExact, ExprsExactHKT, ExprsPart, ID, Loose, Merge, MergeArgs, MergeInto, MergeMapOArgs, Model, MongoTypeNames, N, NoRaw, NullToOBJ, O, OPick, OPickD, Patch, RONoRaw, RORec, RawStages, Rec, Replace, SnapshotStreamExecutionResult, StrKey, Strict, TS, WriteonlyCollection, doc, jsonPrim, notArr };
|
package/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UUID, Timestamp, MongoClient } from 'mongodb';
|
|
2
2
|
import crypto$1 from 'crypto';
|
|
3
3
|
import { canonicalize } from 'json-canonicalize';
|
|
4
4
|
import { SynchronousPromise } from 'synchronous-promise';
|
|
@@ -81,9 +81,6 @@ const val = (val) => asExpr({
|
|
|
81
81
|
? { $literal: val }
|
|
82
82
|
: val),
|
|
83
83
|
});
|
|
84
|
-
const afterWriteTime = asExpr({
|
|
85
|
-
raw: () => asExprRaw(new Timestamp(0xffffffffffffffffn)),
|
|
86
|
-
});
|
|
87
84
|
const current = asExpr({
|
|
88
85
|
raw: () => asExprRaw('$$CLUSTER_TIME'),
|
|
89
86
|
});
|
|
@@ -845,7 +842,7 @@ const $mergeX = (out, keys, f, map, ext) => {
|
|
|
845
842
|
const setDeleted = out.whenNotMatched === 'discard';
|
|
846
843
|
const replacer = map(field(omitPick().backward(spread(patch, {
|
|
847
844
|
_id: ['_id', f.of('_id').expr()],
|
|
848
|
-
touchedAt: ['touchedAt',
|
|
845
|
+
touchedAt: ['touchedAt', current],
|
|
849
846
|
}))));
|
|
850
847
|
const sss = setDeleted
|
|
851
848
|
? link()
|
|
@@ -889,7 +886,7 @@ const $mergeId = () => (out, keys, id, ext) => {
|
|
|
889
886
|
return $mergeX(out, keys, root().of('after'), or => {
|
|
890
887
|
return ite(eqTyped(root().of('after').expr(), nil), field(omRORec.backward(spread(mapExact(keys, () => nil), {
|
|
891
888
|
_id: ['_id', id],
|
|
892
|
-
touchedAt: ['touchedAt',
|
|
889
|
+
touchedAt: ['touchedAt', current],
|
|
893
890
|
}))), or);
|
|
894
891
|
}, ext);
|
|
895
892
|
};
|
|
@@ -911,7 +908,7 @@ const subMerge = (args, out, gid, extra, idPrefix, first) => {
|
|
|
911
908
|
const mapId = (k, v) => map1(k, v);
|
|
912
909
|
const F1 = {
|
|
913
910
|
_id: ['_id', to(idPrefix ? concat$1(val(idPrefix), $rand) : $rand)],
|
|
914
|
-
touchedAt: ['touchedAt', to(
|
|
911
|
+
touchedAt: ['touchedAt', to(current)],
|
|
915
912
|
};
|
|
916
913
|
const F2 = mapId(gid, to(gidPath));
|
|
917
914
|
const addExtraAndMerge = {
|
|
@@ -922,7 +919,7 @@ const subMerge = (args, out, gid, extra, idPrefix, first) => {
|
|
|
922
919
|
const addTSAndExtra = {
|
|
923
920
|
...mapExact0(e, to),
|
|
924
921
|
...(out.whenNotMatched === 'insert' ? { deletedAt: ['deletedAt', to(nil)] } : {}),
|
|
925
|
-
touchedAt: ['touchedAt', to(
|
|
922
|
+
touchedAt: ['touchedAt', to(current)],
|
|
926
923
|
};
|
|
927
924
|
const updater = set()(addTSAndExtra);
|
|
928
925
|
const whenMatched = getWhenMatched(out.whenNotMatched);
|
|
@@ -1540,7 +1537,7 @@ const $insertX = (out, expr, map, ext, extExpr) => {
|
|
|
1540
1537
|
raw: () => {
|
|
1541
1538
|
const replacer = map(mergeObjects(expr, field(mergeExpr(extExpr, {
|
|
1542
1539
|
deletedAt: ['deletedAt', nil],
|
|
1543
|
-
touchedAt: ['touchedAt',
|
|
1540
|
+
touchedAt: ['touchedAt', current],
|
|
1544
1541
|
}))));
|
|
1545
1542
|
return link()
|
|
1546
1543
|
.with($replaceWith_(replacer))
|
|
@@ -1560,7 +1557,7 @@ const $insertPart = (out, ext) => {
|
|
|
1560
1557
|
return $insertX(out, assertNotNull(root().of('after').expr()), x => ite(eq(root().of('after').expr())(nil), field(mergeExpr(translateOmit().forward(extExpr), {
|
|
1561
1558
|
deletedAt: ['deletedAt', current],
|
|
1562
1559
|
_id: ['_id', assertNotNull(root().of('before').of('_id').expr())],
|
|
1563
|
-
touchedAt: ['touchedAt',
|
|
1560
|
+
touchedAt: ['touchedAt', current],
|
|
1564
1561
|
})), x), ext, extExpr);
|
|
1565
1562
|
};
|
|
1566
1563
|
const $insert = (out) => $insertPart(out, {});
|
|
@@ -1610,7 +1607,6 @@ const addTeardown = (it, tr) => {
|
|
|
1610
1607
|
|
|
1611
1608
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
1612
1609
|
const maxTimestamp = new Timestamp(0xffffffffffffffffn);
|
|
1613
|
-
const isMax = (x) => x instanceof Timestamp && x.equals(maxTimestamp);
|
|
1614
1610
|
const getCurrentTimestamp = async (db) => {
|
|
1615
1611
|
const adminDb = db.admin();
|
|
1616
1612
|
const serverStatus = await adminDb.command({ serverStatus: 1 });
|
|
@@ -1633,53 +1629,65 @@ async function waitUntilStablePast(db, oplogTs, { pollMs = 0, timeoutMs = 10_000
|
|
|
1633
1629
|
await sleep(pollMs);
|
|
1634
1630
|
}
|
|
1635
1631
|
}
|
|
1636
|
-
async function* tailOplog(db
|
|
1637
|
-
let lastTs =
|
|
1638
|
-
const reopenDelayMs =
|
|
1632
|
+
async function* tailOplog(db) {
|
|
1633
|
+
let lastTs = await getCurrentTimestamp(db);
|
|
1634
|
+
const reopenDelayMs = 250;
|
|
1639
1635
|
const coll = db.client.db('local').collection('oplog.rs');
|
|
1640
1636
|
while (true) {
|
|
1641
1637
|
const cursor = coll.find({
|
|
1642
1638
|
ts: { $gt: lastTs },
|
|
1643
|
-
|
|
1644
|
-
|
|
1639
|
+
$or: [
|
|
1640
|
+
{
|
|
1641
|
+
ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
|
|
1642
|
+
op: { $in: ['i', 'u'] },
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
ns: 'admin.$cmd',
|
|
1646
|
+
op: 'c',
|
|
1647
|
+
'o.applyOps': {
|
|
1648
|
+
$elemMatch: {
|
|
1649
|
+
ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
|
|
1650
|
+
op: { $in: ['i', 'u'] },
|
|
1651
|
+
},
|
|
1652
|
+
},
|
|
1653
|
+
},
|
|
1654
|
+
],
|
|
1645
1655
|
}, {
|
|
1646
1656
|
tailable: true,
|
|
1647
1657
|
awaitData: true,
|
|
1648
1658
|
noCursorTimeout: true,
|
|
1649
1659
|
});
|
|
1650
1660
|
try {
|
|
1651
|
-
for await (const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
else {
|
|
1659
|
-
let changeTouched = false;
|
|
1660
|
-
if (doc.o['$v'] !== 2) {
|
|
1661
|
-
throw new Error(`Expected update with $v: 2, got ${JSON.stringify(doc)}`);
|
|
1661
|
+
for await (const docs of cursor) {
|
|
1662
|
+
for (const doc of docs.op === 'c' ? docs.o['applyOps'] : [docs]) {
|
|
1663
|
+
if (doc.op === 'i' || '_id' in doc.o) {
|
|
1664
|
+
const fields = new Set(Object.keys(doc.o));
|
|
1665
|
+
fields.delete('_id');
|
|
1666
|
+
yield { fields, doc };
|
|
1662
1667
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
if (['u', 'i', 'd'].includes(updateOp)) {
|
|
1667
|
-
updatedFields.push(...Object.keys(diff[updateOp]));
|
|
1668
|
-
if (isMax(diff[updateOp]['touchedAt'])) {
|
|
1669
|
-
changeTouched = true;
|
|
1670
|
-
}
|
|
1668
|
+
else {
|
|
1669
|
+
if (doc.o['$v'] !== 2) {
|
|
1670
|
+
throw new Error(`Expected update with $v: 2, got ${JSON.stringify(doc)}`);
|
|
1671
1671
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1672
|
+
const updatedFields = [];
|
|
1673
|
+
const diff = doc.o['diff'];
|
|
1674
|
+
for (const updateOp in diff) {
|
|
1675
|
+
if (['u', 'i', 'd'].includes(updateOp)) {
|
|
1676
|
+
updatedFields.push(...Object.keys(diff[updateOp]));
|
|
1677
|
+
}
|
|
1678
|
+
else if (updateOp.startsWith('s')) {
|
|
1679
|
+
updatedFields.push(updateOp.slice(1));
|
|
1680
|
+
}
|
|
1674
1681
|
}
|
|
1682
|
+
yield { fields: new Set(updatedFields), doc };
|
|
1675
1683
|
}
|
|
1676
|
-
yield { fields: new Set(updatedFields), doc, changeTouched };
|
|
1677
1684
|
}
|
|
1678
1685
|
}
|
|
1679
1686
|
}
|
|
1680
1687
|
catch (e) {
|
|
1681
1688
|
log('oplog loop error, notifying watchers and reopening');
|
|
1682
1689
|
console.error(e);
|
|
1690
|
+
lastTs = await getCurrentTimestamp(db);
|
|
1683
1691
|
yield null;
|
|
1684
1692
|
}
|
|
1685
1693
|
finally {
|
|
@@ -1701,7 +1709,7 @@ const loop = async (db) => {
|
|
|
1701
1709
|
let notify = makePromise();
|
|
1702
1710
|
let batch = [];
|
|
1703
1711
|
const run = async () => {
|
|
1704
|
-
for await (const event of tailOplog(db
|
|
1712
|
+
for await (const event of tailOplog(db)) {
|
|
1705
1713
|
if (event?.fields.size === 0)
|
|
1706
1714
|
continue;
|
|
1707
1715
|
batch = event && batch ? [...batch, event] : null;
|
|
@@ -1728,23 +1736,6 @@ const loop = async (db) => {
|
|
|
1728
1736
|
}
|
|
1729
1737
|
continue;
|
|
1730
1738
|
}
|
|
1731
|
-
const groups = Object.groupBy(events.filter(e => e.changeTouched), ev => ev.doc.ns);
|
|
1732
|
-
for (const [ns, evs] of Object.entries(groups)) {
|
|
1733
|
-
if (!evs)
|
|
1734
|
-
continue;
|
|
1735
|
-
const [dbName, collName] = ns.split('.');
|
|
1736
|
-
if (dbName !== db.databaseName)
|
|
1737
|
-
continue;
|
|
1738
|
-
const coll = db.collection(collName);
|
|
1739
|
-
coll
|
|
1740
|
-
.bulkWrite(evs.map((e) => ({
|
|
1741
|
-
updateOne: {
|
|
1742
|
-
filter: { _id: e.doc.o['_id'] ?? e.doc.o2?._id },
|
|
1743
|
-
update: { $set: { touchedAt: e.doc.ts } },
|
|
1744
|
-
},
|
|
1745
|
-
})))
|
|
1746
|
-
.catch(() => { });
|
|
1747
|
-
}
|
|
1748
1739
|
for (const { fields, doc } of events) {
|
|
1749
1740
|
const m = watchers.get(doc.ns);
|
|
1750
1741
|
if (!m)
|
|
@@ -1812,6 +1803,7 @@ const actions = {
|
|
|
1812
1803
|
],
|
|
1813
1804
|
};
|
|
1814
1805
|
|
|
1806
|
+
const previous = (ts) => new Timestamp({ t: ts.high - 60, i: 0 });
|
|
1815
1807
|
const getFirstStages = (view, needs) => {
|
|
1816
1808
|
const { projection, hardMatch: pre, match } = view;
|
|
1817
1809
|
const projectInput = projection && $project_(spread(projection, {
|
|
@@ -1822,7 +1814,7 @@ const getFirstStages = (view, needs) => {
|
|
|
1822
1814
|
const hardMatch = removeNotYetSynchronizedFields ? $and(pre, ...removeNotYetSynchronizedFields) : pre;
|
|
1823
1815
|
const firstStages = (lastTS, keepNulls = false) => {
|
|
1824
1816
|
const hardQuery = $and(lastTS
|
|
1825
|
-
? root().of('touchedAt').has($gtTs(lastTS.ts))
|
|
1817
|
+
? root().of('touchedAt').has($gtTs(previous(lastTS.ts)))
|
|
1826
1818
|
: root().of('deletedAt').has($eq(null)), lastTS ? null : match && $expr(match), keepNulls ? pre : hardMatch);
|
|
1827
1819
|
const ln = link()
|
|
1828
1820
|
.with($match_(hardQuery));
|
|
@@ -2129,7 +2121,7 @@ const executes$1 = (view, input, streamName, needs) => {
|
|
|
2129
2121
|
}));
|
|
2130
2122
|
const notDeleted = root().of('deletedAt').has($eq(null));
|
|
2131
2123
|
const stages = (lastTS) => {
|
|
2132
|
-
const hardQuery = $and(lastTS && root().of('touchedAt').has($gteTs(lastTS.ts)), hardMatch, notDeleted, match && $expr(match));
|
|
2124
|
+
const hardQuery = $and(lastTS && root().of('touchedAt').has($gteTs(previous(lastTS.ts))), hardMatch, notDeleted, match && $expr(match));
|
|
2133
2125
|
const ln = link().with($match_(hardQuery));
|
|
2134
2126
|
return (projectInput ? ln.with(projectInput) : ln).with(input);
|
|
2135
2127
|
};
|
|
@@ -2250,4 +2242,4 @@ const executes = (view, input, needs) => {
|
|
|
2250
2242
|
};
|
|
2251
2243
|
const single = (view, needs = {}) => pipe(input => executes(view, input, needs), emptyDelta(), concatDelta, emptyDelta);
|
|
2252
2244
|
|
|
2253
|
-
export { $accumulator, $and, $countDict, $entries, $eq, $exists, $expr, $getField, $group, $groupId, $groupMerge, $group_, $gt, $gtTs, $gte, $gteTs, $ifNull, $in, $insert, $insertPart, $insertX, $keys, $let, $lookup, $lt, $lte, $map, $map0, $map1, $match, $matchDelta, $merge, $merge2, $mergeId, $mergePart, $merge_, $ne, $nin, $nor, $or, $outerLookup, $pushDict, $rand, $reduce, $replaceWith, $set, $simpleInsert, $simpleMerge, $simpleMergePart, $sum, $type, $unwind, $unwindDelta, Field, Machine, add,
|
|
2245
|
+
export { $accumulator, $and, $countDict, $entries, $eq, $exists, $expr, $getField, $group, $groupId, $groupMerge, $group_, $gt, $gtTs, $gte, $gteTs, $ifNull, $in, $insert, $insertPart, $insertX, $keys, $let, $lookup, $lt, $lte, $map, $map0, $map1, $match, $matchDelta, $merge, $merge2, $mergeId, $mergePart, $merge_, $ne, $nin, $nor, $or, $outerLookup, $pushDict, $rand, $reduce, $replaceWith, $set, $simpleInsert, $simpleMerge, $simpleMergePart, $sum, $type, $unwind, $unwindDelta, Field, Machine, add, and, anyElementTrue, array, ceil, comp, concat$1 as concat, concatArray, createIndex, ctx, current, dateAdd, dateDiff, dateLt, datePart, dayAndMonthPart, divide, enablePreAndPostImages, eq, eqTyped, except, exprMapVal, field, fieldF, fieldM, filter, filterDefined, first$1 as first, firstSure, floor, from, func, getWhenMatched, getWhenMatchedForMerge, gt, gte, inArray, isArray, ite, last, log, lt, lte, makeCol, map1, mapVal, max, maxDate, mergeExact, mergeExact0, mergeExpr, mergeObjects, minDate, monthPart, multiply, ne, nil, noop, not, notNull, now, or, pair, prepare, rand, range, regex, root, set, setField, single, size, slice, sortArray, staging, startOf, str, sub, subtract, to, toInt, val, weekPart, wrap, year };
|
package/index.js
CHANGED
|
@@ -83,9 +83,6 @@ const val = (val) => asExpr({
|
|
|
83
83
|
? { $literal: val }
|
|
84
84
|
: val),
|
|
85
85
|
});
|
|
86
|
-
const afterWriteTime = asExpr({
|
|
87
|
-
raw: () => asExprRaw(new mongodb.Timestamp(0xffffffffffffffffn)),
|
|
88
|
-
});
|
|
89
86
|
const current = asExpr({
|
|
90
87
|
raw: () => asExprRaw('$$CLUSTER_TIME'),
|
|
91
88
|
});
|
|
@@ -847,7 +844,7 @@ const $mergeX = (out, keys, f, map, ext) => {
|
|
|
847
844
|
const setDeleted = out.whenNotMatched === 'discard';
|
|
848
845
|
const replacer = map(field(omitPick().backward(spread(patch, {
|
|
849
846
|
_id: ['_id', f.of('_id').expr()],
|
|
850
|
-
touchedAt: ['touchedAt',
|
|
847
|
+
touchedAt: ['touchedAt', current],
|
|
851
848
|
}))));
|
|
852
849
|
const sss = setDeleted
|
|
853
850
|
? link()
|
|
@@ -891,7 +888,7 @@ const $mergeId = () => (out, keys, id, ext) => {
|
|
|
891
888
|
return $mergeX(out, keys, root().of('after'), or => {
|
|
892
889
|
return ite(eqTyped(root().of('after').expr(), nil), field(omRORec.backward(spread(mapExact(keys, () => nil), {
|
|
893
890
|
_id: ['_id', id],
|
|
894
|
-
touchedAt: ['touchedAt',
|
|
891
|
+
touchedAt: ['touchedAt', current],
|
|
895
892
|
}))), or);
|
|
896
893
|
}, ext);
|
|
897
894
|
};
|
|
@@ -913,7 +910,7 @@ const subMerge = (args, out, gid, extra, idPrefix, first) => {
|
|
|
913
910
|
const mapId = (k, v) => map1(k, v);
|
|
914
911
|
const F1 = {
|
|
915
912
|
_id: ['_id', to(idPrefix ? concat$1(val(idPrefix), $rand) : $rand)],
|
|
916
|
-
touchedAt: ['touchedAt', to(
|
|
913
|
+
touchedAt: ['touchedAt', to(current)],
|
|
917
914
|
};
|
|
918
915
|
const F2 = mapId(gid, to(gidPath));
|
|
919
916
|
const addExtraAndMerge = {
|
|
@@ -924,7 +921,7 @@ const subMerge = (args, out, gid, extra, idPrefix, first) => {
|
|
|
924
921
|
const addTSAndExtra = {
|
|
925
922
|
...mapExact0(e, to),
|
|
926
923
|
...(out.whenNotMatched === 'insert' ? { deletedAt: ['deletedAt', to(nil)] } : {}),
|
|
927
|
-
touchedAt: ['touchedAt', to(
|
|
924
|
+
touchedAt: ['touchedAt', to(current)],
|
|
928
925
|
};
|
|
929
926
|
const updater = set()(addTSAndExtra);
|
|
930
927
|
const whenMatched = getWhenMatched(out.whenNotMatched);
|
|
@@ -1542,7 +1539,7 @@ const $insertX = (out, expr, map, ext, extExpr) => {
|
|
|
1542
1539
|
raw: () => {
|
|
1543
1540
|
const replacer = map(mergeObjects(expr, field(mergeExpr(extExpr, {
|
|
1544
1541
|
deletedAt: ['deletedAt', nil],
|
|
1545
|
-
touchedAt: ['touchedAt',
|
|
1542
|
+
touchedAt: ['touchedAt', current],
|
|
1546
1543
|
}))));
|
|
1547
1544
|
return link()
|
|
1548
1545
|
.with($replaceWith_(replacer))
|
|
@@ -1562,7 +1559,7 @@ const $insertPart = (out, ext) => {
|
|
|
1562
1559
|
return $insertX(out, assertNotNull(root().of('after').expr()), x => ite(eq(root().of('after').expr())(nil), field(mergeExpr(translateOmit().forward(extExpr), {
|
|
1563
1560
|
deletedAt: ['deletedAt', current],
|
|
1564
1561
|
_id: ['_id', assertNotNull(root().of('before').of('_id').expr())],
|
|
1565
|
-
touchedAt: ['touchedAt',
|
|
1562
|
+
touchedAt: ['touchedAt', current],
|
|
1566
1563
|
})), x), ext, extExpr);
|
|
1567
1564
|
};
|
|
1568
1565
|
const $insert = (out) => $insertPart(out, {});
|
|
@@ -1612,7 +1609,6 @@ const addTeardown = (it, tr) => {
|
|
|
1612
1609
|
|
|
1613
1610
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
1614
1611
|
const maxTimestamp = new mongodb.Timestamp(0xffffffffffffffffn);
|
|
1615
|
-
const isMax = (x) => x instanceof mongodb.Timestamp && x.equals(maxTimestamp);
|
|
1616
1612
|
const getCurrentTimestamp = async (db) => {
|
|
1617
1613
|
const adminDb = db.admin();
|
|
1618
1614
|
const serverStatus = await adminDb.command({ serverStatus: 1 });
|
|
@@ -1635,53 +1631,65 @@ async function waitUntilStablePast(db, oplogTs, { pollMs = 0, timeoutMs = 10_000
|
|
|
1635
1631
|
await sleep(pollMs);
|
|
1636
1632
|
}
|
|
1637
1633
|
}
|
|
1638
|
-
async function* tailOplog(db
|
|
1639
|
-
let lastTs =
|
|
1640
|
-
const reopenDelayMs =
|
|
1634
|
+
async function* tailOplog(db) {
|
|
1635
|
+
let lastTs = await getCurrentTimestamp(db);
|
|
1636
|
+
const reopenDelayMs = 250;
|
|
1641
1637
|
const coll = db.client.db('local').collection('oplog.rs');
|
|
1642
1638
|
while (true) {
|
|
1643
1639
|
const cursor = coll.find({
|
|
1644
1640
|
ts: { $gt: lastTs },
|
|
1645
|
-
|
|
1646
|
-
|
|
1641
|
+
$or: [
|
|
1642
|
+
{
|
|
1643
|
+
ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
|
|
1644
|
+
op: { $in: ['i', 'u'] },
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
ns: 'admin.$cmd',
|
|
1648
|
+
op: 'c',
|
|
1649
|
+
'o.applyOps': {
|
|
1650
|
+
$elemMatch: {
|
|
1651
|
+
ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
|
|
1652
|
+
op: { $in: ['i', 'u'] },
|
|
1653
|
+
},
|
|
1654
|
+
},
|
|
1655
|
+
},
|
|
1656
|
+
],
|
|
1647
1657
|
}, {
|
|
1648
1658
|
tailable: true,
|
|
1649
1659
|
awaitData: true,
|
|
1650
1660
|
noCursorTimeout: true,
|
|
1651
1661
|
});
|
|
1652
1662
|
try {
|
|
1653
|
-
for await (const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
else {
|
|
1661
|
-
let changeTouched = false;
|
|
1662
|
-
if (doc.o['$v'] !== 2) {
|
|
1663
|
-
throw new Error(`Expected update with $v: 2, got ${JSON.stringify(doc)}`);
|
|
1663
|
+
for await (const docs of cursor) {
|
|
1664
|
+
for (const doc of docs.op === 'c' ? docs.o['applyOps'] : [docs]) {
|
|
1665
|
+
if (doc.op === 'i' || '_id' in doc.o) {
|
|
1666
|
+
const fields = new Set(Object.keys(doc.o));
|
|
1667
|
+
fields.delete('_id');
|
|
1668
|
+
yield { fields, doc };
|
|
1664
1669
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
if (['u', 'i', 'd'].includes(updateOp)) {
|
|
1669
|
-
updatedFields.push(...Object.keys(diff[updateOp]));
|
|
1670
|
-
if (isMax(diff[updateOp]['touchedAt'])) {
|
|
1671
|
-
changeTouched = true;
|
|
1672
|
-
}
|
|
1670
|
+
else {
|
|
1671
|
+
if (doc.o['$v'] !== 2) {
|
|
1672
|
+
throw new Error(`Expected update with $v: 2, got ${JSON.stringify(doc)}`);
|
|
1673
1673
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1674
|
+
const updatedFields = [];
|
|
1675
|
+
const diff = doc.o['diff'];
|
|
1676
|
+
for (const updateOp in diff) {
|
|
1677
|
+
if (['u', 'i', 'd'].includes(updateOp)) {
|
|
1678
|
+
updatedFields.push(...Object.keys(diff[updateOp]));
|
|
1679
|
+
}
|
|
1680
|
+
else if (updateOp.startsWith('s')) {
|
|
1681
|
+
updatedFields.push(updateOp.slice(1));
|
|
1682
|
+
}
|
|
1676
1683
|
}
|
|
1684
|
+
yield { fields: new Set(updatedFields), doc };
|
|
1677
1685
|
}
|
|
1678
|
-
yield { fields: new Set(updatedFields), doc, changeTouched };
|
|
1679
1686
|
}
|
|
1680
1687
|
}
|
|
1681
1688
|
}
|
|
1682
1689
|
catch (e) {
|
|
1683
1690
|
log('oplog loop error, notifying watchers and reopening');
|
|
1684
1691
|
console.error(e);
|
|
1692
|
+
lastTs = await getCurrentTimestamp(db);
|
|
1685
1693
|
yield null;
|
|
1686
1694
|
}
|
|
1687
1695
|
finally {
|
|
@@ -1703,7 +1711,7 @@ const loop = async (db) => {
|
|
|
1703
1711
|
let notify = makePromise();
|
|
1704
1712
|
let batch = [];
|
|
1705
1713
|
const run = async () => {
|
|
1706
|
-
for await (const event of tailOplog(db
|
|
1714
|
+
for await (const event of tailOplog(db)) {
|
|
1707
1715
|
if (event?.fields.size === 0)
|
|
1708
1716
|
continue;
|
|
1709
1717
|
batch = event && batch ? [...batch, event] : null;
|
|
@@ -1730,23 +1738,6 @@ const loop = async (db) => {
|
|
|
1730
1738
|
}
|
|
1731
1739
|
continue;
|
|
1732
1740
|
}
|
|
1733
|
-
const groups = Object.groupBy(events.filter(e => e.changeTouched), ev => ev.doc.ns);
|
|
1734
|
-
for (const [ns, evs] of Object.entries(groups)) {
|
|
1735
|
-
if (!evs)
|
|
1736
|
-
continue;
|
|
1737
|
-
const [dbName, collName] = ns.split('.');
|
|
1738
|
-
if (dbName !== db.databaseName)
|
|
1739
|
-
continue;
|
|
1740
|
-
const coll = db.collection(collName);
|
|
1741
|
-
coll
|
|
1742
|
-
.bulkWrite(evs.map((e) => ({
|
|
1743
|
-
updateOne: {
|
|
1744
|
-
filter: { _id: e.doc.o['_id'] ?? e.doc.o2?._id },
|
|
1745
|
-
update: { $set: { touchedAt: e.doc.ts } },
|
|
1746
|
-
},
|
|
1747
|
-
})))
|
|
1748
|
-
.catch(() => { });
|
|
1749
|
-
}
|
|
1750
1741
|
for (const { fields, doc } of events) {
|
|
1751
1742
|
const m = watchers.get(doc.ns);
|
|
1752
1743
|
if (!m)
|
|
@@ -1814,6 +1805,7 @@ const actions = {
|
|
|
1814
1805
|
],
|
|
1815
1806
|
};
|
|
1816
1807
|
|
|
1808
|
+
const previous = (ts) => new mongodb.Timestamp({ t: ts.high - 60, i: 0 });
|
|
1817
1809
|
const getFirstStages = (view, needs) => {
|
|
1818
1810
|
const { projection, hardMatch: pre, match } = view;
|
|
1819
1811
|
const projectInput = projection && $project_(spread(projection, {
|
|
@@ -1824,7 +1816,7 @@ const getFirstStages = (view, needs) => {
|
|
|
1824
1816
|
const hardMatch = removeNotYetSynchronizedFields ? $and(pre, ...removeNotYetSynchronizedFields) : pre;
|
|
1825
1817
|
const firstStages = (lastTS, keepNulls = false) => {
|
|
1826
1818
|
const hardQuery = $and(lastTS
|
|
1827
|
-
? root().of('touchedAt').has($gtTs(lastTS.ts))
|
|
1819
|
+
? root().of('touchedAt').has($gtTs(previous(lastTS.ts)))
|
|
1828
1820
|
: root().of('deletedAt').has($eq(null)), lastTS ? null : match && $expr(match), keepNulls ? pre : hardMatch);
|
|
1829
1821
|
const ln = link()
|
|
1830
1822
|
.with($match_(hardQuery));
|
|
@@ -2131,7 +2123,7 @@ const executes$1 = (view, input, streamName, needs) => {
|
|
|
2131
2123
|
}));
|
|
2132
2124
|
const notDeleted = root().of('deletedAt').has($eq(null));
|
|
2133
2125
|
const stages = (lastTS) => {
|
|
2134
|
-
const hardQuery = $and(lastTS && root().of('touchedAt').has($gteTs(lastTS.ts)), hardMatch, notDeleted, match && $expr(match));
|
|
2126
|
+
const hardQuery = $and(lastTS && root().of('touchedAt').has($gteTs(previous(lastTS.ts))), hardMatch, notDeleted, match && $expr(match));
|
|
2135
2127
|
const ln = link().with($match_(hardQuery));
|
|
2136
2128
|
return (projectInput ? ln.with(projectInput) : ln).with(input);
|
|
2137
2129
|
};
|
|
@@ -2308,7 +2300,6 @@ exports.$unwindDelta = $unwindDelta;
|
|
|
2308
2300
|
exports.Field = Field;
|
|
2309
2301
|
exports.Machine = Machine;
|
|
2310
2302
|
exports.add = add;
|
|
2311
|
-
exports.afterWriteTime = afterWriteTime;
|
|
2312
2303
|
exports.and = and;
|
|
2313
2304
|
exports.anyElementTrue = anyElementTrue;
|
|
2314
2305
|
exports.array = array;
|