@ignex/ninox 0.1.7 → 0.2.0
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/README.md +46 -22
- package/dist/index.d.ts +88 -87
- package/dist/index.js +724 -220
- package/dist/index.js.map +1 -1
- package/dist/{timeout-B10CTiGm.d.ts → timeout-BaP92Iyg.d.ts} +5 -15
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +71 -35
- package/dist/utils.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ObjectId, Decimal128, Long, Int32, Double, Timestamp, Binary, MinKey, MaxKey, DBRef, Code, MongoClient } from 'mongodb';
|
|
1
|
+
import { BSON, ObjectId, Decimal128, Long, Int32, Double, Timestamp, Binary, MinKey, MaxKey, DBRef, Code, MongoClient } from 'mongodb';
|
|
2
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
3
|
import { writeFile, mkdir, readdir } from 'fs/promises';
|
|
3
4
|
import { basename, join } from 'path';
|
|
4
5
|
import { pathToFileURL } from 'url';
|
|
6
|
+
import { isIP } from 'net';
|
|
5
7
|
|
|
6
8
|
// src/utils/timeout.ts
|
|
7
9
|
var withTimeout = (promise, timeoutMs, label) => {
|
|
@@ -228,7 +230,7 @@ var isMongoDuplicateKeyError = (error) => {
|
|
|
228
230
|
};
|
|
229
231
|
var isTransactionUnsupportedError = (error) => {
|
|
230
232
|
const code = error?.code;
|
|
231
|
-
if (code === 20) return true;
|
|
233
|
+
if (code === 20 || code === "TRANSACTIONS_UNSUPPORTED") return true;
|
|
232
234
|
const message = error instanceof Error ? error.message : "";
|
|
233
235
|
return /Transaction numbers are only allowed/i.test(message) || /transactions are not supported/i.test(message) || /does not support retryable writes/i.test(message);
|
|
234
236
|
};
|
|
@@ -313,6 +315,12 @@ var mapMongoDriverError = (error, ctx = {}) => {
|
|
|
313
315
|
}
|
|
314
316
|
const code = error.code;
|
|
315
317
|
switch (code) {
|
|
318
|
+
case 20:
|
|
319
|
+
return new InfraError(
|
|
320
|
+
"TRANSACTIONS_UNSUPPORTED",
|
|
321
|
+
"MongoDB deployment does not support transactions; enable allowNonTransactionalFallback to run non-atomically",
|
|
322
|
+
context
|
|
323
|
+
);
|
|
316
324
|
case 112:
|
|
317
325
|
return new DomainError("VERSION_CONFLICT", "Document version conflict", context);
|
|
318
326
|
case 50:
|
|
@@ -406,46 +414,81 @@ var cloneDeep = (value) => {
|
|
|
406
414
|
}
|
|
407
415
|
return value;
|
|
408
416
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (v instanceof Date) return `date:${v.getTime()}`;
|
|
419
|
-
if (typeof v === "object") {
|
|
420
|
-
const anyV = v;
|
|
421
|
-
if (typeof anyV.toHexString === "function") return `oid:${anyV.toHexString()}`;
|
|
422
|
-
if (anyV instanceof RegExp) return `re:${anyV.source}/${anyV.flags}`;
|
|
423
|
-
}
|
|
424
|
-
return v;
|
|
417
|
+
var { EJSON } = BSON;
|
|
418
|
+
var bsonTypeOf = (value) => {
|
|
419
|
+
const type = value._bsontype;
|
|
420
|
+
return typeof type === "string" ? type : void 0;
|
|
421
|
+
};
|
|
422
|
+
var isPlainObject = (value) => {
|
|
423
|
+
if (value === null || typeof value !== "object") return false;
|
|
424
|
+
const prototype = Object.getPrototypeOf(value);
|
|
425
|
+
return (prototype === Object.prototype || prototype === null) && bsonTypeOf(value) === void 0;
|
|
425
426
|
};
|
|
426
427
|
var stableStringify = (value) => {
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
428
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
429
|
+
const visit = (input) => {
|
|
430
|
+
if (input === null) return { t: "null" };
|
|
431
|
+
if (input === void 0) return { t: "undefined" };
|
|
432
|
+
switch (typeof input) {
|
|
433
|
+
case "string":
|
|
434
|
+
case "boolean":
|
|
435
|
+
return { t: typeof input, v: input };
|
|
436
|
+
case "number":
|
|
437
|
+
if (Number.isNaN(input)) return { t: "number", v: "NaN" };
|
|
438
|
+
if (input === Infinity) return { t: "number", v: "Infinity" };
|
|
439
|
+
if (input === -Infinity) return { t: "number", v: "-Infinity" };
|
|
440
|
+
if (Object.is(input, -0)) return { t: "number", v: "-0" };
|
|
441
|
+
return { t: "number", v: input };
|
|
442
|
+
case "bigint":
|
|
443
|
+
return { t: "bigint", v: input.toString() };
|
|
444
|
+
case "symbol":
|
|
445
|
+
case "function":
|
|
446
|
+
throw new TypeError(`stableStringify: unsupported ${typeof input} value`);
|
|
447
|
+
}
|
|
448
|
+
const object = input;
|
|
449
|
+
if (ancestors.has(object)) throw new TypeError("stableStringify: cyclic value");
|
|
450
|
+
ancestors.add(object);
|
|
451
|
+
try {
|
|
452
|
+
if (input instanceof Date) {
|
|
453
|
+
return { t: "date", v: Number.isNaN(input.getTime()) ? "Invalid" : input.toISOString() };
|
|
454
|
+
}
|
|
455
|
+
if (input instanceof RegExp) return { t: "regexp", v: `re:${input.source}/${input.flags}` };
|
|
456
|
+
const bsonType = bsonTypeOf(object);
|
|
457
|
+
if (bsonType !== void 0) {
|
|
458
|
+
return { t: "bson", kind: bsonType, v: EJSON.stringify(input, { relaxed: false }) };
|
|
459
|
+
}
|
|
460
|
+
if (Array.isArray(input)) return { t: "array", v: input.map(visit) };
|
|
461
|
+
if (input instanceof Map) {
|
|
462
|
+
return {
|
|
463
|
+
t: "map",
|
|
464
|
+
v: [...input.entries()].map(([key, entry]) => [visit(key), visit(entry)])
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (input instanceof Set) return { t: "set", v: [...input].map(visit) };
|
|
468
|
+
if (Buffer.isBuffer(input)) return { t: "buffer", v: input.toString("base64") };
|
|
469
|
+
if (ArrayBuffer.isView(input)) {
|
|
470
|
+
return {
|
|
471
|
+
t: "bytes",
|
|
472
|
+
kind: input.constructor.name,
|
|
473
|
+
v: Buffer.from(input.buffer, input.byteOffset, input.byteLength).toString("base64")
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (input instanceof ArrayBuffer) {
|
|
477
|
+
return { t: "arraybuffer", v: Buffer.from(input).toString("base64") };
|
|
478
|
+
}
|
|
479
|
+
if (isPlainObject(input)) {
|
|
480
|
+
const out = {};
|
|
481
|
+
for (const key of Object.keys(input).sort()) out[key] = visit(input[key]);
|
|
482
|
+
return { t: "object", v: out };
|
|
483
|
+
}
|
|
484
|
+
throw new TypeError(`stableStringify: unsupported object ${input.constructor.name}`);
|
|
485
|
+
} finally {
|
|
486
|
+
ancestors.delete(object);
|
|
433
487
|
}
|
|
434
|
-
return toPrimitive(v);
|
|
435
488
|
};
|
|
436
489
|
return JSON.stringify(visit(value));
|
|
437
490
|
};
|
|
438
|
-
var hashString = (input) =>
|
|
439
|
-
let h1 = 5381;
|
|
440
|
-
let h2 = 2166136261;
|
|
441
|
-
for (let i = 0; i < input.length; i++) {
|
|
442
|
-
const c = input.charCodeAt(i);
|
|
443
|
-
h1 = (h1 << 5) + h1 + c | 0;
|
|
444
|
-
h2 = (h2 ^ c) * 16777619 | 0;
|
|
445
|
-
h2 = h2 << 3 | h2 >>> 29;
|
|
446
|
-
}
|
|
447
|
-
return `${(h1 >>> 0).toString(36)}${(h2 >>> 0).toString(36)}`;
|
|
448
|
-
};
|
|
491
|
+
var hashString = (input) => createHash("sha256").update(input, "utf8").digest("base64url");
|
|
449
492
|
var stableHash = (value) => hashString(stableStringify(value));
|
|
450
493
|
|
|
451
494
|
// src/utils/lru.ts
|
|
@@ -590,7 +633,6 @@ var RefreshTicker = class {
|
|
|
590
633
|
this.host = host;
|
|
591
634
|
this.intervalMs = tickIntervalMs;
|
|
592
635
|
}
|
|
593
|
-
host;
|
|
594
636
|
timer;
|
|
595
637
|
intervalMs;
|
|
596
638
|
/** Whether the timer is currently running. */
|
|
@@ -725,7 +767,6 @@ var WatchCoordinator = class {
|
|
|
725
767
|
constructor(host) {
|
|
726
768
|
this.host = host;
|
|
727
769
|
}
|
|
728
|
-
host;
|
|
729
770
|
streams = /* @__PURE__ */ new Map();
|
|
730
771
|
failedStreams = /* @__PURE__ */ new Set();
|
|
731
772
|
stopped = true;
|
|
@@ -1274,7 +1315,7 @@ var createHotCache = (options) => new HotCache(options);
|
|
|
1274
1315
|
// src/cache/query-cache.ts
|
|
1275
1316
|
var SEP = "\0";
|
|
1276
1317
|
var NS_SEP = "";
|
|
1277
|
-
var cacheCollectionKey = (dbName, physical) => `${dbName}${NS_SEP}${physical}`;
|
|
1318
|
+
var cacheCollectionKey = (dbName, physical, clusterNamespace = "") => `${clusterNamespace}${NS_SEP}${dbName}${NS_SEP}${physical}`;
|
|
1278
1319
|
var QueryCache = class {
|
|
1279
1320
|
lru;
|
|
1280
1321
|
index = /* @__PURE__ */ new Map();
|
|
@@ -1299,8 +1340,8 @@ var QueryCache = class {
|
|
|
1299
1340
|
max: options.maxSize ?? 500,
|
|
1300
1341
|
onEvict: (key) => this.unregisterKey(key)
|
|
1301
1342
|
});
|
|
1302
|
-
this.ttlMs = options.ttlMs ??
|
|
1303
|
-
this.clone = options.clone ??
|
|
1343
|
+
this.ttlMs = options.ttlMs ?? 3e4;
|
|
1344
|
+
this.clone = options.clone ?? true;
|
|
1304
1345
|
}
|
|
1305
1346
|
get size() {
|
|
1306
1347
|
return this.lru.size;
|
|
@@ -1375,6 +1416,7 @@ var QueryCache = class {
|
|
|
1375
1416
|
this.sets++;
|
|
1376
1417
|
const ttl = ttlMs ?? this.ttlMs;
|
|
1377
1418
|
const expiresAt = ttl > 0 ? Date.now() + ttl : Infinity;
|
|
1419
|
+
this.unregisterKey(key);
|
|
1378
1420
|
this.lru.set(key, {
|
|
1379
1421
|
value,
|
|
1380
1422
|
expiresAt,
|
|
@@ -1433,14 +1475,24 @@ var withGracefulMongoTransaction = async (runner, fn, options = {}) => {
|
|
|
1433
1475
|
}
|
|
1434
1476
|
}
|
|
1435
1477
|
}
|
|
1436
|
-
|
|
1478
|
+
const unsupported = () => new InfraError(
|
|
1479
|
+
"TRANSACTIONS_UNSUPPORTED",
|
|
1480
|
+
"MongoDB deployment does not support transactions; set allowNonTransactionalFallback to run non-atomically"
|
|
1481
|
+
);
|
|
1482
|
+
if (!enabled) {
|
|
1483
|
+
if (options.allowNonTransactionalFallback === true) return fn(null);
|
|
1484
|
+
throw unsupported();
|
|
1485
|
+
}
|
|
1437
1486
|
try {
|
|
1438
|
-
return await runner.transaction(fn
|
|
1487
|
+
return await runner.transaction(fn, {
|
|
1488
|
+
...options.allowNonTransactionalFallback !== void 0 ? { allowNonTransactionalFallback: options.allowNonTransactionalFallback } : {}
|
|
1489
|
+
});
|
|
1439
1490
|
} catch (err) {
|
|
1440
1491
|
if (isTransactionUnsupportedError(err)) {
|
|
1492
|
+
if (options.allowNonTransactionalFallback !== true) throw err;
|
|
1441
1493
|
options.logger?.warn?.(
|
|
1442
1494
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
1443
|
-
"transactions unsupported
|
|
1495
|
+
"transactions unsupported; running explicitly requested non-transactional fallback"
|
|
1444
1496
|
);
|
|
1445
1497
|
return fn(null);
|
|
1446
1498
|
}
|
|
@@ -1607,89 +1659,134 @@ var nextMigrationNumber = async (dir) => {
|
|
|
1607
1659
|
return match ? Math.max(max, Number(match[1])) : max;
|
|
1608
1660
|
}, 0) + 1;
|
|
1609
1661
|
};
|
|
1610
|
-
|
|
1611
|
-
// src/migrations/journal.ts
|
|
1612
1662
|
var isDuplicateKeyError = (err) => {
|
|
1613
1663
|
const code = err?.code;
|
|
1614
1664
|
if (code === 11e3 || code === 11001) return true;
|
|
1615
1665
|
return /E11000 duplicate key/.test(String(err?.message ?? ""));
|
|
1616
1666
|
};
|
|
1667
|
+
var expiredLeaseFilter = (now, staleCutoff) => ({
|
|
1668
|
+
$or: [
|
|
1669
|
+
{ expiresAt: { $lt: now } },
|
|
1670
|
+
{ expiresAt: { $exists: false }, appliedAt: { $lt: staleCutoff } }
|
|
1671
|
+
]
|
|
1672
|
+
});
|
|
1617
1673
|
var createMigrationJournal = (manager) => {
|
|
1618
1674
|
let indexReady = false;
|
|
1619
1675
|
const ensureIndex = async () => {
|
|
1620
1676
|
if (indexReady) return;
|
|
1621
|
-
|
|
1622
|
-
await manager.createIndex
|
|
1623
|
-
} catch {
|
|
1677
|
+
if (manager.createIndex) {
|
|
1678
|
+
await manager.createIndex(JOURNAL_COLLECTION, { name: 1 }, { unique: true });
|
|
1624
1679
|
}
|
|
1625
1680
|
indexReady = true;
|
|
1626
1681
|
};
|
|
1627
1682
|
const appliedNames = async () => {
|
|
1628
|
-
const rows = await manager.findMany(JOURNAL_COLLECTION);
|
|
1683
|
+
const rows = await manager.findMany(JOURNAL_COLLECTION, {}, { cache: false });
|
|
1629
1684
|
return new Set(
|
|
1630
|
-
rows.filter(
|
|
1685
|
+
rows.filter(
|
|
1686
|
+
(row) => row.status === void 0 || row.status === "applied" || row.status === "rolling_back"
|
|
1687
|
+
).map((row) => row.name)
|
|
1631
1688
|
);
|
|
1632
1689
|
};
|
|
1633
|
-
const stealableFilter = (name, now, staleCutoff) => ({
|
|
1634
|
-
name,
|
|
1635
|
-
status: { $ne: "applied" },
|
|
1636
|
-
$or: [
|
|
1637
|
-
{ expiresAt: { $lt: now } },
|
|
1638
|
-
{ expiresAt: { $exists: false }, appliedAt: { $lt: staleCutoff } },
|
|
1639
|
-
{ status: { $nin: ["running", "applied"] } }
|
|
1640
|
-
]
|
|
1641
|
-
});
|
|
1642
1690
|
const claim = async (name, file, leaseMs) => {
|
|
1643
1691
|
await ensureIndex();
|
|
1644
1692
|
const now = /* @__PURE__ */ new Date();
|
|
1645
|
-
const
|
|
1693
|
+
const staleCutoff = new Date(Date.now() - leaseMs);
|
|
1694
|
+
const claimId = randomUUID();
|
|
1646
1695
|
const stole = await manager.findOneAndUpdate(
|
|
1647
1696
|
JOURNAL_COLLECTION,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1697
|
+
{
|
|
1698
|
+
name,
|
|
1699
|
+
status: { $nin: ["applied", "rolling_back"] },
|
|
1700
|
+
...expiredLeaseFilter(now, staleCutoff)
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
$set: {
|
|
1704
|
+
status: "running",
|
|
1705
|
+
appliedAt: now,
|
|
1706
|
+
file,
|
|
1707
|
+
expiresAt: new Date(Date.now() + leaseMs),
|
|
1708
|
+
claimId
|
|
1709
|
+
}
|
|
1710
|
+
},
|
|
1711
|
+
{ returnDocument: "before", cache: false }
|
|
1651
1712
|
);
|
|
1652
|
-
if (stole !== null) return
|
|
1713
|
+
if (stole !== null) return claimId;
|
|
1653
1714
|
try {
|
|
1654
1715
|
await manager.insertOne(JOURNAL_COLLECTION, {
|
|
1655
1716
|
name,
|
|
1656
1717
|
appliedAt: now,
|
|
1657
1718
|
status: "running",
|
|
1658
1719
|
file,
|
|
1659
|
-
expiresAt
|
|
1720
|
+
expiresAt: new Date(Date.now() + leaseMs),
|
|
1721
|
+
claimId
|
|
1660
1722
|
});
|
|
1661
|
-
return
|
|
1723
|
+
return claimId;
|
|
1662
1724
|
} catch (err) {
|
|
1663
|
-
if (isDuplicateKeyError(err)) return
|
|
1725
|
+
if (isDuplicateKeyError(err)) return null;
|
|
1664
1726
|
throw err;
|
|
1665
1727
|
}
|
|
1666
1728
|
};
|
|
1667
|
-
const
|
|
1729
|
+
const claimRollback = async (name, file, leaseMs) => {
|
|
1730
|
+
await ensureIndex();
|
|
1731
|
+
const now = /* @__PURE__ */ new Date();
|
|
1732
|
+
const claimId = randomUUID();
|
|
1733
|
+
const claimed = await manager.findOneAndUpdate(
|
|
1734
|
+
JOURNAL_COLLECTION,
|
|
1735
|
+
{
|
|
1736
|
+
name,
|
|
1737
|
+
$or: [
|
|
1738
|
+
{ status: "applied" },
|
|
1739
|
+
{ status: "rolling_back", ...expiredLeaseFilter(now, new Date(Date.now() - leaseMs)) }
|
|
1740
|
+
]
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
$set: {
|
|
1744
|
+
status: "rolling_back",
|
|
1745
|
+
file,
|
|
1746
|
+
claimId,
|
|
1747
|
+
expiresAt: new Date(Date.now() + leaseMs)
|
|
1748
|
+
}
|
|
1749
|
+
},
|
|
1750
|
+
{ returnDocument: "before", cache: false }
|
|
1751
|
+
);
|
|
1752
|
+
return claimed === null ? null : claimId;
|
|
1753
|
+
};
|
|
1754
|
+
const renew = async (name, claimId, leaseMs) => {
|
|
1668
1755
|
await manager.updateOne(
|
|
1669
1756
|
JOURNAL_COLLECTION,
|
|
1670
|
-
{ name, status: "running" },
|
|
1671
|
-
{ $set: { expiresAt: new Date(Date.now() + leaseMs) } }
|
|
1757
|
+
{ name, claimId, status: { $in: ["running", "rolling_back"] } },
|
|
1758
|
+
{ $set: { expiresAt: new Date(Date.now() + leaseMs) } },
|
|
1759
|
+
{ cache: false }
|
|
1672
1760
|
);
|
|
1673
1761
|
};
|
|
1674
|
-
const markApplied = async (name) => {
|
|
1762
|
+
const markApplied = async (name, claimId) => {
|
|
1675
1763
|
const res = await manager.updateOne(
|
|
1676
1764
|
JOURNAL_COLLECTION,
|
|
1677
|
-
{ name, status: "running" },
|
|
1678
|
-
{ $set: { status: "applied" } }
|
|
1765
|
+
{ name, claimId, status: "running" },
|
|
1766
|
+
{ $set: { status: "applied" }, $unset: { expiresAt: "", claimId: "" } },
|
|
1767
|
+
{ cache: false }
|
|
1679
1768
|
);
|
|
1680
1769
|
if (res?.matchedCount === 0) {
|
|
1681
1770
|
await manager.insertOne(JOURNAL_COLLECTION, { name, appliedAt: /* @__PURE__ */ new Date(), status: "applied" }).catch(() => {
|
|
1682
1771
|
});
|
|
1772
|
+
throw new Error(`Migration "${name}" lost its claim before it could be marked applied`);
|
|
1683
1773
|
}
|
|
1684
1774
|
};
|
|
1685
|
-
const removeRunning = async (name) => {
|
|
1686
|
-
await manager.deleteOne(JOURNAL_COLLECTION, { name, status: "running" }).catch(() => {
|
|
1775
|
+
const removeRunning = async (name, claimId) => {
|
|
1776
|
+
await manager.deleteOne(JOURNAL_COLLECTION, { name, claimId, status: "running" }, { cache: false }).catch(() => {
|
|
1687
1777
|
});
|
|
1688
1778
|
};
|
|
1689
|
-
const unrecord = async (name) => {
|
|
1690
|
-
await manager.deleteOne(
|
|
1779
|
+
const unrecord = async (name, claimId) => {
|
|
1780
|
+
const result = await manager.deleteOne(
|
|
1781
|
+
JOURNAL_COLLECTION,
|
|
1782
|
+
{ name, claimId, status: "rolling_back" },
|
|
1783
|
+
{ cache: false }
|
|
1784
|
+
);
|
|
1785
|
+
if (result?.deletedCount === 0) {
|
|
1786
|
+
throw new Error(`Migration "${name}" lost its rollback claim`);
|
|
1787
|
+
}
|
|
1691
1788
|
};
|
|
1692
|
-
return { appliedNames, claim, renew, markApplied, removeRunning, unrecord };
|
|
1789
|
+
return { appliedNames, claim, claimRollback, renew, markApplied, removeRunning, unrecord };
|
|
1693
1790
|
};
|
|
1694
1791
|
|
|
1695
1792
|
// src/migrations/index.ts
|
|
@@ -1709,10 +1806,10 @@ var createMongoMigrationRunner = (service, options = {}) => {
|
|
|
1709
1806
|
}
|
|
1710
1807
|
return db;
|
|
1711
1808
|
};
|
|
1712
|
-
const startHeartbeat = (journal, name) => {
|
|
1809
|
+
const startHeartbeat = (journal, name, claimId) => {
|
|
1713
1810
|
const timer = setInterval(
|
|
1714
1811
|
() => {
|
|
1715
|
-
void journal.renew(name, leaseMs).catch(() => {
|
|
1812
|
+
void journal.renew(name, claimId, leaseMs).catch(() => {
|
|
1716
1813
|
});
|
|
1717
1814
|
},
|
|
1718
1815
|
Math.max(leaseMs / 2, 1e3)
|
|
@@ -1729,19 +1826,20 @@ var createMongoMigrationRunner = (service, options = {}) => {
|
|
|
1729
1826
|
for (const file of pending) {
|
|
1730
1827
|
const migration = await loadMigration(migrationDir, file);
|
|
1731
1828
|
logger.info({ name: migration.name }, "running migration up");
|
|
1732
|
-
|
|
1829
|
+
const claimId = await journal.claim(migration.name, basename(file), leaseMs);
|
|
1830
|
+
if (claimId === null) {
|
|
1733
1831
|
logger.info(
|
|
1734
1832
|
{ name: migration.name },
|
|
1735
1833
|
"migration claimed by another runner or already applied, skipping"
|
|
1736
1834
|
);
|
|
1737
1835
|
continue;
|
|
1738
1836
|
}
|
|
1739
|
-
const stopHeartbeat = startHeartbeat(journal, migration.name);
|
|
1837
|
+
const stopHeartbeat = startHeartbeat(journal, migration.name, claimId);
|
|
1740
1838
|
try {
|
|
1741
1839
|
await migration.up({ service, logger, name: migration.name });
|
|
1742
|
-
await journal.markApplied(migration.name);
|
|
1840
|
+
await journal.markApplied(migration.name, claimId);
|
|
1743
1841
|
} catch (err) {
|
|
1744
|
-
await journal.removeRunning(migration.name);
|
|
1842
|
+
await journal.removeRunning(migration.name, claimId);
|
|
1745
1843
|
throw err;
|
|
1746
1844
|
} finally {
|
|
1747
1845
|
stopHeartbeat();
|
|
@@ -1764,9 +1862,19 @@ var createMongoMigrationRunner = (service, options = {}) => {
|
|
|
1764
1862
|
const appliedFiles = files.filter((file) => applied.has(migrationNameOf(file))).reverse();
|
|
1765
1863
|
for (const file of appliedFiles) {
|
|
1766
1864
|
const migration = await loadMigration(migrationDir, file);
|
|
1865
|
+
const claimId = await journal.claimRollback(migration.name, basename(file), leaseMs);
|
|
1866
|
+
if (claimId === null) {
|
|
1867
|
+
logger.info({ name: migration.name }, "migration rollback already claimed, skipping");
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
const stopHeartbeat = startHeartbeat(journal, migration.name, claimId);
|
|
1767
1871
|
logger.info({ name: migration.name }, "running migration down");
|
|
1768
|
-
|
|
1769
|
-
|
|
1872
|
+
try {
|
|
1873
|
+
await migration.down({ service, logger, name: migration.name });
|
|
1874
|
+
await journal.unrecord(migration.name, claimId);
|
|
1875
|
+
} finally {
|
|
1876
|
+
stopHeartbeat();
|
|
1877
|
+
}
|
|
1770
1878
|
logger.info({ name: migration.name }, "migration rolled back");
|
|
1771
1879
|
if (targetName !== void 0 && migration.name === targetName) break;
|
|
1772
1880
|
}
|
|
@@ -1867,7 +1975,6 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
1867
1975
|
constructor(ctx) {
|
|
1868
1976
|
this.ctx = ctx;
|
|
1869
1977
|
}
|
|
1870
|
-
ctx;
|
|
1871
1978
|
_filter = {};
|
|
1872
1979
|
_sort;
|
|
1873
1980
|
_skip;
|
|
@@ -2308,6 +2415,16 @@ var toMongoValidator = (schema) => ({
|
|
|
2308
2415
|
});
|
|
2309
2416
|
|
|
2310
2417
|
// src/schema/types.ts
|
|
2418
|
+
var SUPPORTED_STRING_FORMATS = /* @__PURE__ */ new Set([
|
|
2419
|
+
"email",
|
|
2420
|
+
"uri",
|
|
2421
|
+
"date-time",
|
|
2422
|
+
"date",
|
|
2423
|
+
"uuid",
|
|
2424
|
+
"hostname",
|
|
2425
|
+
"ipv4",
|
|
2426
|
+
"ipv6"
|
|
2427
|
+
]);
|
|
2311
2428
|
var chain = (field) => {
|
|
2312
2429
|
const obj = field;
|
|
2313
2430
|
obj.optional = () => chain({ ...field, flags: { ...field.flags, optional: true } });
|
|
@@ -2321,6 +2438,11 @@ function objectField(properties, opts = {}) {
|
|
|
2321
2438
|
var s = {
|
|
2322
2439
|
/** UTF-8 string field (optional `format` / length / regex constraints). */
|
|
2323
2440
|
string(opts = {}) {
|
|
2441
|
+
if (opts.format !== void 0 && !SUPPORTED_STRING_FORMATS.has(opts.format)) {
|
|
2442
|
+
throw new Error(
|
|
2443
|
+
`Unsupported string format "${opts.format}"; supported: ${[...SUPPORTED_STRING_FORMATS].join(", ")}`
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2324
2446
|
return chain({ kind: "string", ...BASE, ...opts });
|
|
2325
2447
|
},
|
|
2326
2448
|
/** 32-bit integer field with optional bounds (min/max, exclusive, multipleOf). */
|
|
@@ -2486,6 +2608,200 @@ var checkReservedValue = (key, value, path, issues) => {
|
|
|
2486
2608
|
`Reserved field "${key}" must be ${types.join(" | ")}`
|
|
2487
2609
|
);
|
|
2488
2610
|
};
|
|
2611
|
+
var typeMatches = (value, type) => {
|
|
2612
|
+
if (type === "number") return typeof value === "number" || value instanceof BSON.Long;
|
|
2613
|
+
if (type === "int") {
|
|
2614
|
+
return value instanceof BSON.Int32 || typeof value === "number" && Number.isInteger(value);
|
|
2615
|
+
}
|
|
2616
|
+
if (type === "long") {
|
|
2617
|
+
return value instanceof BSON.Long || typeof value === "number" && Number.isInteger(value);
|
|
2618
|
+
}
|
|
2619
|
+
if (type === "double") return typeof value === "number" || value instanceof BSON.Double;
|
|
2620
|
+
if (type === "decimal") return value instanceof BSON.Decimal128;
|
|
2621
|
+
if (type === "object")
|
|
2622
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2623
|
+
if (type === "array") return Array.isArray(value);
|
|
2624
|
+
if (type === "string") return typeof value === "string";
|
|
2625
|
+
if (type === "bool" || type === "boolean") return typeof value === "boolean";
|
|
2626
|
+
if (type === "date") return value instanceof Date;
|
|
2627
|
+
if (type === "objectId") return value instanceof BSON.ObjectId;
|
|
2628
|
+
if (type === "null") return value === null;
|
|
2629
|
+
if (type === "binData") return value instanceof BSON.Binary;
|
|
2630
|
+
if (type === "regex") return value instanceof RegExp;
|
|
2631
|
+
return true;
|
|
2632
|
+
};
|
|
2633
|
+
var comparable = (value) => {
|
|
2634
|
+
if (typeof value === "number") return value;
|
|
2635
|
+
if (value instanceof BSON.Long || value instanceof BSON.Int32 || value instanceof BSON.Double) {
|
|
2636
|
+
return Number(value.toString());
|
|
2637
|
+
}
|
|
2638
|
+
if (value instanceof BSON.Decimal128) return Number(value.toString());
|
|
2639
|
+
return void 0;
|
|
2640
|
+
};
|
|
2641
|
+
var validateRawSchema = (schema, value, path, issues) => {
|
|
2642
|
+
const types = schema.bsonType === void 0 ? [] : [schema.bsonType].flat();
|
|
2643
|
+
if (types.length > 0 && !types.some((type) => typeMatches(value, type))) {
|
|
2644
|
+
push(issues, path, "type", types.join(" | "), "BSON type does not match raw schema");
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
if (schema.enum && !schema.enum.some(
|
|
2648
|
+
(candidate) => BSON.EJSON.stringify(candidate) === BSON.EJSON.stringify(value)
|
|
2649
|
+
)) {
|
|
2650
|
+
push(issues, path, "enum", "raw enum", "Value is not in the allowed set");
|
|
2651
|
+
}
|
|
2652
|
+
if (typeof value === "string") {
|
|
2653
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
2654
|
+
push(issues, path, "constraint", `minLength ${schema.minLength}`, "String is too short");
|
|
2655
|
+
}
|
|
2656
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
2657
|
+
push(issues, path, "constraint", `maxLength ${schema.maxLength}`, "String is too long");
|
|
2658
|
+
}
|
|
2659
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
|
|
2660
|
+
push(
|
|
2661
|
+
issues,
|
|
2662
|
+
path,
|
|
2663
|
+
"constraint",
|
|
2664
|
+
`pattern ${schema.pattern}`,
|
|
2665
|
+
"String does not match pattern"
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
const numeric = comparable(value);
|
|
2670
|
+
if (numeric !== void 0) {
|
|
2671
|
+
const minimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum;
|
|
2672
|
+
const maximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum;
|
|
2673
|
+
if (minimum !== void 0 && (numeric < minimum || schema.exclusiveMinimum === true && numeric === minimum)) {
|
|
2674
|
+
push(issues, path, "constraint", `minimum ${minimum}`, "Number is below minimum");
|
|
2675
|
+
}
|
|
2676
|
+
if (maximum !== void 0 && (numeric > maximum || schema.exclusiveMaximum === true && numeric === maximum)) {
|
|
2677
|
+
push(issues, path, "constraint", `maximum ${maximum}`, "Number is above maximum");
|
|
2678
|
+
}
|
|
2679
|
+
if (schema.multipleOf !== void 0 && Math.abs(numeric / schema.multipleOf - Math.round(numeric / schema.multipleOf)) > 1e-9) {
|
|
2680
|
+
push(
|
|
2681
|
+
issues,
|
|
2682
|
+
path,
|
|
2683
|
+
"constraint",
|
|
2684
|
+
`multipleOf ${schema.multipleOf}`,
|
|
2685
|
+
"Number is not a multiple"
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
if (Array.isArray(value)) {
|
|
2690
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
2691
|
+
push(issues, path, "constraint", `minItems ${schema.minItems}`, "Array is too short");
|
|
2692
|
+
}
|
|
2693
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
2694
|
+
push(issues, path, "constraint", `maxItems ${schema.maxItems}`, "Array is too long");
|
|
2695
|
+
}
|
|
2696
|
+
if (schema.uniqueItems) {
|
|
2697
|
+
const unique = new Set(value.map((item) => BSON.EJSON.stringify(item, { relaxed: false })));
|
|
2698
|
+
if (unique.size !== value.length) {
|
|
2699
|
+
push(issues, path, "constraint", "uniqueItems", "Array contains duplicate items");
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (Array.isArray(schema.items)) {
|
|
2703
|
+
schema.items.forEach((item, index) => {
|
|
2704
|
+
if (index < value.length)
|
|
2705
|
+
validateRawSchema(item, value[index], `${path}[${index}]`, issues);
|
|
2706
|
+
});
|
|
2707
|
+
} else if (schema.items) {
|
|
2708
|
+
value.forEach((item, index) => {
|
|
2709
|
+
validateRawSchema(schema.items, item, `${path}[${index}]`, issues);
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
2714
|
+
const object = value;
|
|
2715
|
+
for (const required of schema.required ?? []) {
|
|
2716
|
+
if (object[required] === void 0)
|
|
2717
|
+
push(issues, `${path}.${required}`, "missing", "required", "Required field is missing");
|
|
2718
|
+
}
|
|
2719
|
+
for (const [key, child] of Object.entries(schema.properties ?? {})) {
|
|
2720
|
+
if (key in object)
|
|
2721
|
+
validateRawSchema(child, object[key], path ? `${path}.${key}` : key, issues);
|
|
2722
|
+
}
|
|
2723
|
+
for (const [pattern, child] of Object.entries(schema.patternProperties ?? {})) {
|
|
2724
|
+
for (const key of Object.keys(object)) {
|
|
2725
|
+
if (new RegExp(pattern).test(key))
|
|
2726
|
+
validateRawSchema(child, object[key], `${path}.${key}`, issues);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (schema.additionalProperties === false) {
|
|
2730
|
+
const known = /* @__PURE__ */ new Set([
|
|
2731
|
+
...Object.keys(schema.properties ?? {}),
|
|
2732
|
+
...Object.keys(object).filter(
|
|
2733
|
+
(key) => Object.keys(schema.patternProperties ?? {}).some(
|
|
2734
|
+
(pattern) => new RegExp(pattern).test(key)
|
|
2735
|
+
)
|
|
2736
|
+
)
|
|
2737
|
+
]);
|
|
2738
|
+
for (const key of Object.keys(object)) {
|
|
2739
|
+
if (!known.has(key))
|
|
2740
|
+
push(
|
|
2741
|
+
issues,
|
|
2742
|
+
`${path}.${key}`,
|
|
2743
|
+
"unknown_key",
|
|
2744
|
+
"no additional properties",
|
|
2745
|
+
"Unexpected field"
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
} else if (typeof schema.additionalProperties === "object") {
|
|
2749
|
+
for (const key of Object.keys(object)) {
|
|
2750
|
+
if (!(key in (schema.properties ?? {})))
|
|
2751
|
+
validateRawSchema(schema.additionalProperties, object[key], `${path}.${key}`, issues);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
const count = Object.keys(object).length;
|
|
2755
|
+
if (schema.minProperties !== void 0 && count < schema.minProperties)
|
|
2756
|
+
push(
|
|
2757
|
+
issues,
|
|
2758
|
+
path,
|
|
2759
|
+
"constraint",
|
|
2760
|
+
`minProperties ${schema.minProperties}`,
|
|
2761
|
+
"Too few properties"
|
|
2762
|
+
);
|
|
2763
|
+
if (schema.maxProperties !== void 0 && count > schema.maxProperties)
|
|
2764
|
+
push(
|
|
2765
|
+
issues,
|
|
2766
|
+
path,
|
|
2767
|
+
"constraint",
|
|
2768
|
+
`maxProperties ${schema.maxProperties}`,
|
|
2769
|
+
"Too many properties"
|
|
2770
|
+
);
|
|
2771
|
+
for (const [key, dependency] of Object.entries(schema.dependencies ?? {})) {
|
|
2772
|
+
if (!(key in object)) continue;
|
|
2773
|
+
if (Array.isArray(dependency)) {
|
|
2774
|
+
for (const required of dependency)
|
|
2775
|
+
if (!(required in object))
|
|
2776
|
+
push(issues, path, "constraint", "dependencies", `${key} requires ${required}`);
|
|
2777
|
+
} else validateRawSchema(dependency, object, path, issues);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
for (const child of schema.allOf ?? []) validateRawSchema(child, value, path, issues);
|
|
2781
|
+
if (schema.anyOf) {
|
|
2782
|
+
const matches = schema.anyOf.some((child) => {
|
|
2783
|
+
const candidate = [];
|
|
2784
|
+
validateRawSchema(child, value, path, candidate);
|
|
2785
|
+
return candidate.length === 0;
|
|
2786
|
+
});
|
|
2787
|
+
if (!matches) push(issues, path, "constraint", "anyOf", "No anyOf schema matched");
|
|
2788
|
+
}
|
|
2789
|
+
if (schema.oneOf) {
|
|
2790
|
+
const matches = schema.oneOf.filter((child) => {
|
|
2791
|
+
const candidate = [];
|
|
2792
|
+
validateRawSchema(child, value, path, candidate);
|
|
2793
|
+
return candidate.length === 0;
|
|
2794
|
+
}).length;
|
|
2795
|
+
if (matches !== 1)
|
|
2796
|
+
push(issues, path, "constraint", "oneOf", "Exactly one oneOf schema must match");
|
|
2797
|
+
}
|
|
2798
|
+
if (schema.not) {
|
|
2799
|
+
const candidate = [];
|
|
2800
|
+
validateRawSchema(schema.not, value, path, candidate);
|
|
2801
|
+
if (candidate.length === 0)
|
|
2802
|
+
push(issues, path, "constraint", "not", "Value matched a forbidden schema");
|
|
2803
|
+
}
|
|
2804
|
+
};
|
|
2489
2805
|
|
|
2490
2806
|
// src/schema/validate-doc/field-validators.ts
|
|
2491
2807
|
var patternCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -2501,6 +2817,22 @@ var compiledPattern = (field) => {
|
|
|
2501
2817
|
return void 0;
|
|
2502
2818
|
}
|
|
2503
2819
|
};
|
|
2820
|
+
var STRING_FORMAT_VALIDATORS = {
|
|
2821
|
+
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
|
|
2822
|
+
uri: (value) => {
|
|
2823
|
+
try {
|
|
2824
|
+
return new URL(value).protocol.length > 0;
|
|
2825
|
+
} catch {
|
|
2826
|
+
return false;
|
|
2827
|
+
}
|
|
2828
|
+
},
|
|
2829
|
+
"date-time": (value) => /^\d{4}-\d{2}-\d{2}T/.test(value) && !Number.isNaN(Date.parse(value)),
|
|
2830
|
+
date: (value) => /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`)),
|
|
2831
|
+
uuid: (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value),
|
|
2832
|
+
hostname: (value) => /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(value),
|
|
2833
|
+
ipv4: (value) => isIP(value) === 4,
|
|
2834
|
+
ipv6: (value) => isIP(value) === 6
|
|
2835
|
+
};
|
|
2504
2836
|
var stringValidator = (_deps, field, value, path, issues) => {
|
|
2505
2837
|
const f = field;
|
|
2506
2838
|
if (typeof value !== "string") {
|
|
@@ -2535,6 +2867,16 @@ var stringValidator = (_deps, field, value, path, issues) => {
|
|
|
2535
2867
|
`"${value}" does not match pattern`
|
|
2536
2868
|
);
|
|
2537
2869
|
}
|
|
2870
|
+
const format = f.format ? STRING_FORMAT_VALIDATORS[f.format] : void 0;
|
|
2871
|
+
if (format && !format(value)) {
|
|
2872
|
+
push(
|
|
2873
|
+
issues,
|
|
2874
|
+
path,
|
|
2875
|
+
"constraint",
|
|
2876
|
+
`string(format ${f.format})`,
|
|
2877
|
+
`"${value}" is not a valid ${f.format}`
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2538
2880
|
};
|
|
2539
2881
|
var numberValidator = (_deps, field, value, path, issues) => {
|
|
2540
2882
|
const f = field;
|
|
@@ -2605,7 +2947,8 @@ var nullValidator = (_deps, _field, value, path, issues) => {
|
|
|
2605
2947
|
};
|
|
2606
2948
|
var anyValidator = () => {
|
|
2607
2949
|
};
|
|
2608
|
-
var rawValidator = () => {
|
|
2950
|
+
var rawValidator = (_deps, field, value, path, issues) => {
|
|
2951
|
+
validateRawSchema(field.fragment, value, path, issues);
|
|
2609
2952
|
};
|
|
2610
2953
|
var enumValidator = (_deps, field, value, path, issues) => {
|
|
2611
2954
|
const f = field;
|
|
@@ -2875,13 +3218,13 @@ var CacheInvalidator = class {
|
|
|
2875
3218
|
connectedAt = Date.now();
|
|
2876
3219
|
if (backoff > 0) {
|
|
2877
3220
|
this.cache.invalidateByCollection(
|
|
2878
|
-
cacheCollectionKey(ref.db.databaseName, ref.collection)
|
|
3221
|
+
cacheCollectionKey(ref.db.databaseName, ref.collection, ref.namespace)
|
|
2879
3222
|
);
|
|
2880
3223
|
}
|
|
2881
3224
|
const iterator = stream[Symbol.asyncIterator]();
|
|
2882
3225
|
for await (const _change of iterator) {
|
|
2883
3226
|
this.cache.invalidateByCollection(
|
|
2884
|
-
cacheCollectionKey(ref.db.databaseName, ref.collection)
|
|
3227
|
+
cacheCollectionKey(ref.db.databaseName, ref.collection, ref.namespace)
|
|
2885
3228
|
);
|
|
2886
3229
|
}
|
|
2887
3230
|
} catch (err) {
|
|
@@ -2998,7 +3341,8 @@ var makeGetDbUrl = (dbClients, config) => (name) => {
|
|
|
2998
3341
|
};
|
|
2999
3342
|
var makeConnectionFactory = (dbClients, db, getDbUrl, buildManager2, openedClients = /* @__PURE__ */ new Map()) => {
|
|
3000
3343
|
const connecting = /* @__PURE__ */ new Map();
|
|
3001
|
-
|
|
3344
|
+
let transition = Promise.resolve();
|
|
3345
|
+
const connect = async (names) => {
|
|
3002
3346
|
const keys = names && names.length > 0 ? names : Object.keys(dbClients);
|
|
3003
3347
|
const openedThisCall = [];
|
|
3004
3348
|
const assignedThisCall = [];
|
|
@@ -3016,6 +3360,7 @@ var makeConnectionFactory = (dbClients, db, getDbUrl, buildManager2, openedClien
|
|
|
3016
3360
|
const c = new MongoClient(dbUrl, {
|
|
3017
3361
|
connectTimeoutMS: definition.connectTimeoutMs ?? 1e4,
|
|
3018
3362
|
serverSelectionTimeoutMS: definition.connectTimeoutMs ?? 1e4,
|
|
3363
|
+
monitorCommands: definition.monitorCommands ?? false,
|
|
3019
3364
|
...definition.readPreference && { readPreference: definition.readPreference },
|
|
3020
3365
|
...definition.readConcern && { readConcern: definition.readConcern },
|
|
3021
3366
|
...definition.writeConcern && { writeConcern: definition.writeConcern }
|
|
@@ -3046,18 +3391,20 @@ var makeConnectionFactory = (dbClients, db, getDbUrl, buildManager2, openedClien
|
|
|
3046
3391
|
throw err;
|
|
3047
3392
|
}
|
|
3048
3393
|
};
|
|
3394
|
+
return (names) => {
|
|
3395
|
+
const result = transition.then(() => connect(names));
|
|
3396
|
+
transition = result.catch(() => {
|
|
3397
|
+
});
|
|
3398
|
+
return result;
|
|
3399
|
+
};
|
|
3049
3400
|
};
|
|
3050
3401
|
var closeConnections = async (openedClients) => {
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
});
|
|
3055
|
-
} catch {
|
|
3056
|
-
return Promise.resolve();
|
|
3057
|
-
}
|
|
3058
|
-
});
|
|
3059
|
-
await Promise.allSettled(closes);
|
|
3402
|
+
const results = await Promise.allSettled(
|
|
3403
|
+
[...openedClients.values()].map((client) => client.close())
|
|
3404
|
+
);
|
|
3060
3405
|
openedClients.clear();
|
|
3406
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
3407
|
+
if (errors.length > 0) throw new AggregateError(errors, "Failed to close MongoDB connections");
|
|
3061
3408
|
};
|
|
3062
3409
|
|
|
3063
3410
|
// src/service/health.ts
|
|
@@ -3099,9 +3446,19 @@ var createForeignKeyLoader = (deps2, collection, foreignField, options = {}) =>
|
|
|
3099
3446
|
collection,
|
|
3100
3447
|
{ [foreignField]: { $in: keys } },
|
|
3101
3448
|
{
|
|
3102
|
-
|
|
3449
|
+
limit: options.maxDocs !== void 0 ? options.maxDocs + 1 : 0,
|
|
3450
|
+
...options.session !== void 0 ? { session: options.session } : {},
|
|
3451
|
+
...options.maxTimeMS !== void 0 ? { maxTimeMS: options.maxTimeMS } : {},
|
|
3452
|
+
...options.readPreference !== void 0 ? { readPreference: options.readPreference } : {},
|
|
3453
|
+
...options.readConcern !== void 0 ? { readConcern: options.readConcern } : {},
|
|
3454
|
+
...options.queryCache !== void 0 ? { queryCache: options.queryCache } : {}
|
|
3103
3455
|
}
|
|
3104
3456
|
);
|
|
3457
|
+
if (options.maxDocs !== void 0 && docs.length > options.maxDocs) {
|
|
3458
|
+
throw new BadRequest(
|
|
3459
|
+
`populate: ${collection}.${foreignField} exceeded maxDocs ${options.maxDocs}; raise the cap or narrow the relation`
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3105
3462
|
const index = /* @__PURE__ */ new Map();
|
|
3106
3463
|
for (const doc of docs) {
|
|
3107
3464
|
const value = doc[foreignField];
|
|
@@ -3264,8 +3621,11 @@ var traceDbOp = async (logger, meta, fn, options = {}) => {
|
|
|
3264
3621
|
};
|
|
3265
3622
|
|
|
3266
3623
|
// src/service/op-deps.ts
|
|
3267
|
-
var makeOpDeps = (logger, dbLabel, resolve, wrapMongoErrors) => {
|
|
3268
|
-
const trace = (meta2, fn) => traceDbOp(logger, meta2, fn, {
|
|
3624
|
+
var makeOpDeps = (logger, dbLabel, resolve, wrapMongoErrors, debugQuery) => {
|
|
3625
|
+
const trace = (meta2, fn) => traceDbOp(logger, meta2, fn, {
|
|
3626
|
+
wrapMongoErrors,
|
|
3627
|
+
...debugQuery !== void 0 ? { debugQuery } : {}
|
|
3628
|
+
});
|
|
3269
3629
|
const meta = (collection, op, params) => ({
|
|
3270
3630
|
collection: String(collection),
|
|
3271
3631
|
physicalCollection: resolve(String(collection)),
|
|
@@ -3335,8 +3695,6 @@ var PipelineBuilder = class _PipelineBuilder {
|
|
|
3335
3695
|
this.deps = deps2;
|
|
3336
3696
|
this.stages = stages;
|
|
3337
3697
|
}
|
|
3338
|
-
deps;
|
|
3339
|
-
stages;
|
|
3340
3698
|
/** Raw accumulated stages — embeddable into `$lookup` / `$facet` branches. */
|
|
3341
3699
|
raw() {
|
|
3342
3700
|
return this.stages;
|
|
@@ -3669,20 +4027,20 @@ var createCachedAggregate = (ctx) => {
|
|
|
3669
4027
|
const shouldDedupe = resolved.dedupe !== false && (resolved.dedupe === true || opts.dedupeReads === true && noSession) && opts.inFlight !== void 0;
|
|
3670
4028
|
const runDriver = () => defineCrudOp(deps2, collection, opName, execute, options, false, { pipeline });
|
|
3671
4029
|
const key = useCache ? cache.key(
|
|
3672
|
-
cacheCollectionKey(client.databaseName, physical),
|
|
4030
|
+
cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace),
|
|
3673
4031
|
stableHash([opName, pipeline, options])
|
|
3674
4032
|
) : void 0;
|
|
3675
4033
|
const toSourceKeys = () => collectAggSources(String(collection), pipeline, sources).map((name) => {
|
|
3676
4034
|
try {
|
|
3677
|
-
return cacheCollectionKey(client.databaseName, resolve(name));
|
|
4035
|
+
return cacheCollectionKey(client.databaseName, resolve(name), opts.cacheNamespace);
|
|
3678
4036
|
} catch {
|
|
3679
|
-
return cacheCollectionKey(client.databaseName, name);
|
|
4037
|
+
return cacheCollectionKey(client.databaseName, name, opts.cacheNamespace);
|
|
3680
4038
|
}
|
|
3681
4039
|
});
|
|
3682
4040
|
const runOnce = () => {
|
|
3683
4041
|
if (useCache && key) {
|
|
3684
4042
|
const sourceKeys = toSourceKeys();
|
|
3685
|
-
const primaryKey = cacheCollectionKey(client.databaseName, physical);
|
|
4043
|
+
const primaryKey = cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace);
|
|
3686
4044
|
const versions = { [primaryKey]: cache.versionOf(primaryKey) };
|
|
3687
4045
|
for (const col of sourceKeys) {
|
|
3688
4046
|
if (!(col in versions)) versions[col] = cache.versionOf(col);
|
|
@@ -3697,8 +4055,8 @@ var createCachedAggregate = (ctx) => {
|
|
|
3697
4055
|
return runDriver();
|
|
3698
4056
|
};
|
|
3699
4057
|
if (shouldDedupe) {
|
|
3700
|
-
const primaryKey = cacheCollectionKey(client.databaseName, physical);
|
|
3701
|
-
const gen = cache !== void 0 ? cache.versionOf(
|
|
4058
|
+
const primaryKey = cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace);
|
|
4059
|
+
const gen = cache !== void 0 ? toSourceKeys().map((source) => `${source}:${cache.versionOf(source)}`).join("|") : "";
|
|
3702
4060
|
const dedupeKey = `${primaryKey}|${gen}|${opName}|${stableHash([pipeline, options])}`;
|
|
3703
4061
|
return await opts.inFlight.run(dedupeKey, runOnce);
|
|
3704
4062
|
}
|
|
@@ -3907,7 +4265,13 @@ var makeTextSearchOp = (ctx) => {
|
|
|
3907
4265
|
// src/service/aggregation/index.ts
|
|
3908
4266
|
var makeAggregationOps = (client, dbLabel, logger, opts) => {
|
|
3909
4267
|
const resolve = opts.resolveCollectionName;
|
|
3910
|
-
const deps2 = makeOpDeps(
|
|
4268
|
+
const deps2 = makeOpDeps(
|
|
4269
|
+
logger,
|
|
4270
|
+
dbLabel,
|
|
4271
|
+
resolve,
|
|
4272
|
+
opts.wrapMongoErrors === true,
|
|
4273
|
+
opts.debugQuery
|
|
4274
|
+
);
|
|
3911
4275
|
const coll = (collection) => client.collection(resolve(String(collection)));
|
|
3912
4276
|
const ctx = {
|
|
3913
4277
|
resolve,
|
|
@@ -3925,11 +4289,7 @@ var makeAggregationOps = (client, dbLabel, logger, opts) => {
|
|
|
3925
4289
|
};
|
|
3926
4290
|
|
|
3927
4291
|
// src/schema/apply-defaults.ts
|
|
3928
|
-
var cloneDefault = (value) =>
|
|
3929
|
-
if (value === null || typeof value !== "object") return value;
|
|
3930
|
-
if (typeof structuredClone === "function") return structuredClone(value);
|
|
3931
|
-
return Array.isArray(value) ? [...value] : { ...value };
|
|
3932
|
-
};
|
|
4292
|
+
var cloneDefault = (value) => cloneDeep(value);
|
|
3933
4293
|
var applySchemaDefaults = (schema, doc) => {
|
|
3934
4294
|
if (!schema || doc === null || typeof doc !== "object" || Array.isArray(doc)) return;
|
|
3935
4295
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
@@ -3978,7 +4338,9 @@ var checkDocsDrift = (options, collection, op, docs) => {
|
|
|
3978
4338
|
const schema = options.getSchema?.(collection);
|
|
3979
4339
|
if (!schema) return;
|
|
3980
4340
|
for (const doc of docs) {
|
|
3981
|
-
const issues = validateDoc(schema, doc)
|
|
4341
|
+
const issues = validateDoc(schema, doc).filter(
|
|
4342
|
+
(issue) => options.partial !== true || issue.code !== "missing"
|
|
4343
|
+
);
|
|
3982
4344
|
if (issues.length === 0) continue;
|
|
3983
4345
|
const detail = issues.map((i) => `${i.path}: ${i.message}`).join("; ");
|
|
3984
4346
|
const extra = { collection, db: options.db, op, issues };
|
|
@@ -4012,7 +4374,8 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4012
4374
|
logger,
|
|
4013
4375
|
dbLabel,
|
|
4014
4376
|
resolve,
|
|
4015
|
-
opts.wrapMongoErrors === true
|
|
4377
|
+
opts.wrapMongoErrors === true,
|
|
4378
|
+
opts.debugQuery
|
|
4016
4379
|
);
|
|
4017
4380
|
const coll = (name) => client.collection(resolve(String(name)));
|
|
4018
4381
|
const toDriverFilter = (filter) => filter;
|
|
@@ -4020,7 +4383,7 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4020
4383
|
const writeRun = (collection, opName, execute, options, params) => defineCrudOp(deps2, collection, opName, execute, options, true, params);
|
|
4021
4384
|
const invalidate = (collection) => {
|
|
4022
4385
|
opts.cache?.invalidateByCollection(
|
|
4023
|
-
cacheCollectionKey(client.databaseName, resolve(String(collection)))
|
|
4386
|
+
cacheCollectionKey(client.databaseName, resolve(String(collection)), opts.cacheNamespace)
|
|
4024
4387
|
);
|
|
4025
4388
|
};
|
|
4026
4389
|
const timestampsFor = (collection) => opts.timestamps?.[String(collection)];
|
|
@@ -4050,6 +4413,26 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4050
4413
|
if (!ts?.updatedAt) return doc;
|
|
4051
4414
|
return { ...doc, [ts.updatedAt]: /* @__PURE__ */ new Date() };
|
|
4052
4415
|
};
|
|
4416
|
+
const validateWrite = (collection, doc, generateId = true) => {
|
|
4417
|
+
if (opts.validateWrites === false) return;
|
|
4418
|
+
const schema = opts.getSchema?.(String(collection));
|
|
4419
|
+
if (!schema) return;
|
|
4420
|
+
if (generateId && doc._id === void 0 && schema.properties._id?.kind === "objectId") {
|
|
4421
|
+
doc._id = new ObjectId();
|
|
4422
|
+
}
|
|
4423
|
+
const issues = validateDoc(schema, doc).filter(
|
|
4424
|
+
(issue) => generateId || issue.path !== "_id" || issue.code !== "missing"
|
|
4425
|
+
);
|
|
4426
|
+
if (issues.length > 0) {
|
|
4427
|
+
throw new DomainError("VALIDATION_FAILED", "Document failed schema validation", {
|
|
4428
|
+
db: client.databaseName,
|
|
4429
|
+
collection: String(collection),
|
|
4430
|
+
...doc._id !== void 0 ? { documentId: doc._id } : {},
|
|
4431
|
+
fields: [...new Set(issues.map((issue) => issue.path))],
|
|
4432
|
+
issues
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
};
|
|
4053
4436
|
const read = async (collection, opName, filter, options, execute, shape = "none", keyExtra) => {
|
|
4054
4437
|
const physical = resolve(String(collection));
|
|
4055
4438
|
const resolved = resolveQueryOptions(options);
|
|
@@ -4062,14 +4445,15 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4062
4445
|
const checkDrift = (value) => {
|
|
4063
4446
|
const mode = resolveDriftMode(resolved.sdk.drift, opts.drift);
|
|
4064
4447
|
if (mode === "off") return;
|
|
4065
|
-
|
|
4448
|
+
const partial = resolved.driverOpts.projection !== void 0;
|
|
4066
4449
|
const docs = shape === "many" ? value : shape === "one" && value !== null ? [value] : [];
|
|
4067
4450
|
checkDocsDrift(
|
|
4068
4451
|
{
|
|
4069
4452
|
logger,
|
|
4070
4453
|
db: dbLabel,
|
|
4071
4454
|
drift: mode,
|
|
4072
|
-
...opts.getSchema !== void 0 ? { getSchema: opts.getSchema } : {}
|
|
4455
|
+
...opts.getSchema !== void 0 ? { getSchema: opts.getSchema } : {},
|
|
4456
|
+
partial
|
|
4073
4457
|
},
|
|
4074
4458
|
String(collection),
|
|
4075
4459
|
opName,
|
|
@@ -4098,14 +4482,17 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4098
4482
|
}
|
|
4099
4483
|
return payloadHash;
|
|
4100
4484
|
};
|
|
4101
|
-
const key = useCache ? opts.cache.key(
|
|
4485
|
+
const key = useCache ? opts.cache.key(
|
|
4486
|
+
cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace),
|
|
4487
|
+
getPayloadHash()
|
|
4488
|
+
) : void 0;
|
|
4102
4489
|
const fetchAndCheck = () => executeWrapped().then((value) => {
|
|
4103
4490
|
checkDrift(value);
|
|
4104
4491
|
return value;
|
|
4105
4492
|
});
|
|
4106
4493
|
const runOnce = () => {
|
|
4107
4494
|
if (useCache && key) {
|
|
4108
|
-
const colKey = cacheCollectionKey(client.databaseName, physical);
|
|
4495
|
+
const colKey = cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace);
|
|
4109
4496
|
const versions = { [colKey]: opts.cache.versionOf(colKey) };
|
|
4110
4497
|
const hit = opts.cache.get(key);
|
|
4111
4498
|
if (hit !== void 0) return Promise.resolve(hit);
|
|
@@ -4117,7 +4504,7 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4117
4504
|
return fetchAndCheck();
|
|
4118
4505
|
};
|
|
4119
4506
|
if (shouldDedupe) {
|
|
4120
|
-
const colKey = cacheCollectionKey(client.databaseName, physical);
|
|
4507
|
+
const colKey = cacheCollectionKey(client.databaseName, physical, opts.cacheNamespace);
|
|
4121
4508
|
const gen = opts.cache !== void 0 ? opts.cache.versionOf(colKey) : "";
|
|
4122
4509
|
return await opts.inFlight.run(`${colKey}|${gen}|${getPayloadHash()}`, runOnce);
|
|
4123
4510
|
}
|
|
@@ -4147,6 +4534,7 @@ var createCrudContext = (client, dbLabel, logger, opts) => {
|
|
|
4147
4534
|
stampCreate,
|
|
4148
4535
|
stampUpdate,
|
|
4149
4536
|
stampReplace,
|
|
4537
|
+
validateWrite,
|
|
4150
4538
|
read,
|
|
4151
4539
|
normalizeFindOptions
|
|
4152
4540
|
};
|
|
@@ -4371,7 +4759,16 @@ var makeWatchOps = (ctx) => {
|
|
|
4371
4759
|
// src/service/crud/write-ops.ts
|
|
4372
4760
|
var makeWriteOps = (ctx, readOps) => {
|
|
4373
4761
|
const { opts } = ctx;
|
|
4374
|
-
const {
|
|
4762
|
+
const {
|
|
4763
|
+
coll,
|
|
4764
|
+
toDriverFilter,
|
|
4765
|
+
writeRun,
|
|
4766
|
+
invalidate,
|
|
4767
|
+
stampCreate,
|
|
4768
|
+
stampUpdate,
|
|
4769
|
+
stampReplace,
|
|
4770
|
+
validateWrite
|
|
4771
|
+
} = ctx;
|
|
4375
4772
|
const { getOne } = readOps;
|
|
4376
4773
|
const safeAfterHooks = async (collection, name, hookCtx) => {
|
|
4377
4774
|
try {
|
|
@@ -4393,6 +4790,7 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4393
4790
|
doc
|
|
4394
4791
|
});
|
|
4395
4792
|
stampCreate(collection, doc);
|
|
4793
|
+
validateWrite(collection, doc);
|
|
4396
4794
|
const result = await writeRun(
|
|
4397
4795
|
collection,
|
|
4398
4796
|
"mongo.insertOne",
|
|
@@ -4426,7 +4824,10 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4426
4824
|
collection: String(collection),
|
|
4427
4825
|
docs
|
|
4428
4826
|
});
|
|
4429
|
-
for (const d of docs)
|
|
4827
|
+
for (const d of docs) {
|
|
4828
|
+
stampCreate(collection, d);
|
|
4829
|
+
validateWrite(collection, d);
|
|
4830
|
+
}
|
|
4430
4831
|
const result = await writeRun(
|
|
4431
4832
|
collection,
|
|
4432
4833
|
"mongo.insertMany",
|
|
@@ -4550,6 +4951,7 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4550
4951
|
filter
|
|
4551
4952
|
});
|
|
4552
4953
|
const effective = stampReplace(collection, replacement);
|
|
4954
|
+
validateWrite(collection, effective, false);
|
|
4553
4955
|
const result = await writeRun(
|
|
4554
4956
|
collection,
|
|
4555
4957
|
"mongo.findOneAndReplace",
|
|
@@ -4583,6 +4985,8 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4583
4985
|
collection: String(collection),
|
|
4584
4986
|
filter
|
|
4585
4987
|
});
|
|
4988
|
+
const effective = stampReplace(collection, replacement);
|
|
4989
|
+
validateWrite(collection, effective, false);
|
|
4586
4990
|
const result = await writeRun(
|
|
4587
4991
|
collection,
|
|
4588
4992
|
"mongo.replaceOne",
|
|
@@ -4593,7 +4997,7 @@ var makeWriteOps = (ctx, readOps) => {
|
|
|
4593
4997
|
};
|
|
4594
4998
|
return coll(collection).replaceOne(
|
|
4595
4999
|
toDriverFilter(filter),
|
|
4596
|
-
|
|
5000
|
+
effective,
|
|
4597
5001
|
repOpts
|
|
4598
5002
|
);
|
|
4599
5003
|
},
|
|
@@ -4849,6 +5253,7 @@ var makeCrudOps = (client, dbLabel, logger, opts) => {
|
|
|
4849
5253
|
...makeWatchOps(ctx)
|
|
4850
5254
|
};
|
|
4851
5255
|
};
|
|
5256
|
+
var { EJSON: EJSON2 } = BSON;
|
|
4852
5257
|
var tag = (value) => {
|
|
4853
5258
|
if (value instanceof ObjectId) return { t: "oid", v: value.toHexString() };
|
|
4854
5259
|
if (value instanceof Date) return { t: "date", v: value.toISOString() };
|
|
@@ -4856,6 +5261,9 @@ var tag = (value) => {
|
|
|
4856
5261
|
if (typeof value === "number") return { t: "number", v: value };
|
|
4857
5262
|
if (typeof value === "boolean") return { t: "boolean", v: value };
|
|
4858
5263
|
if (value === null || value === void 0) return { t: "null" };
|
|
5264
|
+
if (typeof value === "object" && "_bsontype" in value) {
|
|
5265
|
+
return { t: "bson", v: EJSON2.stringify(value, { relaxed: false }) };
|
|
5266
|
+
}
|
|
4859
5267
|
throw new BadRequest(
|
|
4860
5268
|
`paginateCursor: unsupported sort value type "${typeof value}" \u2014 key values must be ObjectId, Date, string, number, boolean, or null`
|
|
4861
5269
|
);
|
|
@@ -4889,6 +5297,11 @@ var untag = (tagged) => {
|
|
|
4889
5297
|
return tagged.v;
|
|
4890
5298
|
case "null":
|
|
4891
5299
|
return null;
|
|
5300
|
+
case "bson":
|
|
5301
|
+
if (typeof tagged.v !== "string") {
|
|
5302
|
+
throw new BadRequest("paginateCursor: invalid cursor \u2014 BSON value must be a string");
|
|
5303
|
+
}
|
|
5304
|
+
return EJSON2.parse(tagged.v, { relaxed: false });
|
|
4892
5305
|
}
|
|
4893
5306
|
};
|
|
4894
5307
|
var encodeCursor = (cursor) => Buffer.from(
|
|
@@ -4933,11 +5346,17 @@ var makePaginateCursor = (ctx) => {
|
|
|
4933
5346
|
const { client, dbLabel, logger, opts, resolve, deps: deps2 } = ctx;
|
|
4934
5347
|
const paginateCursor = async (collection, baseFilter, config) => {
|
|
4935
5348
|
const sortKeys = Object.entries(config.sort);
|
|
5349
|
+
const uniqueTiebreaker = config.uniqueTiebreaker ?? "_id";
|
|
4936
5350
|
if (sortKeys.length === 0) {
|
|
4937
5351
|
throw new BadRequest(
|
|
4938
5352
|
"paginateCursor: `sort` is required (include a unique tiebreaker like `_id`)"
|
|
4939
5353
|
);
|
|
4940
5354
|
}
|
|
5355
|
+
if (sortKeys.at(-1)?.[0] !== uniqueTiebreaker) {
|
|
5356
|
+
throw new BadRequest(
|
|
5357
|
+
`paginateCursor: the final sort key must be the unique tiebreaker "${uniqueTiebreaker}"`
|
|
5358
|
+
);
|
|
5359
|
+
}
|
|
4941
5360
|
if (config.before !== void 0 && config.after !== void 0) {
|
|
4942
5361
|
throw new BadRequest("paginateCursor: provide either `after` or `before`, not both");
|
|
4943
5362
|
}
|
|
@@ -4993,10 +5412,15 @@ var makePaginateCursor = (ctx) => {
|
|
|
4993
5412
|
"mongo.paginateCursor",
|
|
4994
5413
|
page
|
|
4995
5414
|
);
|
|
4996
|
-
const boundary = backwards ?
|
|
5415
|
+
const boundary = backwards ? page[0] : page[page.length - 1];
|
|
4997
5416
|
let nextCursor = null;
|
|
4998
5417
|
if (hasMore && boundary !== void 0) {
|
|
4999
|
-
const values = sortKeys.map(
|
|
5418
|
+
const values = sortKeys.map(
|
|
5419
|
+
([f]) => f.split(".").reduce(
|
|
5420
|
+
(value, part) => value !== null && typeof value === "object" ? value[part] : void 0,
|
|
5421
|
+
boundary
|
|
5422
|
+
)
|
|
5423
|
+
);
|
|
5000
5424
|
if (values.some((v) => v === void 0)) {
|
|
5001
5425
|
throw new BadRequest(
|
|
5002
5426
|
"paginateCursor: boundary document is missing a sort field \u2014 every sort key must exist on every document for keyset pagination"
|
|
@@ -5023,6 +5447,12 @@ var makePaginateFlexible = (ctx) => {
|
|
|
5023
5447
|
// APIs used to disagree silently (10 vs 100).
|
|
5024
5448
|
{ limit: DEFAULT_FIND_LIMIT }
|
|
5025
5449
|
);
|
|
5450
|
+
const maxOffset = config.maxOffset ?? 1e4;
|
|
5451
|
+
if (normalized.offset > maxOffset) {
|
|
5452
|
+
throw new BadRequest(
|
|
5453
|
+
`paginateFlexible: offset ${normalized.offset} exceeds maxOffset ${maxOffset}; use paginateCursor for deep pages`
|
|
5454
|
+
);
|
|
5455
|
+
}
|
|
5026
5456
|
const dataStages = [
|
|
5027
5457
|
...config.prePipeline ?? [],
|
|
5028
5458
|
...config.sort && Object.keys(config.sort).length > 0 ? [{ $sort: config.sort }] : [],
|
|
@@ -5057,19 +5487,18 @@ var makePaginateFlexible = (ctx) => {
|
|
|
5057
5487
|
const row = rows[0];
|
|
5058
5488
|
const data = row?.data ?? [];
|
|
5059
5489
|
const totalCount = row?.totalCount?.[0]?.count ?? 0;
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
}
|
|
5490
|
+
checkDocsDrift(
|
|
5491
|
+
{
|
|
5492
|
+
logger,
|
|
5493
|
+
db: dbLabel,
|
|
5494
|
+
drift: resolveDriftMode(config.queryOptions?.drift, opts.drift),
|
|
5495
|
+
...opts.getSchema !== void 0 ? { getSchema: opts.getSchema } : {},
|
|
5496
|
+
partial: config.projection !== void 0
|
|
5497
|
+
},
|
|
5498
|
+
String(collection),
|
|
5499
|
+
"mongo.paginateFlexible",
|
|
5500
|
+
data
|
|
5501
|
+
);
|
|
5073
5502
|
return buildPaginationResult(data, totalCount, normalized.page, normalized.limit);
|
|
5074
5503
|
}
|
|
5075
5504
|
});
|
|
@@ -5080,7 +5509,13 @@ var makePaginateFlexible = (ctx) => {
|
|
|
5080
5509
|
// src/service/pagination/index.ts
|
|
5081
5510
|
var makePaginationOps = (client, dbLabel, logger, opts) => {
|
|
5082
5511
|
const resolve = opts.resolveCollectionName;
|
|
5083
|
-
const deps2 = makeOpDeps(
|
|
5512
|
+
const deps2 = makeOpDeps(
|
|
5513
|
+
logger,
|
|
5514
|
+
dbLabel,
|
|
5515
|
+
resolve,
|
|
5516
|
+
opts.wrapMongoErrors === true,
|
|
5517
|
+
opts.debugQuery
|
|
5518
|
+
);
|
|
5084
5519
|
const ctx = {
|
|
5085
5520
|
client,
|
|
5086
5521
|
dbLabel,
|
|
@@ -5097,16 +5532,37 @@ var makePaginationOps = (client, dbLabel, logger, opts) => {
|
|
|
5097
5532
|
};
|
|
5098
5533
|
|
|
5099
5534
|
// src/service/schema-ops.ts
|
|
5100
|
-
var optionSignature = (
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5535
|
+
var optionSignature = (options) => {
|
|
5536
|
+
const normalized = {
|
|
5537
|
+
unique: Boolean(options?.unique),
|
|
5538
|
+
sparse: Boolean(options?.sparse),
|
|
5539
|
+
hidden: Boolean(options?.hidden),
|
|
5540
|
+
expireAfterSeconds: typeof options?.expireAfterSeconds === "number" ? options.expireAfterSeconds : null
|
|
5541
|
+
};
|
|
5542
|
+
for (const key of [
|
|
5543
|
+
"partialFilterExpression",
|
|
5544
|
+
"collation",
|
|
5545
|
+
"wildcardProjection",
|
|
5546
|
+
"weights",
|
|
5547
|
+
"default_language",
|
|
5548
|
+
"language_override",
|
|
5549
|
+
"textIndexVersion",
|
|
5550
|
+
"2dsphereIndexVersion",
|
|
5551
|
+
"bits",
|
|
5552
|
+
"min",
|
|
5553
|
+
"max",
|
|
5554
|
+
"bucketSize",
|
|
5555
|
+
"storageEngine"
|
|
5556
|
+
]) {
|
|
5557
|
+
if (options?.[key] !== void 0) normalized[key] = options[key];
|
|
5558
|
+
}
|
|
5559
|
+
return stableStringify(normalized);
|
|
5560
|
+
};
|
|
5105
5561
|
var indexKeyOf = (key, options) => `${JSON.stringify(Object.keys(key).map((k) => [k, key[k]]))}|${optionSignature(options)}`;
|
|
5106
5562
|
var makeSchemaOps = (client, _logger, opts) => {
|
|
5107
|
-
const
|
|
5563
|
+
const readCollectionValidation = async (physical) => {
|
|
5108
5564
|
const info = await client.listCollections({ name: physical }).next();
|
|
5109
|
-
return info?.options
|
|
5565
|
+
return info?.options ?? {};
|
|
5110
5566
|
};
|
|
5111
5567
|
const ensureDeclaredIndexes = async (collection, physical, def) => {
|
|
5112
5568
|
const declared = def?.indexes ?? [];
|
|
@@ -5120,7 +5576,7 @@ var makeSchemaOps = (client, _logger, opts) => {
|
|
|
5120
5576
|
try {
|
|
5121
5577
|
await handle.createIndex(index.key, index.options);
|
|
5122
5578
|
} catch (err) {
|
|
5123
|
-
if (err?.code
|
|
5579
|
+
if ([85, 86].includes(err?.code ?? -1)) {
|
|
5124
5580
|
throw new Error(
|
|
5125
5581
|
`Cannot apply declared index on "${collection}": an index with the same name/key already exists with different options. Run db.syncIndexes("${collection}") to reconcile index drift.`,
|
|
5126
5582
|
{ cause: err }
|
|
@@ -5145,19 +5601,21 @@ var makeSchemaOps = (client, _logger, opts) => {
|
|
|
5145
5601
|
created = false;
|
|
5146
5602
|
}
|
|
5147
5603
|
if (created) {
|
|
5148
|
-
|
|
5149
|
-
await ensureDeclaredIndexes(collection, physical, def);
|
|
5150
|
-
} catch (err) {
|
|
5151
|
-
await client.dropCollection(physical).catch(() => {
|
|
5152
|
-
});
|
|
5153
|
-
throw err;
|
|
5154
|
-
}
|
|
5604
|
+
await ensureDeclaredIndexes(collection, physical, def);
|
|
5155
5605
|
return;
|
|
5156
5606
|
}
|
|
5157
5607
|
if (createOptions.validator) {
|
|
5158
|
-
const current = await
|
|
5159
|
-
|
|
5160
|
-
|
|
5608
|
+
const current = await readCollectionValidation(physical);
|
|
5609
|
+
const collMod = {};
|
|
5610
|
+
if (stableStringify(current.validator) !== stableStringify(createOptions.validator)) {
|
|
5611
|
+
collMod.validator = createOptions.validator;
|
|
5612
|
+
}
|
|
5613
|
+
for (const key of ["validationLevel", "validationAction"]) {
|
|
5614
|
+
const requested = createOptions[key];
|
|
5615
|
+
if (requested !== void 0 && current[key] !== requested) collMod[key] = requested;
|
|
5616
|
+
}
|
|
5617
|
+
if (Object.keys(collMod).length > 0) {
|
|
5618
|
+
await client.command({ collMod: physical, ...collMod });
|
|
5161
5619
|
}
|
|
5162
5620
|
}
|
|
5163
5621
|
await ensureDeclaredIndexes(collection, physical, def);
|
|
@@ -5173,6 +5631,7 @@ var makeSchemaOps = (client, _logger, opts) => {
|
|
|
5173
5631
|
collMod: physical,
|
|
5174
5632
|
validator: toMongoValidator(resolved)
|
|
5175
5633
|
});
|
|
5634
|
+
if (def) def.schema = resolved;
|
|
5176
5635
|
};
|
|
5177
5636
|
const syncIndexes = async (collection) => {
|
|
5178
5637
|
const physical = opts.resolveCollectionName(collection);
|
|
@@ -5186,52 +5645,68 @@ var makeSchemaOps = (client, _logger, opts) => {
|
|
|
5186
5645
|
const declaredByKey = new Map(
|
|
5187
5646
|
declared.map((d) => [indexKeyOf(d.key, d.options), d])
|
|
5188
5647
|
);
|
|
5189
|
-
const
|
|
5648
|
+
const keyPattern = (key) => JSON.stringify(Object.entries(key));
|
|
5190
5649
|
const dropped = [];
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
if (!existingByKey.has(key)) {
|
|
5205
|
-
created.push(spec.options?.name ?? afterByKey.get(key) ?? JSON.stringify(spec.key));
|
|
5650
|
+
const droppedNames = /* @__PURE__ */ new Set();
|
|
5651
|
+
for (const spec of declaredByKey.values()) {
|
|
5652
|
+
const signature = indexKeyOf(spec.key, spec.options);
|
|
5653
|
+
if (existingByKey.has(signature)) continue;
|
|
5654
|
+
for (const index of existing) {
|
|
5655
|
+
const name = index.name;
|
|
5656
|
+
if (droppedNames.has(name)) continue;
|
|
5657
|
+
const sameName = spec.options?.name !== void 0 && index.name === spec.options.name;
|
|
5658
|
+
if (sameName || keyPattern(index.key) === keyPattern(spec.key)) {
|
|
5659
|
+
await handle.dropIndex(name);
|
|
5660
|
+
droppedNames.add(name);
|
|
5661
|
+
dropped.push(name);
|
|
5662
|
+
}
|
|
5206
5663
|
}
|
|
5664
|
+
await handle.createIndex(spec.key, spec.options);
|
|
5207
5665
|
}
|
|
5208
|
-
for (const [
|
|
5209
|
-
if (!declaredByKey.has(
|
|
5666
|
+
for (const [signature, name] of existingByKey) {
|
|
5667
|
+
if (!declaredByKey.has(signature) && !droppedNames.has(name)) {
|
|
5210
5668
|
await handle.dropIndex(name);
|
|
5211
5669
|
dropped.push(name);
|
|
5212
5670
|
}
|
|
5213
5671
|
}
|
|
5672
|
+
const after = (await handle.listIndexes().toArray()).filter((i) => i.name !== "_id_");
|
|
5673
|
+
const afterByKey = new Map(
|
|
5674
|
+
after.map((i) => [indexKeyOf(i.key, i), i.name])
|
|
5675
|
+
);
|
|
5676
|
+
const created = [...declaredByKey].filter(([signature]) => !existingByKey.has(signature)).map(
|
|
5677
|
+
([signature, spec]) => spec.options?.name ?? afterByKey.get(signature) ?? JSON.stringify(spec.key)
|
|
5678
|
+
);
|
|
5214
5679
|
return { created, dropped };
|
|
5215
5680
|
};
|
|
5216
5681
|
return { createSchema, updateSchema, syncIndexes };
|
|
5217
5682
|
};
|
|
5218
|
-
|
|
5219
|
-
// src/service/transaction.ts
|
|
5220
5683
|
var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
5221
5684
|
let warnedUnsupported = false;
|
|
5222
5685
|
let journalIndexReady = false;
|
|
5223
5686
|
const MIGRATE_LEASE_MS = 12e4;
|
|
5224
|
-
const transaction = async (fn, options) => {
|
|
5687
|
+
const transaction = async (fn, options = {}) => {
|
|
5688
|
+
const { allowNonTransactionalFallback, ...driverOptions } = options;
|
|
5689
|
+
const allowFallback = allowNonTransactionalFallback ?? opts.allowNonTransactionalFallback ?? false;
|
|
5225
5690
|
const session = client.startSession();
|
|
5226
5691
|
try {
|
|
5227
|
-
return await session.withTransaction(async () => fn(session),
|
|
5692
|
+
return await session.withTransaction(async () => fn(session), driverOptions);
|
|
5228
5693
|
} catch (err) {
|
|
5229
5694
|
if (isTransactionUnsupportedError(err)) {
|
|
5695
|
+
if (!allowFallback) {
|
|
5696
|
+
if (opts.wrapMongoErrors) {
|
|
5697
|
+
throw new InfraError(
|
|
5698
|
+
"TRANSACTIONS_UNSUPPORTED",
|
|
5699
|
+
"MongoDB deployment does not support transactions; enable allowNonTransactionalFallback to run non-atomically",
|
|
5700
|
+
{ db: handle.databaseName, op: "transaction" }
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
throw err;
|
|
5704
|
+
}
|
|
5230
5705
|
if (!warnedUnsupported) {
|
|
5231
5706
|
warnedUnsupported = true;
|
|
5232
5707
|
logger.warn?.(
|
|
5233
5708
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
5234
|
-
"transactions unsupported
|
|
5709
|
+
"transactions unsupported; running explicitly requested non-transactional fallback"
|
|
5235
5710
|
);
|
|
5236
5711
|
}
|
|
5237
5712
|
return fn(null);
|
|
@@ -5241,16 +5716,14 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5241
5716
|
}
|
|
5242
5717
|
throw err;
|
|
5243
5718
|
} finally {
|
|
5244
|
-
await session.endSession()
|
|
5719
|
+
await session.endSession().catch(() => {
|
|
5720
|
+
});
|
|
5245
5721
|
}
|
|
5246
5722
|
};
|
|
5247
|
-
const migrate = async (name, fn, filePath) => {
|
|
5723
|
+
const migrate = async (name, fn, filePath, options = {}) => {
|
|
5248
5724
|
const journal = handle.collection(opts.migrationCollectionPhysical);
|
|
5249
5725
|
if (!journalIndexReady) {
|
|
5250
|
-
|
|
5251
|
-
await journal.createIndex({ name: 1 }, { unique: true });
|
|
5252
|
-
} catch {
|
|
5253
|
-
}
|
|
5726
|
+
await journal.createIndex({ name: 1 }, { unique: true });
|
|
5254
5727
|
journalIndexReady = true;
|
|
5255
5728
|
}
|
|
5256
5729
|
const isDupKeyError = (err) => {
|
|
@@ -5262,10 +5735,11 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5262
5735
|
const sessionOpts = session ? { session } : {};
|
|
5263
5736
|
const now = /* @__PURE__ */ new Date();
|
|
5264
5737
|
const expiresAt = new Date(Date.now() + MIGRATE_LEASE_MS);
|
|
5738
|
+
const claimId = randomUUID();
|
|
5265
5739
|
const stole = await journal.findOneAndUpdate(
|
|
5266
5740
|
{
|
|
5267
5741
|
name,
|
|
5268
|
-
status: { $exists: true, $nin: ["applied"] },
|
|
5742
|
+
status: { $exists: true, $nin: ["applied", "rolling_back"] },
|
|
5269
5743
|
$or: [
|
|
5270
5744
|
{ expiresAt: { $lt: now } },
|
|
5271
5745
|
{
|
|
@@ -5279,13 +5753,14 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5279
5753
|
status: "running",
|
|
5280
5754
|
appliedAt: now,
|
|
5281
5755
|
expiresAt,
|
|
5756
|
+
claimId,
|
|
5282
5757
|
...filePath ? { file: filePath } : {}
|
|
5283
5758
|
}
|
|
5284
5759
|
},
|
|
5285
5760
|
{ returnDocument: "before", ...sessionOpts }
|
|
5286
5761
|
);
|
|
5287
5762
|
if (stole !== null) {
|
|
5288
|
-
await runClaimed(session);
|
|
5763
|
+
await runClaimed(session, claimId);
|
|
5289
5764
|
return;
|
|
5290
5765
|
}
|
|
5291
5766
|
try {
|
|
@@ -5295,6 +5770,7 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5295
5770
|
appliedAt: now,
|
|
5296
5771
|
status: "running",
|
|
5297
5772
|
expiresAt,
|
|
5773
|
+
claimId,
|
|
5298
5774
|
...filePath ? { file: filePath } : {}
|
|
5299
5775
|
},
|
|
5300
5776
|
sessionOpts
|
|
@@ -5313,13 +5789,13 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5313
5789
|
}
|
|
5314
5790
|
return;
|
|
5315
5791
|
}
|
|
5316
|
-
await runClaimed(session);
|
|
5317
|
-
});
|
|
5318
|
-
async function runClaimed(session) {
|
|
5792
|
+
await runClaimed(session, claimId);
|
|
5793
|
+
}, options);
|
|
5794
|
+
async function runClaimed(session, claimId) {
|
|
5319
5795
|
const sessionOpts = session ? { session } : {};
|
|
5320
5796
|
const heartbeat = session === null ? setInterval(() => {
|
|
5321
5797
|
void journal.updateOne(
|
|
5322
|
-
{ name, status: "running" },
|
|
5798
|
+
{ name, claimId, status: "running" },
|
|
5323
5799
|
{ $set: { expiresAt: new Date(Date.now() + MIGRATE_LEASE_MS) } }
|
|
5324
5800
|
).catch(() => {
|
|
5325
5801
|
});
|
|
@@ -5328,16 +5804,15 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5328
5804
|
try {
|
|
5329
5805
|
await fn(session);
|
|
5330
5806
|
const res = await journal.updateOne(
|
|
5331
|
-
{ name, status: "running" },
|
|
5332
|
-
{ $set: { status: "applied" } },
|
|
5807
|
+
{ name, claimId, status: "running" },
|
|
5808
|
+
{ $set: { status: "applied" }, $unset: { expiresAt: "", claimId: "" } },
|
|
5333
5809
|
sessionOpts
|
|
5334
5810
|
);
|
|
5335
|
-
if (res
|
|
5336
|
-
|
|
5337
|
-
});
|
|
5811
|
+
if (res.matchedCount === 0) {
|
|
5812
|
+
throw new Error(`Migration "${name}" lost its claim before it could be marked applied`);
|
|
5338
5813
|
}
|
|
5339
5814
|
} catch (err) {
|
|
5340
|
-
await journal.deleteOne({ name }, sessionOpts).catch(() => {
|
|
5815
|
+
await journal.deleteOne({ name, claimId, status: "running" }, sessionOpts).catch(() => {
|
|
5341
5816
|
});
|
|
5342
5817
|
throw err;
|
|
5343
5818
|
} finally {
|
|
@@ -5349,7 +5824,7 @@ var makeTransactionOps = (client, handle, logger, _config, opts) => {
|
|
|
5349
5824
|
};
|
|
5350
5825
|
|
|
5351
5826
|
// src/service/manager.ts
|
|
5352
|
-
var buildManager = (deps2, dbKey, dbName, client) => {
|
|
5827
|
+
var buildManager = (deps2, dbKey, dbName, client, cacheNamespace = "") => {
|
|
5353
5828
|
const { dbClients, config, logger, sharedCache, dedupeReads, inFlight, collectionRefs } = deps2;
|
|
5354
5829
|
const definition = dbClients[dbKey];
|
|
5355
5830
|
const handle = client.db(dbName);
|
|
@@ -5362,7 +5837,11 @@ var buildManager = (deps2, dbKey, dbName, client) => {
|
|
|
5362
5837
|
registry.set(logical, def);
|
|
5363
5838
|
hooks[logical] = def.hooks;
|
|
5364
5839
|
if (def.timestamps) timestamps[logical] = def.timestamps;
|
|
5365
|
-
collectionRefs.push({
|
|
5840
|
+
collectionRefs.push({
|
|
5841
|
+
db: handle,
|
|
5842
|
+
collection: baseResolveCollectionName(logical),
|
|
5843
|
+
namespace: cacheNamespace
|
|
5844
|
+
});
|
|
5366
5845
|
}
|
|
5367
5846
|
const getDefinition = (logical) => registry.get(logical);
|
|
5368
5847
|
const resolveCollectionName = (logical) => {
|
|
@@ -5373,18 +5852,23 @@ var buildManager = (deps2, dbKey, dbName, client) => {
|
|
|
5373
5852
|
};
|
|
5374
5853
|
const crud = makeCrudOps(handle, dbName, logger, {
|
|
5375
5854
|
resolveCollectionName,
|
|
5855
|
+
cacheNamespace,
|
|
5376
5856
|
wrapMongoErrors: deps2.wrapMongoErrors,
|
|
5857
|
+
...deps2.debugQuery !== void 0 ? { debugQuery: deps2.debugQuery } : {},
|
|
5377
5858
|
...sharedCache !== void 0 ? { cache: sharedCache } : {},
|
|
5378
5859
|
dedupeReads,
|
|
5379
5860
|
inFlight,
|
|
5380
5861
|
hooks,
|
|
5381
5862
|
timestamps,
|
|
5863
|
+
...deps2.validateWrites !== void 0 ? { validateWrites: deps2.validateWrites } : {},
|
|
5382
5864
|
...deps2.drift !== void 0 ? { drift: deps2.drift } : {},
|
|
5383
5865
|
...getDefinition !== void 0 ? { getSchema: (logical) => getDefinition(logical)?.schema } : {}
|
|
5384
5866
|
});
|
|
5385
5867
|
const pagination = makePaginationOps(handle, dbName, logger, {
|
|
5386
5868
|
resolveCollectionName,
|
|
5869
|
+
cacheNamespace,
|
|
5387
5870
|
wrapMongoErrors: deps2.wrapMongoErrors,
|
|
5871
|
+
...deps2.debugQuery !== void 0 ? { debugQuery: deps2.debugQuery } : {},
|
|
5388
5872
|
...deps2.drift !== void 0 ? { drift: deps2.drift } : {},
|
|
5389
5873
|
...getDefinition !== void 0 ? { getSchema: (logical) => getDefinition(logical)?.schema } : {},
|
|
5390
5874
|
...sharedCache !== void 0 ? { cache: sharedCache } : {},
|
|
@@ -5393,7 +5877,9 @@ var buildManager = (deps2, dbKey, dbName, client) => {
|
|
|
5393
5877
|
});
|
|
5394
5878
|
const aggregation = makeAggregationOps(handle, dbName, logger, {
|
|
5395
5879
|
resolveCollectionName,
|
|
5880
|
+
cacheNamespace,
|
|
5396
5881
|
wrapMongoErrors: deps2.wrapMongoErrors,
|
|
5882
|
+
...deps2.debugQuery !== void 0 ? { debugQuery: deps2.debugQuery } : {},
|
|
5397
5883
|
...sharedCache !== void 0 ? { cache: sharedCache } : {},
|
|
5398
5884
|
dedupeReads,
|
|
5399
5885
|
inFlight
|
|
@@ -5401,16 +5887,14 @@ var buildManager = (deps2, dbKey, dbName, client) => {
|
|
|
5401
5887
|
const schema = makeSchemaOps(handle, logger, { resolveCollectionName, getDefinition });
|
|
5402
5888
|
const transactionOps = makeTransactionOps(client, handle, logger, config, {
|
|
5403
5889
|
migrationCollectionPhysical: resolveCollectionName("_migrations"),
|
|
5404
|
-
wrapMongoErrors: deps2.wrapMongoErrors
|
|
5890
|
+
wrapMongoErrors: deps2.wrapMongoErrors,
|
|
5891
|
+
...deps2.allowNonTransactionalFallback !== void 0 ? { allowNonTransactionalFallback: deps2.allowNonTransactionalFallback } : {}
|
|
5405
5892
|
});
|
|
5406
5893
|
const populator = makePopulator({
|
|
5407
|
-
findMany: async (logical, filter, options) => {
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
limit: options?.limit ?? 1e4
|
|
5412
|
-
}).toArray();
|
|
5413
|
-
}
|
|
5894
|
+
findMany: async (logical, filter, options) => crud.findMany(logical, filter, {
|
|
5895
|
+
...options,
|
|
5896
|
+
cache: options?.queryCache ?? false
|
|
5897
|
+
})
|
|
5414
5898
|
});
|
|
5415
5899
|
const populate = (async (docs, relations, options = {}) => {
|
|
5416
5900
|
const result = await populator.populate(docs, relations, options);
|
|
@@ -5424,6 +5908,7 @@ var buildManager = (deps2, dbKey, dbName, client) => {
|
|
|
5424
5908
|
...transactionOps,
|
|
5425
5909
|
populate,
|
|
5426
5910
|
client: handle,
|
|
5911
|
+
mongoClient: client,
|
|
5427
5912
|
db: deps2.db
|
|
5428
5913
|
};
|
|
5429
5914
|
};
|
|
@@ -5451,25 +5936,44 @@ var createMongoService = (dbClients, configOverrides = {}) => {
|
|
|
5451
5936
|
dedupeReads,
|
|
5452
5937
|
inFlight,
|
|
5453
5938
|
wrapMongoErrors: runtime.wrapMongoErrors,
|
|
5939
|
+
...configOverrides.debugQuery !== void 0 ? { debugQuery: configOverrides.debugQuery } : {},
|
|
5940
|
+
...configOverrides.allowNonTransactionalFallback !== void 0 ? { allowNonTransactionalFallback: configOverrides.allowNonTransactionalFallback } : {},
|
|
5941
|
+
...configOverrides.validateWrites !== void 0 ? { validateWrites: configOverrides.validateWrites } : {},
|
|
5454
5942
|
...configOverrides.drift !== void 0 ? { drift: configOverrides.drift } : {},
|
|
5455
5943
|
collectionRefs,
|
|
5456
5944
|
db
|
|
5457
5945
|
},
|
|
5458
5946
|
key,
|
|
5459
5947
|
dbName,
|
|
5460
|
-
client
|
|
5948
|
+
client,
|
|
5949
|
+
stableHash(getDbUrl(key))
|
|
5461
5950
|
)),
|
|
5462
5951
|
openedClients
|
|
5463
5952
|
);
|
|
5464
|
-
|
|
5953
|
+
let lifecycle = Promise.resolve();
|
|
5954
|
+
const serializeLifecycle = (operation) => {
|
|
5955
|
+
const result = lifecycle.then(operation, operation);
|
|
5956
|
+
lifecycle = result.then(
|
|
5957
|
+
() => {
|
|
5958
|
+
},
|
|
5959
|
+
() => {
|
|
5960
|
+
}
|
|
5961
|
+
);
|
|
5962
|
+
return result;
|
|
5963
|
+
};
|
|
5964
|
+
const makeConnections = (names) => serializeLifecycle(async () => {
|
|
5465
5965
|
await baseMakeConnections(names);
|
|
5466
5966
|
await cacheInvalidator?.start(collectionRefs);
|
|
5467
|
-
};
|
|
5468
|
-
const closeConnections2 = async () => {
|
|
5967
|
+
});
|
|
5968
|
+
const closeConnections2 = () => serializeLifecycle(async () => {
|
|
5469
5969
|
await cacheInvalidator?.stop();
|
|
5470
5970
|
collectionRefs.length = 0;
|
|
5471
|
-
|
|
5472
|
-
|
|
5971
|
+
try {
|
|
5972
|
+
await closeConnections(openedClients);
|
|
5973
|
+
} finally {
|
|
5974
|
+
for (const key of Object.keys(db)) delete db[key];
|
|
5975
|
+
}
|
|
5976
|
+
});
|
|
5473
5977
|
const eachDb = async (fn) => {
|
|
5474
5978
|
return Promise.all(Object.keys(db).map((key) => fn(db[key], key.replace(/Client$/, ""))));
|
|
5475
5979
|
};
|