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