@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 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, afterWriteTime, 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 };
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 { Timestamp, UUID, MongoClient } from 'mongodb';
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', afterWriteTime],
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', afterWriteTime],
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(afterWriteTime)],
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(afterWriteTime)],
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', afterWriteTime],
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', afterWriteTime],
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, opts) {
1637
- let lastTs = opts.since ?? (await getCurrentTimestamp(db));
1638
- const reopenDelayMs = opts.reopenDelayMs ?? 250;
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
- ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
1644
- op: { $in: ['i', 'u'] },
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 doc of cursor) {
1652
- lastTs = doc.ts;
1653
- if (doc.op === 'i' || '_id' in doc.o) {
1654
- const fields = new Set(Object.keys(doc.o));
1655
- fields.delete('_id');
1656
- yield { fields, doc, changeTouched: isMax(doc.o['touchedAt']) };
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
- const updatedFields = [];
1664
- const diff = doc.o['diff'];
1665
- for (const updateOp in diff) {
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
- else if (updateOp.startsWith('s')) {
1673
- updatedFields.push(updateOp.slice(1));
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, afterWriteTime, 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 };
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', afterWriteTime],
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', afterWriteTime],
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(afterWriteTime)],
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(afterWriteTime)],
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', afterWriteTime],
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', afterWriteTime],
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, opts) {
1639
- let lastTs = opts.since ?? (await getCurrentTimestamp(db));
1640
- const reopenDelayMs = opts.reopenDelayMs ?? 250;
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
- ns: RegExp(`^${db.namespace}\\.(?!tmp_)(?!__).*(?<!_snapshot)$`),
1646
- op: { $in: ['i', 'u'] },
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 doc of cursor) {
1654
- lastTs = doc.ts;
1655
- if (doc.op === 'i' || '_id' in doc.o) {
1656
- const fields = new Set(Object.keys(doc.o));
1657
- fields.delete('_id');
1658
- yield { fields, doc, changeTouched: isMax(doc.o['touchedAt']) };
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
- const updatedFields = [];
1666
- const diff = doc.o['diff'];
1667
- for (const updateOp in diff) {
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
- else if (updateOp.startsWith('s')) {
1675
- updatedFields.push(updateOp.slice(1));
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;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "module": "index.esm.js",
4
4
  "typings": "index.d.ts",
5
5
  "name": "@omegup/msync",
6
- "version": "0.1.29",
6
+ "version": "0.1.31",
7
7
  "dependencies": {
8
8
  "dayjs": "^1.11.9",
9
9
  "dotenv": "^16.3.1",