@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.
- package/index.d.ts +35 -11
- package/index.esm.js +362 -229
- package/index.js +363 -228
- 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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 $
|
|
1092
|
-
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
{
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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',
|
|
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:
|
|
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
|
|
1251
|
-
|
|
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',
|
|
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,
|
|
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: {
|
|
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(
|
|
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 = '
|
|
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({
|
|
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: [
|
|
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: [
|
|
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(
|
|
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(
|
|
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(
|
|
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;
|