@omegup/msync 0.1.45 → 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 +315 -177
  3. package/index.js +316 -176
  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
  };
@@ -1861,6 +1986,19 @@ const actions = {
1861
1986
  ],
1862
1987
  };
1863
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
+
1864
2002
  const previous = (ts) => new mongodb.Timestamp({ t: ts.high - 60, i: 0 });
1865
2003
  const getFirstStages = (view, needs) => {
1866
2004
  const { projection, hardMatch: pre, match } = view;
@@ -2393,6 +2531,7 @@ exports.gte = gte;
2393
2531
  exports.inArray = inArray;
2394
2532
  exports.isArray = isArray;
2395
2533
  exports.ite = ite;
2534
+ exports.joinIdOf = joinIdOf;
2396
2535
  exports.last = last;
2397
2536
  exports.log = log;
2398
2537
  exports.lt = lt;
@@ -2402,6 +2541,7 @@ exports.map1 = map1;
2402
2541
  exports.mapFromPlainDateTime = mapFromPlainDateTime;
2403
2542
  exports.mapToPlainDateTime = mapToPlainDateTime;
2404
2543
  exports.mapVal = mapVal;
2544
+ exports.matchDelta = matchDelta;
2405
2545
  exports.max = max;
2406
2546
  exports.maxDate = maxDate;
2407
2547
  exports.mergeExact = mergeExact;