@omegup/msync 0.1.44 → 0.1.46

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.
Files changed (4) hide show
  1. package/index.d.ts +35 -11
  2. package/index.esm.js +362 -229
  3. package/index.js +363 -228
  4. package/package.json +1 -1
package/index.js CHANGED
@@ -219,7 +219,7 @@ const { root, ctx } = Field;
219
219
  const asAccumulator = (x) => x;
220
220
  const $sum_ = (expr) => ({ raw: f => asAccumulator({ $sum: expr.raw(f).get() }) });
221
221
  const $sum = (expr) => ({
222
- group: $sum_(ite(root().of('old').expr(), subtract(val(0), $ifNull(sub(expr, root().of('v')), val(0))), sub(expr, root().of('v')))),
222
+ group: $sum_(ite(root().of('deleted').expr(), val(0), ite(root().of('old').expr(), subtract(val(0), $ifNull(sub(expr, root().of('v')), val(0))), sub(expr, root().of('v'))))),
223
223
  merge: (x, y) => add($ifNull(x, val(0)), y),
224
224
  });
225
225
  const $accumulator_ = (init, accumulateArgs, accumulate, merge) => {
@@ -242,7 +242,13 @@ const $accumulator = (init, accumulateArgs, accumulate, merge) => ({
242
242
  });
243
243
  const $countDict = (expr) => $accumulator(function () {
244
244
  return {};
245
- }, [sub(expr, root().of('v')), root().of('old').expr()], function (a, k, old) {
245
+ }, [
246
+ sub(expr, root().of('v')),
247
+ root().of('old').expr(),
248
+ root().of('deleted').expr(),
249
+ ], function (a, k, old, deleted) {
250
+ if (deleted)
251
+ return a;
246
252
  let y = (a[k] || 0) + (old ? -1 : 1);
247
253
  return y ? (a[k] = y) : delete a[k], a;
248
254
  }, function (a, b) {
@@ -253,7 +259,13 @@ const $countDict = (expr) => $accumulator(function () {
253
259
  });
254
260
  const $countDictArray = (expr) => $accumulator(function () {
255
261
  return {};
256
- }, [sub(expr, root().of('v')), root().of('old').expr()], function (a, keys, old) {
262
+ }, [
263
+ sub(expr, root().of('v')),
264
+ root().of('old').expr(),
265
+ root().of('deleted').expr(),
266
+ ], function (a, keys, old, deleted) {
267
+ if (deleted)
268
+ return a;
257
269
  keys.forEach(k => {
258
270
  const y = (a[k] || 0) + (old ? -1 : 1);
259
271
  if (y)
@@ -274,7 +286,10 @@ const $pushDict = (key, value) => $accumulator(function () {
274
286
  sub(key, root().of('v')),
275
287
  sub(value, root().of('v')),
276
288
  root().of('old').expr(),
277
- ], function (ra, k, v, old) {
289
+ root().of('deleted').expr(),
290
+ ], function (ra, k, v, old, deleted) {
291
+ if (deleted)
292
+ return ra;
278
293
  let a = { ...ra };
279
294
  const equal = (a, b) => {
280
295
  if (!a || !b)
@@ -402,6 +417,7 @@ const assertEqual = () => ({
402
417
  forward1: id,
403
418
  backward1: id,
404
419
  });
420
+ const literalsEqaul = (_) => assertEqual();
405
421
  const omitRORec = () => assertEqual();
406
422
  const omitPick = () => assertEqual();
407
423
  const translateOmit = () => assertEqual();
@@ -420,7 +436,7 @@ const filter = ({ as, cond, expr, limit, }) => asExpr({
420
436
  input: expr.raw(f).get(),
421
437
  as,
422
438
  cond: cond.raw(f).get(),
423
- limit: limit?.raw(f).get() ?? null,
439
+ ...(limit ? { limit: limit.raw(f).get() } : {}),
424
440
  },
425
441
  }),
426
442
  });
@@ -564,9 +580,23 @@ const subGroup = (id, args, addGrp) => {
564
580
  const part = filterDefined(array(ite(eqTyped(root().of('after').expr(), nil), nil, field({
565
581
  v: ['v', root().of('after').expr()],
566
582
  old: ['old', val(false)],
583
+ deleted: [
584
+ 'deleted',
585
+ root()
586
+ .of('deleted')
587
+ .of('after')
588
+ .expr(),
589
+ ],
567
590
  })), ite(eqTyped(root().of('before').expr(), nil), nil, field({
568
591
  v: ['v', root().of('before').expr()],
569
592
  old: ['old', val(true)],
593
+ deleted: [
594
+ 'deleted',
595
+ root()
596
+ .of('deleted')
597
+ .of('before')
598
+ .expr(),
599
+ ],
570
600
  }))));
571
601
  const replaceWith = (expr) => $replaceWith_(expr);
572
602
  return link()
@@ -1052,9 +1082,10 @@ const $setDelta = (updater) => {
1052
1082
  after1: ['after1', 'after'],
1053
1083
  before1: ['before1', 'before'],
1054
1084
  };
1055
- const update = (k) => subUpdater(weaken(updater), root().of(k));
1085
+ const side = (k) => root().of(k);
1086
+ const update = (k) => subUpdater(weaken(updater), side(k));
1056
1087
  return link()
1057
- .with($setEach(k => to(root().of(k).expr()), dict))
1088
+ .with($setEach(k => to(side(k).expr()), dict))
1058
1089
  .with($setEach(update, dict))
1059
1090
  .with($replaceWithEach(field => root().of(`${field}1`).expr()))
1060
1091
  .with($setEach(k => to(nil), dict)).stages;
@@ -1088,147 +1119,229 @@ const dualIn = operator();
1088
1119
  const $in = dualIn('$in');
1089
1120
  const $nin = dualIn('$nin');
1090
1121
 
1091
- const $unwindDelta = (k1, k2, k, middle, includeNull1, includeNull2) => {
1092
- const outer1 = includeNull1 === null;
1093
- const outer2 = includeNull2 === null;
1094
- const newItems = {
1095
- $filter: {
1096
- input: { $ifNull: [`$after.${k2}`, []] },
1122
+ const $matchDelta = (query) => concatStages($replaceWithDelta(ite(query, root().expr(), nil)), matchDelta());
1123
+ const matchDelta = () => {
1124
+ const part = (side) => root().of(side).expr();
1125
+ return $match_($expr(ne(part('before'))(part('after'))));
1126
+ };
1127
+
1128
+ const $match = (query) => ({
1129
+ raw: $match1($expr(query)),
1130
+ delta: $matchDelta(query),
1131
+ });
1132
+
1133
+ const joinIdOf = (k, k1, k1Id, k2Id) => {
1134
+ if (k === k1)
1135
+ return k1Id;
1136
+ if (typeof k === 'string')
1137
+ return k2Id;
1138
+ const idOf = (key) => (key === k1 ? k1Id : k2Id);
1139
+ return concat(idOf(k[0]), val(k[1]), idOf(k[2]));
1140
+ };
1141
+ const andAlso = (probe, keep) => ite(eqTyped(probe, nil), nil, keep);
1142
+ const notObjAndAlso = (probe, keep) => ite(eqTyped(probe, field({})), nil, keep);
1143
+ const $unwindDelta = (k1, k2, k, includeNull1, includeNull2) => {
1144
+ const emptyArr = array();
1145
+ const src = root();
1146
+ const k2Arr = (side) => src.of(side).of(k2).expr();
1147
+ const k2OrEmpty = (side) => $ifNull(k2Arr(side), emptyArr);
1148
+ const k2Empty = (side) => eq(k2Arr(side))(emptyArr);
1149
+ const beforeItems = k2OrEmpty('before');
1150
+ const afterItems = k2OrEmpty('after');
1151
+ const beforeIds = $map1(beforeItems, (item) => item.of('_id').expr());
1152
+ const newByK2Id = filter({
1153
+ expr: afterItems,
1154
+ as: 'a',
1155
+ cond: not(inArray(ctx()('a').of('_id').expr(), beforeIds)),
1156
+ });
1157
+ const oldByK2Id = $map0({
1158
+ input: beforeItems,
1159
+ as: 'b',
1160
+ expr: field({
1161
+ before: ['before', ctx()('b').expr()],
1162
+ after: [
1163
+ 'after',
1164
+ first$1(filter({
1165
+ expr: afterItems,
1166
+ as: 'a',
1167
+ cond: eq(ctx()('a').of('_id').expr())(ctx()('b').of('_id').expr()),
1168
+ })),
1169
+ ],
1170
+ }),
1171
+ });
1172
+ const k1Slot = (fill) => array(field({
1173
+ before: [
1174
+ 'before',
1175
+ $ifNull(first$1(beforeItems), fill),
1176
+ ],
1177
+ after: [
1178
+ 'after',
1179
+ $ifNull(first$1(afterItems), fill),
1180
+ ],
1181
+ }));
1182
+ const kept = k === k1 ? k1Slot(nil) : oldByK2Id;
1183
+ const added = k === k1 ? emptyArr : newByK2Id;
1184
+ const k1Delta = field({
1185
+ before: [
1186
+ 'before',
1187
+ $ifNull(src.of('before').of(k1).expr(), nil),
1188
+ ],
1189
+ after: [
1190
+ 'after',
1191
+ $ifNull(src.of('after').of(k1).expr(), nil),
1192
+ ],
1193
+ });
1194
+ const unwindJoin = ({ kept, added, padToNull, isPad, join, }) => {
1195
+ const newDeltas = $map0({
1196
+ input: added,
1097
1197
  as: 'a',
1098
- cond: {
1099
- $not: {
1100
- $in: [
1101
- '$$a._id',
1102
- {
1103
- $map: {
1104
- input: { $ifNull: [`$before.${k2}`, []] },
1105
- as: 'b',
1106
- in: '$$b._id',
1107
- },
1108
- },
1109
- ],
1110
- },
1111
- },
1112
- },
1113
- };
1114
- const oldItems = {
1115
- $map: {
1116
- input: { $ifNull: [`$before.${k2}`, []] },
1117
- as: 'b',
1118
- in: {
1119
- before: '$$b',
1120
- after: {
1121
- $ifNull: [
1122
- {
1123
- $first: {
1124
- $filter: {
1125
- input: `$after.${k2}`,
1126
- as: 'a',
1127
- cond: { $eq: ['$$a._id', '$$b._id'] },
1128
- },
1129
- },
1130
- },
1131
- null,
1132
- ],
1133
- },
1134
- },
1135
- },
1136
- };
1137
- const ifNull = (k, part, str = `$${k}.${part}._id`) => outer2 && k == k2 ? { $ifNull: [str, 'null'] } : str;
1138
- const interDot = ([a, b]) => [a, middle ?? '.', b];
1139
- const partReplace = (part) => {
1140
- const def = {
1141
- _id: k
1142
- ? ifNull(k, part)
1143
- : {
1144
- $concat: interDot([k1, k2].sort().map(k => ifNull(k, part))),
1145
- },
1146
- [k1]: `$${k1}.${part}`,
1147
- [k2]: outer2
1148
- ? {
1149
- $cond: {
1150
- if: `$${k2}.${part}._id`,
1151
- then: `$${k2}.${part}`,
1152
- else: null,
1153
- },
1154
- }
1155
- : `$${k2}.${part}`,
1198
+ expr: field({
1199
+ before: ['before', nil],
1200
+ after: ['after', ctx()('a').expr()],
1201
+ }),
1202
+ });
1203
+ const deltas = mergeObjects(field(map1(k1, k1Delta)), field(map1(k2, concatArray(kept, newDeltas))));
1204
+ const doc = root();
1205
+ const parentId = doc.of('_id').expr();
1206
+ const k1At = (side) => doc.of(k1).of(side).expr();
1207
+ const k2Raw = (side) => doc.of(k2).of(side).expr();
1208
+ const docK2Null = root();
1209
+ const k2Keep = (side) => docK2Null.of(k2).of(side).expr();
1210
+ const k2At = (side) => padToNull(k2Raw(side), k2Keep(side));
1211
+ const rowOf = (id, a, b) => mergeObjects(mergeObjects(field({ _id: ['_id', id] }), field(map1(k1, a))), field(map1(k2, b)));
1212
+ const docK1 = root();
1213
+ const k2RawK1 = (side) => docK1.of(k2).of(side).expr();
1214
+ const k2KeepK1K2 = (side) => root().of(k2).of(side).expr();
1215
+ const rowOnK1 = (side) => {
1216
+ const t = docK1.of(k1).of(side);
1217
+ return rowOf(t.of('_id').expr(), t.expr(), padToNull(k2RawK1(side), k2KeepK1K2(side)));
1156
1218
  };
1157
- const skipNulls = [{ $eq: [`$${k1}.${part}`, null] }, { $eq: [`$${k2}.${part}`, null] }].filter((_, i) => ![outer1, outer2][i]);
1158
- if (skipNulls.length === 0)
1159
- return def;
1160
- return {
1161
- $cond: {
1162
- if: skipNulls.length === 1 ? skipNulls[0] : { $or: skipNulls },
1163
- then: null,
1164
- else: def,
1165
- },
1219
+ const docK2 = root();
1220
+ const k1AtK2 = (side) => docK2.of(k1).of(side).expr();
1221
+ const rowOnK2 = (side) => {
1222
+ const u = docK2.of(k2).of(side);
1223
+ return rowOf(u.of('_id').expr(), k1AtK2(side), u.expr());
1166
1224
  };
1225
+ const docK1K2 = root();
1226
+ const k1AtK1K2 = (side) => docK1K2.of(k1).of(side).expr();
1227
+ const k2AtK1K2 = (side) => docK1K2.of(k2).of(side).expr();
1228
+ const k1IdK1K2 = (side) => docK1K2.of(k1).of(side).of('_id').expr();
1229
+ const k2IdK1K2 = (side) => docK1K2.of(k2).of(side).of('_id').expr();
1230
+ const completeId = (side) => joinIdOf(k, k1, k1IdK1K2(side), k2IdK1K2(side));
1231
+ const rowK1K2 = (side) => rowOf(completeId(side), k1AtK1K2(side), k2AtK1K2(side));
1232
+ const liftRowId = $replaceWith_(mergeObjects(root().expr(), field({
1233
+ _id: [
1234
+ '_id',
1235
+ $ifNull(root()
1236
+ .of('before')
1237
+ .of('_id')
1238
+ .expr(), root()
1239
+ .of('after')
1240
+ .of('_id')
1241
+ .expr(), root().of('_id').expr()),
1242
+ ],
1243
+ })));
1244
+ const padded = (side) => isPad(k2Raw(side));
1245
+ const emptyObj = field({});
1246
+ const deletedFlags = mergeObjects(ite(padded('before'), field({ before: ['before', val(true)] }), emptyObj), ite(padded('after'), field({ after: ['after', val(true)] }), emptyObj));
1247
+ const joinAt = (side) => join({
1248
+ k1: k1At(side),
1249
+ k2: k2At(side),
1250
+ rowK1K2: rowK1K2(side),
1251
+ rowOnK1: rowOnK1(side),
1252
+ rowOnK2: rowOnK2(side),
1253
+ k2Keep: k2Keep(side),
1254
+ k2Raw: k2Raw(side),
1255
+ k2RawK1: k2RawK1(side),
1256
+ k2KeepK1K2: k2KeepK1K2(side),
1257
+ });
1258
+ const joinDelta = mergeObjects(field({
1259
+ _id: ['_id', parentId],
1260
+ before: ['before', joinAt('before')],
1261
+ after: ['after', joinAt('after')],
1262
+ }), ite(or(padded('before'), padded('after')), field({ deleted: ['deleted', deletedFlags] }), emptyObj));
1263
+ return link()
1264
+ .with($replaceWith_(mergeObjects(src.expr(), deltas)))
1265
+ .with($unwind_(k2))
1266
+ .with($replaceWith_(joinDelta))
1267
+ .with(matchDelta())
1268
+ .with(liftRowId).stages;
1167
1269
  };
1168
- const part = (k) => root().of(k).expr();
1169
- const stages = link()
1170
- .with(asStages([
1171
- {
1172
- $set: {
1173
- [k1]: {
1174
- before: { $ifNull: [`$before.${k1}`, null] },
1175
- after: { $ifNull: [`$after.${k1}`, null] },
1176
- },
1177
- [k2]: {
1178
- $concatArrays: [
1179
- outer2
1180
- ? {
1181
- $cond: {
1182
- if: { $eq: [`$before.${k2}`, []] },
1183
- then: {
1184
- $cond: {
1185
- if: { $eq: [`$after.${k2}`, []] },
1186
- then: [{ before: {}, after: {} }],
1187
- else: [{ before: {}, after: null }],
1188
- },
1189
- },
1190
- else: oldItems,
1191
- },
1192
- }
1193
- : oldItems,
1194
- {
1195
- $map: {
1196
- input: outer2
1197
- ? {
1198
- $cond: {
1199
- if: {
1200
- $and: [{ $ne: [`$before.${k2}`, []] }, { $eq: [`$after.${k2}`, []] }],
1201
- },
1202
- then: [{}],
1203
- else: newItems,
1204
- },
1205
- }
1206
- : newItems,
1207
- as: 'a',
1208
- in: { before: null, after: '$$a' },
1209
- },
1210
- },
1211
- ],
1212
- },
1213
- },
1214
- },
1215
- ]))
1216
- .with($unwind_(k2))
1217
- .with(asStages([
1218
- {
1219
- $replaceWith: {
1220
- _id: '$_id',
1221
- before: partReplace('before'),
1222
- after: partReplace('after'),
1270
+ const noPadToNull = (k2) => k2;
1271
+ const neverPad = (_k2) => val(false);
1272
+ if (includeNull2 === null) {
1273
+ const padObj = field({});
1274
+ const padDelta = (after) => field({
1275
+ before: ['before', padObj],
1276
+ after: ['after', after],
1277
+ });
1278
+ const addedPadded = k === k1
1279
+ ? array()
1280
+ : ite(and(not(k2Empty('before')), k2Empty('after')), array(padObj), added);
1281
+ const padToNull = (k2, keep) => ite(eqTyped(k2, field({})), nil, keep);
1282
+ const isPad = (k2) => ite(eqTyped(k2, field({})), val(true), val(false));
1283
+ const keptMatched = ite(k2Empty('before'), ite(k2Empty('after'), array(padDelta(padObj)), array(padDelta(nil))), kept);
1284
+ const padded = { added: addedPadded, padToNull, isPad };
1285
+ if (includeNull1 === null) {
1286
+ if (k === k1) {
1287
+ return unwindJoin({
1288
+ ...padded,
1289
+ kept: k1Slot(padObj),
1290
+ join: ({ k1: k1Val, rowOnK1 }) => andAlso(k1Val, rowOnK1),
1291
+ });
1292
+ }
1293
+ if (k === k2) {
1294
+ return unwindJoin({
1295
+ ...padded,
1296
+ kept: keptMatched,
1297
+ join: ({ k2Raw, k2Keep, rowOnK2 }) => notObjAndAlso(k2Raw, andAlso(k2Keep, rowOnK2)),
1298
+ });
1299
+ }
1300
+ return unwindJoin({
1301
+ ...padded,
1302
+ kept: keptMatched,
1303
+ join: ({ k1: k1Val, k2RawK1, k2KeepK1K2, rowK1K2 }) => andAlso(k1Val, notObjAndAlso(k2RawK1, andAlso(k2KeepK1K2, rowK1K2))),
1304
+ });
1305
+ }
1306
+ const eq2 = literalsEqaul();
1307
+ if (k === k1) {
1308
+ return unwindJoin({
1309
+ ...padded,
1310
+ kept: k1Slot(padObj),
1311
+ join: ({ k1: k1Val, rowOnK1 }) => andAlso(k1Val, rowOnK1),
1312
+ });
1313
+ }
1314
+ return unwindJoin({
1315
+ ...padded,
1316
+ kept: keptMatched,
1317
+ join: ({ k1: k1Val, k2RawK1, k2KeepK1K2, rowK1K2 }) => andAlso(k1Val, notObjAndAlso(k2RawK1, andAlso(k2KeepK1K2, eq2.forward(rowK1K2)))),
1318
+ });
1319
+ }
1320
+ if (includeNull1 === null) {
1321
+ const eq1 = literalsEqaul();
1322
+ return unwindJoin({
1323
+ kept,
1324
+ added,
1325
+ padToNull: noPadToNull,
1326
+ isPad: neverPad,
1327
+ join: ({ k1, k2: k2Val, k2KeepK1K2, rowK1K2, rowOnK2 }) => {
1328
+ return k === k2
1329
+ ? andAlso(k2Val, rowOnK2)
1330
+ : andAlso(k1, andAlso(k2KeepK1K2, eq1.forward(rowK1K2)));
1223
1331
  },
1224
- },
1225
- ...(!k && middle ? [{ $set: { _id: { $ifNull: ['$before._id', '$after._id'] } } }] : []),
1226
- ]))
1227
- .with($match_($expr(ne(part('before'))(part('after'))))).stages;
1228
- return stages;
1332
+ });
1333
+ }
1334
+ return unwindJoin({
1335
+ kept,
1336
+ added,
1337
+ padToNull: noPadToNull,
1338
+ isPad: neverPad,
1339
+ join: ({ k1, k2KeepK1K2, rowK1K2 }) => andAlso(k1, andAlso(k2KeepK1K2, rowK1K2)),
1340
+ });
1229
1341
  };
1230
1342
 
1231
- const $unwind = (k, dict, middle) => {
1343
+ const $unwind = (k, dict, middle = '.') => {
1344
+ const joinK = ['left', middle, 'right'];
1232
1345
  return {
1233
1346
  delta: link()
1234
1347
  .with($replaceWithDelta(field({
@@ -1236,22 +1349,32 @@ const $unwind = (k, dict, middle) => {
1236
1349
  left: ['left', root().expr()],
1237
1350
  right: ['right', root().of(k).expr()],
1238
1351
  })))
1239
- .with($unwindDelta('left', 'right', false, middle))
1352
+ .with($unwindDelta('left', 'right', joinK))
1240
1353
  .with($replaceWithDelta(mergeObjects(root().of('left').expr(), fieldM({
1241
1354
  key: root().of('right').expr(),
1242
1355
  id: root().of('_id').expr(),
1243
1356
  }, dict)))).stages,
1244
- raw: $unwind1(k),
1357
+ raw: (f) => link()
1358
+ .with($unwind1(k)(f))
1359
+ .with($set1(set()({
1360
+ _id: [
1361
+ '_id',
1362
+ to(joinIdOf(joinK, 'left', root().of('_id').expr(), root().of(k).of('_id').expr())),
1363
+ ],
1364
+ }))(f)).stages,
1245
1365
  };
1246
1366
  };
1247
1367
 
1248
1368
  const $lookupDelta = ({ field1, field2 }, { coll, exec, input }, k1, k2, k, includeNull1, includeNull2) => {
1249
1369
  const omit = omitRORec();
1250
- const hash = crypto$1.createHash('md5').update(coll.collectionName + jsonCanonicalize.canonicalize(input) + jsonCanonicalize.canonicalize(exec)).digest('base64url');
1251
- const ss = (f) => to($ifNull(root().of(f).of(k1).with(field1).expr(), val(hash)));
1370
+ const hash = crypto$1
1371
+ .createHash('md5')
1372
+ .update(coll.collectionName + jsonCanonicalize.canonicalize(input) + jsonCanonicalize.canonicalize(exec))
1373
+ .digest('base64url');
1374
+ const normForeignKey = (f) => to($ifNull(root().of(f).of(k1).with(field1).expr(), val(hash)));
1252
1375
  return link()
1253
1376
  .with($replaceWithDelta(field(map1(k1, root().expr()))))
1254
- .with($set_(set()({ bId: ['bId', ss('before')], aId: ['aId', ss('after')] })))
1377
+ .with($set_(set()({ bId: ['bId', normForeignKey('before')], aId: ['aId', normForeignKey('after')] })))
1255
1378
  .with($simpleLookup_({
1256
1379
  coll,
1257
1380
  k: 'a',
@@ -1284,16 +1407,22 @@ const $lookupDelta = ({ field1, field2 }, { coll, exec, input }, k1, k2, k, incl
1284
1407
  return $ifNull(root().of(f).expr(), field(omit.backward(mergeExpr(omit.forward(map1(k2, otherField.of(k2).expr())), map1(k1, nil)))));
1285
1408
  })
1286
1409
  : link().stages)
1287
- .with($unwindDelta(k1, k2, k, undefined, includeNull1, includeNull2)).stages;
1410
+ .with($unwindDelta(k1, k2, k, includeNull1, includeNull2)).stages;
1288
1411
  };
1289
1412
 
1290
1413
  const $lookupRaw = ({ field1, field2 }, { coll, exec, input }, k2, k, includeNull) => (f) => {
1291
- root().of('_id').expr();
1414
+ const left = root().of('_id').expr();
1415
+ const right = root().of(k2).of('_id').expr();
1416
+ const keepLeft = k === 'left';
1417
+ const composedId = typeof k === 'string' ? right : concat(left, val(k[1]), right);
1292
1418
  return link()
1293
1419
  .with($simpleLookup1({
1294
1420
  coll,
1295
1421
  k: k2,
1296
- fields: { local: field1, foreign: root().of('before').with(field2) },
1422
+ fields: {
1423
+ local: field1,
1424
+ foreign: root().of('before').with(field2),
1425
+ },
1297
1426
  vars: {},
1298
1427
  pipeline: link()
1299
1428
  .with(input)
@@ -1301,8 +1430,11 @@ const $lookupRaw = ({ field1, field2 }, { coll, exec, input }, k2, k, includeNul
1301
1430
  .with($replaceWith_(root().of('before').expr())).stages,
1302
1431
  })(f))
1303
1432
  .with($unwind1(k2, includeNull)(f))
1304
- .with(link().stages
1305
- ).stages;
1433
+ .with(keepLeft
1434
+ ? link().stages
1435
+ : $set1(set()({
1436
+ _id: ['_id', to($ifNull(composedId, left))],
1437
+ }))(f)).stages;
1306
1438
  };
1307
1439
 
1308
1440
  const asBefore = (f) => f(() => root().of('before'));
@@ -1498,9 +1630,11 @@ const runCont = async (it, cb) => {
1498
1630
  };
1499
1631
 
1500
1632
  const merge = ({ lsource: L, rsource: R, }) => mergeIterators({ sources: { L, R } });
1501
- const join = ({ lField, rField, left, right, as }, leftSnapshot, rightSnapshot, stagesUntilNextLookup, outerLeft) => {
1633
+ const join = ({ lField, rField, left, right, as, toMany }, leftSnapshot, rightSnapshot, stagesUntilNextLookup, outerLeft, middle = '.') => {
1502
1634
  const rightJoinField = { field1: lField, field2: rField };
1503
- const joinId = 'left';
1635
+ const joinId = rField.str() === '_id' && toMany !== true
1636
+ ? 'left'
1637
+ : ['left', middle, 'right'];
1504
1638
  const joinR_Snapshot = asBefore($lookupRaw(rightJoinField, rightSnapshot, as, joinId, outerLeft));
1505
1639
  const resultingSnapshot = concatTStages(leftSnapshot, joinR_Snapshot);
1506
1640
  const dict = { [as]: 'a' };
@@ -1512,7 +1646,10 @@ const join = ({ lField, rField, left, right, as }, leftSnapshot, rightSnapshot,
1512
1646
  const leftJoinField = { field1: rField, field2: lField };
1513
1647
  const joinL_Delta = $lookupDelta(leftJoinField, leftSnapshot, 'right', 'left', joinId, outerLeft);
1514
1648
  const joinR_Delta = $lookupDelta(rightJoinField, rightSnapshot, 'left', 'right', joinId, undefined, outerLeft);
1515
- const mergeForeignIntoDoc = concatStages($replaceWithDelta(mergeObjects(root().of('left').expr(), fieldM({ a: root().of('right').expr(), b: root().of('_id').expr() }, dictId))), stagesUntilNextLookup.delta);
1649
+ const mergeForeignIntoDoc = concatStages($replaceWithDelta(mergeObjects(root().of('left').expr(), fieldM({
1650
+ a: root().of('right').expr(),
1651
+ b: root().of('_id').expr(),
1652
+ }, dictId))), stagesUntilNextLookup.delta);
1516
1653
  const lRunnerInput = concatStages(joinR_Delta, mergeForeignIntoDoc);
1517
1654
  const rRunnerInput = concatStages(joinL_Delta, mergeForeignIntoDoc);
1518
1655
  const getRunner = (f, stages, final) => f.out({
@@ -1541,6 +1678,7 @@ const $lookup = (p) => (l) => $lookup1({
1541
1678
  lField: p.localField,
1542
1679
  rField: p.foreignField,
1543
1680
  left: l(emptyDelta()),
1681
+ toMany: p.toMany,
1544
1682
  });
1545
1683
  const $outerLookup = (p) => (l) => $lookup1({
1546
1684
  right: p.from,
@@ -1548,28 +1686,9 @@ const $outerLookup = (p) => (l) => $lookup1({
1548
1686
  lField: p.localField,
1549
1687
  rField: p.foreignField,
1550
1688
  left: l(emptyDelta()),
1689
+ toMany: p.toMany,
1551
1690
  }, null);
1552
1691
 
1553
- const filterUndefined = (applyOperator) => {
1554
- return (op, args) => applyOperator(op, args.filter(defined));
1555
- };
1556
- const combine = (op, applyOperatorToPreItems) => (...args) => {
1557
- const mapToItems = applyOperatorToPreItems(op, args);
1558
- return mapToItems && { raw: field => mapToItems(x => x.raw(field)) };
1559
- };
1560
- const all = (op, x) => x.length === 0 ? undefined : f => ({ [op]: x.map(f) });
1561
- const first = (op, x) => (x.length === 1 ? f => f(x[0]) : all(op, x));
1562
- const $and = combine('$and', filterUndefined(first));
1563
- const $nor = combine('$nor', filterUndefined(all));
1564
- const $or = combine('$or', filterUndefined(first));
1565
-
1566
- const $matchDelta = (query) => concatStages($replaceWithDelta(ite(query, root().expr(), nil)), $match_($or(root().of('after').has($ne(null)), root().of('before').has($ne(null)))));
1567
-
1568
- const $match = (query) => ({
1569
- raw: $match1($expr(query)),
1570
- delta: $matchDelta(query),
1571
- });
1572
-
1573
1692
  const $setCore = (updater) => ({
1574
1693
  delta: $setDelta(updater),
1575
1694
  raw: $set1(updater),
@@ -1590,7 +1709,10 @@ const $insertX = (out, expr, map, ext, extExpr, update = x => x) => {
1590
1709
  teardown: c => c({
1591
1710
  collection: out,
1592
1711
  method: 'updateMany',
1593
- params: [filter, [{ $set: { deletedAt: '$$NOW', touchedAt: '$$CLUSTER_TIME' } }]],
1712
+ params: [
1713
+ filter,
1714
+ [{ $set: { deletedAt: '$$NOW', touchedAt: '$$CLUSTER_TIME' } }],
1715
+ ],
1594
1716
  }),
1595
1717
  raw: () => {
1596
1718
  const replacer = map(mergeObjects(expr, field(mergeExpr(extExpr, {
@@ -1614,7 +1736,10 @@ const $insertPart = (out, ext) => {
1614
1736
  const extExpr = mapExact(ext, (v) => val(v));
1615
1737
  return $insertX(out, assertNotNull(root().of('after').expr()), x => ite(eq(root().of('after').expr())(nil), field(mergeExpr(translateOmit().forward(extExpr), {
1616
1738
  deletedAt: ['deletedAt', current],
1617
- _id: ['_id', assertNotNull(root().of('before').of('_id').expr())],
1739
+ _id: [
1740
+ '_id',
1741
+ assertNotNull(root().of('before').of('_id').expr()),
1742
+ ],
1618
1743
  touchedAt: ['touchedAt', current],
1619
1744
  })), x), ext, extExpr);
1620
1745
  };
@@ -1630,8 +1755,7 @@ const aggregate = (db, streamName, input, snapshot = true, start = Date.now()) =
1630
1755
  };
1631
1756
  log('exec', streamName, req);
1632
1757
  const start2 = Date.now();
1633
- return db
1634
- .then(d => d.command(req))
1758
+ return db.command(req)
1635
1759
  .then(result => {
1636
1760
  log('prepare', streamName, Date.now() - start);
1637
1761
  log('prepare2', streamName, start2 - start);
@@ -1862,6 +1986,19 @@ const actions = {
1862
1986
  ],
1863
1987
  };
1864
1988
 
1989
+ const filterUndefined = (applyOperator) => {
1990
+ return (op, args) => applyOperator(op, args.filter(defined));
1991
+ };
1992
+ const combine = (op, applyOperatorToPreItems) => (...args) => {
1993
+ const mapToItems = applyOperatorToPreItems(op, args);
1994
+ return mapToItems && { raw: field => mapToItems(x => x.raw(field)) };
1995
+ };
1996
+ const all = (op, x) => x.length === 0 ? undefined : f => ({ [op]: x.map(f) });
1997
+ const first = (op, x) => (x.length === 1 ? f => f(x[0]) : all(op, x));
1998
+ const $and = combine('$and', filterUndefined(first));
1999
+ const $nor = combine('$nor', filterUndefined(all));
2000
+ const $or = combine('$or', filterUndefined(first));
2001
+
1865
2002
  const previous = (ts) => new mongodb.Timestamp({ t: ts.high - 60, i: 0 });
1866
2003
  const getFirstStages = (view, needs) => {
1867
2004
  const { projection, hardMatch: pre, match } = view;
@@ -1882,54 +2019,9 @@ const getFirstStages = (view, needs) => {
1882
2019
  return { firstStages, hardMatch };
1883
2020
  };
1884
2021
 
1885
- require('dotenv').config();
1886
- const uri = process.env['MONGO_URL'];
1887
-
1888
- const enablePreAndPostImages = (coll) => coll.s.db.command({
1889
- collMod: coll.collectionName,
1890
- changeStreamPreAndPostImages: { enabled: true },
1891
- });
1892
- const prepare = async (testName) => {
1893
- const client = new mongodb.MongoClient(uri, testName ? { monitorCommands: true } : {});
1894
- if (testName) {
1895
- const handler = (c) => {
1896
- promises.writeFile(`./out/${testName}.log`, JSON.stringify(c.command) + ',\n', { flag: 'w' });
1897
- };
1898
- client.on('commandStarted', handler);
1899
- client.on('commandSucceeded', handler);
1900
- }
1901
- await client.connect();
1902
- await client.db('admin').command({
1903
- setClusterParameter: {
1904
- changeStreamOptions: {
1905
- preAndPostImages: { expireAfterSeconds: 60 },
1906
- },
1907
- },
1908
- });
1909
- return client;
1910
- };
1911
- const makeCol = async (docs, database, name) => {
1912
- if (!name) {
1913
- (name = crypto.randomUUID());
1914
- }
1915
- try {
1916
- const col = await database.createCollection(name, {
1917
- changeStreamPreAndPostImages: { enabled: true },
1918
- });
1919
- if (docs.length)
1920
- await col.insertMany([...docs]);
1921
- return col;
1922
- }
1923
- catch {
1924
- return database.collection(name);
1925
- }
1926
- };
1927
-
1928
2022
  const streamNames = {};
1929
2023
  const executes$2 = (view, input, streamName, skip = false, after, needs = {}) => {
1930
2024
  const { collection, projection, match } = view;
1931
- const client = prepare();
1932
- const pdb = client.then(cl => cl.db(collection.dbName));
1933
2025
  const { firstStages, hardMatch } = getFirstStages(view, needs);
1934
2026
  const db = collection.s.db, coll = collection.collectionName;
1935
2027
  const hash = crypto$1
@@ -2067,7 +2159,7 @@ const executes$2 = (view, input, streamName, skip = false, after, needs = {}) =>
2067
2159
  whenNotMatched: 'insert',
2068
2160
  })).stages;
2069
2161
  const stream = await makeStream();
2070
- const r = await aggregate(pdb, streamName, c => c({ coll: collection, input: cloneIntoNew }));
2162
+ const r = await aggregate(db, streamName, c => c({ coll: collection, input: cloneIntoNew }));
2071
2163
  const start = Date.now();
2072
2164
  const res = await snapshotCollection.deleteMany({ updated: true, after: null, before: null });
2073
2165
  log('deleting from cloned into new collection', Date.now() - start, res, `db['${snapshotCollection.collectionName}'].deleteMany({ updated: true, after: null, before: null })`);
@@ -2081,7 +2173,7 @@ const executes$2 = (view, input, streamName, skip = false, after, needs = {}) =>
2081
2173
  const stages = finalInput.raw(first);
2082
2174
  await last.updateOne({ _id: streamName }, { $set: { job: 1 } }, { upsert: true });
2083
2175
  const nextRes = stream.tryNext();
2084
- const aggResult = await aggregate(pdb, streamName, c => c({
2176
+ const aggResult = await aggregate(db, streamName, c => c({
2085
2177
  coll: snapshotCollection,
2086
2178
  input: link()
2087
2179
  .with($match_(root().of('updated').has($eq(true))))
@@ -2163,8 +2255,6 @@ const executes$1 = (view, input, streamName, needs) => {
2163
2255
  else if (streamNames[streamName] != hash)
2164
2256
  throw new Error('streamName already used');
2165
2257
  const { collection, projection, hardMatch: pre, match } = view;
2166
- const client = prepare();
2167
- const pdb = client.then(cl => cl.db(collection.dbName));
2168
2258
  const removeNotYetSynchronizedFields = projection &&
2169
2259
  Object.values(mapExactToObject(projection, (_, k) => (needs[k] ?? k.startsWith('_')) ? root().of(k).has($exists(true)) : null));
2170
2260
  const hardMatch = removeNotYetSynchronizedFields
@@ -2258,7 +2348,7 @@ const executes$1 = (view, input, streamName, needs) => {
2258
2348
  const raw = stages(lastTS).with(finalInput.raw(lastTS === null)).stages;
2259
2349
  const stream = await makeStream();
2260
2350
  const nextRes = stream.tryNext();
2261
- const aggResult = await aggregate(pdb, streamName, c => c({
2351
+ const aggResult = await aggregate(db, streamName, c => c({
2262
2352
  coll: collection,
2263
2353
  input: raw,
2264
2354
  }));
@@ -2301,6 +2391,49 @@ const executes = (view, input, needs) => {
2301
2391
  };
2302
2392
  const single = (view, needs = {}) => pipe(input => executes(view, input, needs), emptyDelta(), concatDelta, emptyDelta);
2303
2393
 
2394
+ require('dotenv').config();
2395
+ const uri = process.env['MONGO_URL'];
2396
+
2397
+ const enablePreAndPostImages = (coll) => coll.s.db.command({
2398
+ collMod: coll.collectionName,
2399
+ changeStreamPreAndPostImages: { enabled: true },
2400
+ });
2401
+ const prepare = async (testName) => {
2402
+ const client = new mongodb.MongoClient(uri, testName ? { monitorCommands: true } : {});
2403
+ if (testName) {
2404
+ const handler = (c) => {
2405
+ promises.writeFile(`./out/${testName}.log`, JSON.stringify(c.command) + ',\n', { flag: 'w' });
2406
+ };
2407
+ client.on('commandStarted', handler);
2408
+ client.on('commandSucceeded', handler);
2409
+ }
2410
+ await client.connect();
2411
+ await client.db('admin').command({
2412
+ setClusterParameter: {
2413
+ changeStreamOptions: {
2414
+ preAndPostImages: { expireAfterSeconds: 60 },
2415
+ },
2416
+ },
2417
+ });
2418
+ return client;
2419
+ };
2420
+ const makeCol = async (docs, database, name) => {
2421
+ if (!name) {
2422
+ (name = crypto.randomUUID());
2423
+ }
2424
+ try {
2425
+ const col = await database.createCollection(name, {
2426
+ changeStreamPreAndPostImages: { enabled: true },
2427
+ });
2428
+ if (docs.length)
2429
+ await col.insertMany([...docs]);
2430
+ return col;
2431
+ }
2432
+ catch {
2433
+ return database.collection(name);
2434
+ }
2435
+ };
2436
+
2304
2437
  exports.$accumulator = $accumulator;
2305
2438
  exports.$and = $and;
2306
2439
  exports.$countDict = $countDict;
@@ -2398,6 +2531,7 @@ exports.gte = gte;
2398
2531
  exports.inArray = inArray;
2399
2532
  exports.isArray = isArray;
2400
2533
  exports.ite = ite;
2534
+ exports.joinIdOf = joinIdOf;
2401
2535
  exports.last = last;
2402
2536
  exports.log = log;
2403
2537
  exports.lt = lt;
@@ -2407,6 +2541,7 @@ exports.map1 = map1;
2407
2541
  exports.mapFromPlainDateTime = mapFromPlainDateTime;
2408
2542
  exports.mapToPlainDateTime = mapToPlainDateTime;
2409
2543
  exports.mapVal = mapVal;
2544
+ exports.matchDelta = matchDelta;
2410
2545
  exports.max = max;
2411
2546
  exports.maxDate = maxDate;
2412
2547
  exports.mergeExact = mergeExact;