@omegup/msync 0.1.45 → 0.1.47

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 +41 -14
  2. package/index.esm.js +348 -191
  3. package/index.js +349 -190
  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,10 @@ 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 = (params, leftSnapshot, rightSnapshot, stagesUntilNextLookup, outerLeft) => {
1632
+ const { lField, rField, left, right, as } = params;
1500
1633
  const rightJoinField = { field1: lField, field2: rField };
1501
- const joinId = 'left';
1634
+ const joinId = params.to === 'one' ? 'left' : ['left', params.middle, 'right'];
1502
1635
  const joinR_Snapshot = asBefore($lookupRaw(rightJoinField, rightSnapshot, as, joinId, outerLeft));
1503
1636
  const resultingSnapshot = concatTStages(leftSnapshot, joinR_Snapshot);
1504
1637
  const dict = { [as]: 'a' };
@@ -1510,7 +1643,10 @@ const join = ({ lField, rField, left, right, as }, leftSnapshot, rightSnapshot,
1510
1643
  const leftJoinField = { field1: rField, field2: lField };
1511
1644
  const joinL_Delta = $lookupDelta(leftJoinField, leftSnapshot, 'right', 'left', joinId, outerLeft);
1512
1645
  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);
1646
+ const mergeForeignIntoDoc = concatStages($replaceWithDelta(mergeObjects(root().of('left').expr(), fieldM({
1647
+ a: root().of('right').expr(),
1648
+ b: root().of('_id').expr(),
1649
+ }, dictId))), stagesUntilNextLookup.delta);
1514
1650
  const lRunnerInput = concatStages(joinR_Delta, mergeForeignIntoDoc);
1515
1651
  const rRunnerInput = concatStages(joinL_Delta, mergeForeignIntoDoc);
1516
1652
  const getRunner = (f, stages, final) => f.out({
@@ -1533,40 +1669,42 @@ const join = ({ lField, rField, left, right, as }, leftSnapshot, rightSnapshot,
1533
1669
  };
1534
1670
  };
1535
1671
  const $lookup1 = (p, outerLeft) => (input) => p.left.stages((lStages) => p.right.stages((rStages) => join(p, lStages, rStages, input, outerLeft)));
1536
- const $lookup = (p) => (l) => $lookup1({
1537
- right: p.from,
1538
- as: p.as,
1539
- lField: p.localField,
1540
- rField: p.foreignField,
1541
- left: l(emptyDelta()),
1542
- });
1543
- const $outerLookup = (p) => (l) => $lookup1({
1544
- right: p.from,
1545
- as: p.as,
1546
- lField: p.localField,
1547
- rField: p.foreignField,
1548
- left: l(emptyDelta()),
1549
- }, null);
1550
-
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
- });
1672
+ const $lookup = (p) => (l) => p.to === 'one'
1673
+ ? $lookup1({
1674
+ right: p.from,
1675
+ as: p.as,
1676
+ lField: p.localField,
1677
+ rField: root().of('_id'),
1678
+ left: l(emptyDelta()),
1679
+ to: 'one',
1680
+ })
1681
+ : $lookup1({
1682
+ to: 'many',
1683
+ right: p.from,
1684
+ as: p.as,
1685
+ lField: p.localField,
1686
+ rField: p.foreignField,
1687
+ left: l(emptyDelta()),
1688
+ middle: p.middle,
1689
+ });
1690
+ const $outerLookup = (p) => (l) => p.to === 'one'
1691
+ ? $lookup1({
1692
+ right: p.from,
1693
+ as: p.as,
1694
+ lField: p.localField,
1695
+ rField: root().of('_id'),
1696
+ left: l(emptyDelta()),
1697
+ to: 'one',
1698
+ }, null)
1699
+ : $lookup1({
1700
+ to: 'many',
1701
+ right: p.from,
1702
+ as: p.as,
1703
+ lField: p.localField,
1704
+ rField: p.foreignField,
1705
+ left: l(emptyDelta()),
1706
+ middle: p.middle,
1707
+ }, null);
1570
1708
 
1571
1709
  const $setCore = (updater) => ({
1572
1710
  delta: $setDelta(updater),
@@ -1588,7 +1726,10 @@ const $insertX = (out, expr, map, ext, extExpr, update = x => x) => {
1588
1726
  teardown: c => c({
1589
1727
  collection: out,
1590
1728
  method: 'updateMany',
1591
- params: [filter, [{ $set: { deletedAt: '$$NOW', touchedAt: '$$CLUSTER_TIME' } }]],
1729
+ params: [
1730
+ filter,
1731
+ [{ $set: { deletedAt: '$$NOW', touchedAt: '$$CLUSTER_TIME' } }],
1732
+ ],
1592
1733
  }),
1593
1734
  raw: () => {
1594
1735
  const replacer = map(mergeObjects(expr, field(mergeExpr(extExpr, {
@@ -1612,7 +1753,10 @@ const $insertPart = (out, ext) => {
1612
1753
  const extExpr = mapExact(ext, (v) => val(v));
1613
1754
  return $insertX(out, assertNotNull(root().of('after').expr()), x => ite(eq(root().of('after').expr())(nil), field(mergeExpr(translateOmit().forward(extExpr), {
1614
1755
  deletedAt: ['deletedAt', current],
1615
- _id: ['_id', assertNotNull(root().of('before').of('_id').expr())],
1756
+ _id: [
1757
+ '_id',
1758
+ assertNotNull(root().of('before').of('_id').expr()),
1759
+ ],
1616
1760
  touchedAt: ['touchedAt', current],
1617
1761
  })), x), ext, extExpr);
1618
1762
  };
@@ -1859,6 +2003,19 @@ const actions = {
1859
2003
  ],
1860
2004
  };
1861
2005
 
2006
+ const filterUndefined = (applyOperator) => {
2007
+ return (op, args) => applyOperator(op, args.filter(defined));
2008
+ };
2009
+ const combine = (op, applyOperatorToPreItems) => (...args) => {
2010
+ const mapToItems = applyOperatorToPreItems(op, args);
2011
+ return mapToItems && { raw: field => mapToItems(x => x.raw(field)) };
2012
+ };
2013
+ const all = (op, x) => x.length === 0 ? undefined : f => ({ [op]: x.map(f) });
2014
+ const first = (op, x) => (x.length === 1 ? f => f(x[0]) : all(op, x));
2015
+ const $and = combine('$and', filterUndefined(first));
2016
+ const $nor = combine('$nor', filterUndefined(all));
2017
+ const $or = combine('$or', filterUndefined(first));
2018
+
1862
2019
  const previous = (ts) => new Timestamp({ t: ts.high - 60, i: 0 });
1863
2020
  const getFirstStages = (view, needs) => {
1864
2021
  const { projection, hardMatch: pre, match } = view;
@@ -2294,4 +2451,4 @@ const makeCol = async (docs, database, name) => {
2294
2451
  }
2295
2452
  };
2296
2453
 
2297
- 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 };
2454
+ 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 };