monsqlize 3.3.0 → 3.3.1
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/CHANGELOG.md +4 -3
- package/README.md +31 -5
- package/changelogs/v3.3.1.md +28 -0
- package/dist/cjs/cli/data-task.cjs +3 -2
- package/dist/cjs/index.cjs +1010 -610
- package/dist/cjs/transaction/Transaction.cjs +33 -5
- package/dist/cjs/transaction/TransactionManager.cjs +38 -16
- package/dist/esm/index.mjs +1014 -614
- package/dist/types/collection.d.mts +6 -6
- package/dist/types/collection.d.ts +6 -6
- package/dist/types/data-tasks.d.mts +1 -0
- package/dist/types/data-tasks.d.ts +1 -0
- package/dist/types/model.d.mts +3 -0
- package/dist/types/model.d.ts +3 -0
- package/dist/types/monsqlize.d.mts +4 -0
- package/dist/types/monsqlize.d.ts +4 -0
- package/package.json +3 -2
package/dist/cjs/index.cjs
CHANGED
|
@@ -187,12 +187,17 @@ function createRedisCacheAdapter(redisUrlOrInstance, options) {
|
|
|
187
187
|
|
|
188
188
|
// src/capabilities/cache/distributed-cache-invalidator.ts
|
|
189
189
|
var import_crypto = require("crypto");
|
|
190
|
+
var import_cache_hub2 = require("cache-hub");
|
|
190
191
|
var DistributedCacheInvalidator = class {
|
|
191
192
|
constructor(options) {
|
|
192
193
|
if (!options.cache) {
|
|
193
194
|
throw createError(ErrorCodes.CACHE_UNAVAILABLE, "DistributedCacheInvalidator requires a cache instance");
|
|
194
195
|
}
|
|
195
196
|
this._cache = options.cache;
|
|
197
|
+
const safeMemoryCache = this._cache instanceof import_cache_hub2.MemoryCache || this._cache.local instanceof import_cache_hub2.MemoryCache && this._cache.remote === void 0;
|
|
198
|
+
if (!safeMemoryCache && typeof this._cache.invalidateFromRemote !== "function") {
|
|
199
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "DistributedCacheInvalidator requires receive-only cache invalidation");
|
|
200
|
+
}
|
|
196
201
|
this._logger = options.logger ?? null;
|
|
197
202
|
this.channel = options.channel ?? "monsqlize:cache:invalidate";
|
|
198
203
|
this.instanceId = options.instanceId ?? `instance-${Date.now()}-${(0, import_crypto.randomBytes)(4).toString("hex")}`;
|
|
@@ -240,18 +245,10 @@ var DistributedCacheInvalidator = class {
|
|
|
240
245
|
this._stats.messagesReceived++;
|
|
241
246
|
try {
|
|
242
247
|
if (isPatternInvalidation) {
|
|
243
|
-
|
|
244
|
-
await targetCache.delPattern?.(String(message.pattern));
|
|
245
|
-
}
|
|
248
|
+
await this._invalidateFromRemote("pattern", String(message.pattern));
|
|
246
249
|
this._logger?.debug?.(`[DistributedCacheInvalidator] Invalidated cache targets: ${String(message.pattern)}`);
|
|
247
250
|
} else {
|
|
248
|
-
|
|
249
|
-
if (typeof targetCache.del === "function") {
|
|
250
|
-
await targetCache.del(String(message.key));
|
|
251
|
-
} else {
|
|
252
|
-
await targetCache.delete?.(String(message.key));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
251
|
+
await this._invalidateFromRemote("key", String(message.key));
|
|
255
252
|
this._logger?.debug?.(`[DistributedCacheInvalidator] Invalidated cache key targets: ${String(message.key)}`);
|
|
256
253
|
}
|
|
257
254
|
this._stats.invalidationsTriggered++;
|
|
@@ -261,32 +258,18 @@ var DistributedCacheInvalidator = class {
|
|
|
261
258
|
}
|
|
262
259
|
});
|
|
263
260
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (this._cache?.local && typeof this._cache.local.delPattern === "function") {
|
|
270
|
-
targets.add(this._cache.local);
|
|
271
|
-
}
|
|
272
|
-
if (this._cache?.remote && typeof this._cache.remote.delPattern === "function") {
|
|
273
|
-
targets.add(this._cache.remote);
|
|
274
|
-
}
|
|
275
|
-
return [...targets];
|
|
276
|
-
}
|
|
277
|
-
_getKeyTargetCaches() {
|
|
278
|
-
const targets = /* @__PURE__ */ new Set();
|
|
279
|
-
const supportsKeyDelete = (cache) => typeof cache?.del === "function" || typeof cache?.delete === "function";
|
|
280
|
-
if (supportsKeyDelete(this._cache)) {
|
|
281
|
-
targets.add(this._cache);
|
|
282
|
-
}
|
|
283
|
-
if (supportsKeyDelete(this._cache?.local)) {
|
|
284
|
-
targets.add(this._cache.local);
|
|
261
|
+
async _invalidateFromRemote(kind, value) {
|
|
262
|
+
if (this._cache instanceof import_cache_hub2.MemoryCache) {
|
|
263
|
+
if (kind === "pattern") await this._cache.delPattern(value);
|
|
264
|
+
else await this._cache.del(value);
|
|
265
|
+
return;
|
|
285
266
|
}
|
|
286
|
-
if (
|
|
287
|
-
|
|
267
|
+
if (this._cache.local instanceof import_cache_hub2.MemoryCache && this._cache.remote === void 0) {
|
|
268
|
+
if (kind === "pattern") await this._cache.local.delPattern(value);
|
|
269
|
+
else await this._cache.local.del(value);
|
|
270
|
+
return;
|
|
288
271
|
}
|
|
289
|
-
|
|
272
|
+
await this._cache.invalidateFromRemote?.(kind, value);
|
|
290
273
|
}
|
|
291
274
|
async invalidate(pattern) {
|
|
292
275
|
if (!pattern) return;
|
|
@@ -339,8 +322,8 @@ var DistributedCacheInvalidator = class {
|
|
|
339
322
|
};
|
|
340
323
|
|
|
341
324
|
// src/capabilities/cache/index.ts
|
|
342
|
-
var
|
|
343
|
-
var MultiLevelCache = class extends
|
|
325
|
+
var import_cache_hub3 = require("cache-hub");
|
|
326
|
+
var MultiLevelCache = class extends import_cache_hub3.MultiLevelCache {
|
|
344
327
|
constructor(options) {
|
|
345
328
|
const publishRef = { current: options.publish };
|
|
346
329
|
super({
|
|
@@ -358,6 +341,15 @@ var MultiLevelCache = class extends import_cache_hub2.MultiLevelCache {
|
|
|
358
341
|
this._localCompat.setLockManager?.(lockManager);
|
|
359
342
|
this._remoteCompat?.setLockManager?.(lockManager);
|
|
360
343
|
}
|
|
344
|
+
async invalidateFromRemote(kind, value) {
|
|
345
|
+
if (kind === "pattern") {
|
|
346
|
+
await this._localCompat.delPattern(value);
|
|
347
|
+
await this._remoteCompat?.delPattern(value);
|
|
348
|
+
} else {
|
|
349
|
+
await this._localCompat.del(value);
|
|
350
|
+
await this._remoteCompat?.del(value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
361
353
|
};
|
|
362
354
|
function adaptLegacyCacheLike(v1Cache) {
|
|
363
355
|
if ("has" in v1Cache && typeof v1Cache.has === "function") {
|
|
@@ -735,6 +727,40 @@ function serializeDocument(document) {
|
|
|
735
727
|
}
|
|
736
728
|
|
|
737
729
|
// src/capabilities/model/model-write-helpers.ts
|
|
730
|
+
async function* iterateModelCandidateBatches(context, filter, options, batchSize) {
|
|
731
|
+
const lookupOptions = buildModelVersionLookupOptions(options, {
|
|
732
|
+
_id: 1,
|
|
733
|
+
[context.versionConfig?.field ?? "__v"]: 1
|
|
734
|
+
});
|
|
735
|
+
const stream = context.collection.stream(filter, {
|
|
736
|
+
...lookupOptions,
|
|
737
|
+
sort: { _id: 1 },
|
|
738
|
+
batchSize
|
|
739
|
+
});
|
|
740
|
+
let batch = [];
|
|
741
|
+
try {
|
|
742
|
+
for await (const document of stream) {
|
|
743
|
+
batch.push(document);
|
|
744
|
+
if (batch.length === batchSize) {
|
|
745
|
+
yield batch;
|
|
746
|
+
batch = [];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (batch.length > 0) yield batch;
|
|
750
|
+
} finally {
|
|
751
|
+
stream.destroy();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function buildStrictCandidateFilter(filter, id, versionField, expectedVersion) {
|
|
755
|
+
return { $and: [filter ?? {}, { _id: id }, { [versionField]: expectedVersion }] };
|
|
756
|
+
}
|
|
757
|
+
async function isModelVersionConflict(context, filter, id, versionField, expectedVersion, options) {
|
|
758
|
+
const current = await context.collection.findOne(
|
|
759
|
+
{ $and: [filter ?? {}, { _id: id }] },
|
|
760
|
+
buildModelVersionLookupOptions(options, { [versionField]: 1 })
|
|
761
|
+
);
|
|
762
|
+
return current !== null && current !== void 0 && current[versionField] !== expectedVersion;
|
|
763
|
+
}
|
|
738
764
|
var VERSION_LOOKUP_OPTION_KEYS = [
|
|
739
765
|
"session",
|
|
740
766
|
"readConcern",
|
|
@@ -820,11 +846,14 @@ function mapModelSchemaValidationErrors(errors = []) {
|
|
|
820
846
|
}
|
|
821
847
|
function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
822
848
|
const shouldValidate = context.validateEnabled || options?.validate === true;
|
|
823
|
-
if (!shouldValidate) {
|
|
849
|
+
if (!shouldValidate || options?.skipValidation) {
|
|
824
850
|
return document;
|
|
825
851
|
}
|
|
826
|
-
if (
|
|
827
|
-
|
|
852
|
+
if (context.schemaError) {
|
|
853
|
+
throw withModelErrorMetadata(
|
|
854
|
+
createError(ErrorCodes.VALIDATION_ERROR, `Schema initialization failed: ${context.schemaError.message}`),
|
|
855
|
+
{ errors: [{ field: "_schema", message: context.schemaError.message }], ...metadata }
|
|
856
|
+
);
|
|
828
857
|
}
|
|
829
858
|
if (!context.schemaCache || !context.schemaValidateFn) {
|
|
830
859
|
return document;
|
|
@@ -858,22 +887,31 @@ function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
|
858
887
|
}
|
|
859
888
|
return normalized;
|
|
860
889
|
}
|
|
890
|
+
function buildModelVisibleSoftDeleteCondition(config) {
|
|
891
|
+
return { [config.field]: config.type === "boolean" ? { $ne: true } : null };
|
|
892
|
+
}
|
|
861
893
|
function applyModelSoftDeleteFilter(query, options, softDeleteConfig) {
|
|
862
894
|
if (!softDeleteConfig?.enabled) {
|
|
863
895
|
return query ?? {};
|
|
864
896
|
}
|
|
865
897
|
const resolvedOptions = options ?? {};
|
|
866
898
|
const resolvedQuery = query ?? {};
|
|
867
|
-
if (
|
|
899
|
+
if (resolvedOptions.withDeleted !== void 0 && typeof resolvedOptions.withDeleted !== "boolean") {
|
|
900
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "withDeleted must be a boolean.");
|
|
901
|
+
}
|
|
902
|
+
if (resolvedOptions.onlyDeleted !== void 0 && typeof resolvedOptions.onlyDeleted !== "boolean") {
|
|
903
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "onlyDeleted must be a boolean.");
|
|
904
|
+
}
|
|
905
|
+
if (Object.prototype.hasOwnProperty.call(resolvedQuery, softDeleteConfig.field)) {
|
|
868
906
|
return resolvedQuery;
|
|
869
907
|
}
|
|
870
908
|
if (resolvedOptions.withDeleted) {
|
|
871
909
|
return resolvedQuery;
|
|
872
910
|
}
|
|
873
911
|
if (resolvedOptions.onlyDeleted) {
|
|
874
|
-
return { ...resolvedQuery, [softDeleteConfig.field]: { $ne: null } };
|
|
912
|
+
return { ...resolvedQuery, [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : { $ne: null } };
|
|
875
913
|
}
|
|
876
|
-
return { ...resolvedQuery,
|
|
914
|
+
return { ...resolvedQuery, ...buildModelVisibleSoftDeleteCondition(softDeleteConfig) };
|
|
877
915
|
}
|
|
878
916
|
function applyModelInsertTimestamps(document, timestampsConfig, nowFactory) {
|
|
879
917
|
if (!timestampsConfig) {
|
|
@@ -1168,7 +1206,9 @@ function projectPopulatedDocument(document, select, nestedPaths) {
|
|
|
1168
1206
|
return document;
|
|
1169
1207
|
}
|
|
1170
1208
|
const nestedRelationFields = nestedPaths.map((path5) => normalizePopulateConfig(path5).path);
|
|
1171
|
-
|
|
1209
|
+
const projected = pickFields(document, [.../* @__PURE__ */ new Set([...select, ...nestedRelationFields])]);
|
|
1210
|
+
if (!select.includes("_id")) delete projected._id;
|
|
1211
|
+
return projected;
|
|
1172
1212
|
}
|
|
1173
1213
|
async function populateModelPath(context, docs, path5, traversalState) {
|
|
1174
1214
|
const config = normalizePopulateConfig(path5);
|
|
@@ -1190,7 +1230,13 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1190
1230
|
}
|
|
1191
1231
|
const localValues = docs.map((doc) => getByPath(doc, relation.localField));
|
|
1192
1232
|
const keys = unique(localValues.flatMap((value) => flattenComparableValues(value)));
|
|
1193
|
-
if (
|
|
1233
|
+
if (config.limit !== void 0 && (!Number.isSafeInteger(config.limit) || config.limit < 0)) {
|
|
1234
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "populate limit must be a non-negative integer");
|
|
1235
|
+
}
|
|
1236
|
+
if (config.skip !== void 0 && (!Number.isSafeInteger(config.skip) || config.skip < 0)) {
|
|
1237
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "populate skip must be a non-negative integer");
|
|
1238
|
+
}
|
|
1239
|
+
if (keys.length === 0 || config.limit === 0) {
|
|
1194
1240
|
for (const doc of docs) {
|
|
1195
1241
|
doc[config.path] = relation.single ? null : [];
|
|
1196
1242
|
}
|
|
@@ -1210,36 +1256,63 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1210
1256
|
...effectiveSelect,
|
|
1211
1257
|
relation.foreignField,
|
|
1212
1258
|
"_id",
|
|
1213
|
-
...
|
|
1259
|
+
...Object.keys(config.sort ?? {}),
|
|
1260
|
+
...collectNestedLocalFields(relatedModel, nestedPaths),
|
|
1261
|
+
...relatedModel?.softDeleteConfig?.enabled ? [relatedModel.softDeleteConfig.field] : []
|
|
1214
1262
|
])] : [];
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
...config.
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1263
|
+
const readOptions = {
|
|
1264
|
+
...effectiveSelect ? { projection: Object.fromEntries(projectionFields.map((field) => [field, 1])) } : {},
|
|
1265
|
+
...config.sort ? { sort: config.sort } : {}
|
|
1266
|
+
};
|
|
1267
|
+
const visibilityMatch = applyModelSoftDeleteFilter(config.match ?? {}, {}, relatedModel?.softDeleteConfig ?? null);
|
|
1268
|
+
const readForKeys = (values, options = readOptions) => {
|
|
1269
|
+
const keyFilter = { [relation.foreignField]: { $in: values } };
|
|
1270
|
+
return readPopulateRows(
|
|
1271
|
+
relatedCollection,
|
|
1272
|
+
Object.keys(visibilityMatch).length === 0 ? keyFilter : { $and: [keyFilter, visibilityMatch] },
|
|
1273
|
+
options
|
|
1274
|
+
);
|
|
1275
|
+
};
|
|
1276
|
+
const hydrateRelated = async (rows) => {
|
|
1277
|
+
let hydrated = relatedModel ? relatedModel.hydrateDocuments(rows) : rows.map((item) => ({ ...item }));
|
|
1278
|
+
if (nestedPaths.length > 0 && relatedModel) {
|
|
1279
|
+
if (state.depth + 1 > state.maxDepth) {
|
|
1280
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
|
|
1281
|
+
}
|
|
1282
|
+
hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
|
|
1283
|
+
depth: state.depth + 1,
|
|
1284
|
+
maxDepth: state.maxDepth,
|
|
1285
|
+
activeConfigs: state.activeConfigs
|
|
1286
|
+
});
|
|
1225
1287
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1288
|
+
return hydrated;
|
|
1289
|
+
};
|
|
1290
|
+
if (config.limit !== void 0 || config.skip !== void 0) {
|
|
1291
|
+
const scopedOptions = {
|
|
1292
|
+
...readOptions,
|
|
1293
|
+
...config.skip !== void 0 ? { skip: config.skip } : {},
|
|
1294
|
+
...config.limit !== void 0 ? { limit: config.limit } : {}
|
|
1295
|
+
};
|
|
1296
|
+
for (const [index, doc] of docs.entries()) {
|
|
1297
|
+
const parentKeys = unique(flattenComparableValues(localValues[index]));
|
|
1298
|
+
const rows = parentKeys.length > 0 ? await readForKeys(parentKeys, scopedOptions) : [];
|
|
1299
|
+
const hydrated = await hydrateRelated(rows);
|
|
1300
|
+
const matches = dedupeDocuments(hydrated);
|
|
1301
|
+
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1302
|
+
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1237
1303
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1304
|
+
} else {
|
|
1305
|
+
const rows = [];
|
|
1306
|
+
for (let offset = 0; offset < keys.length; offset += 200) {
|
|
1307
|
+
rows.push(...await readForKeys(keys.slice(offset, offset + 200)));
|
|
1308
|
+
}
|
|
1309
|
+
const hydrated = await hydrateRelated(rows);
|
|
1310
|
+
const grouped = groupBy(hydrated, (item) => getByPath(item, relation.foreignField));
|
|
1311
|
+
for (const [index, doc] of docs.entries()) {
|
|
1312
|
+
const matches = rebuildPopulateMatches(localValues[index], grouped, config.sort);
|
|
1313
|
+
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1314
|
+
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1240
1315
|
}
|
|
1241
|
-
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1242
|
-
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1243
1316
|
}
|
|
1244
1317
|
return docs;
|
|
1245
1318
|
} finally {
|
|
@@ -1440,12 +1513,28 @@ async function saveModelDocument(collection, document, options = {}) {
|
|
|
1440
1513
|
document._id = result.insertedId;
|
|
1441
1514
|
return document;
|
|
1442
1515
|
}
|
|
1443
|
-
async function removeModelDocument(collection, document) {
|
|
1516
|
+
async function removeModelDocument(collection, document, deleteOne = (filter) => collection.deleteOne(filter)) {
|
|
1444
1517
|
if (document._id === void 0) {
|
|
1445
1518
|
return false;
|
|
1446
1519
|
}
|
|
1447
|
-
const result = await
|
|
1448
|
-
|
|
1520
|
+
const result = await deleteOne({ _id: document._id });
|
|
1521
|
+
if (result.matchedCount === void 0 && result.modifiedCount === void 0 && result.deletedCount === void 0) {
|
|
1522
|
+
return result.acknowledged === true;
|
|
1523
|
+
}
|
|
1524
|
+
return Boolean(result.matchedCount || result.modifiedCount || result.deletedCount);
|
|
1525
|
+
}
|
|
1526
|
+
async function readPopulateRows(collection, query, options) {
|
|
1527
|
+
if (typeof collection.stream !== "function") {
|
|
1528
|
+
return await collection.find(query, options);
|
|
1529
|
+
}
|
|
1530
|
+
const stream = collection.stream(query, options);
|
|
1531
|
+
const rows = [];
|
|
1532
|
+
try {
|
|
1533
|
+
for await (const row of stream) rows.push(row);
|
|
1534
|
+
} finally {
|
|
1535
|
+
stream.destroy();
|
|
1536
|
+
}
|
|
1537
|
+
return rows;
|
|
1449
1538
|
}
|
|
1450
1539
|
|
|
1451
1540
|
// src/capabilities/model/model-soft-delete-helpers.ts
|
|
@@ -1453,7 +1542,53 @@ function deletedFilter(filter, softDeleteConfig) {
|
|
|
1453
1542
|
if (!softDeleteConfig) {
|
|
1454
1543
|
return filter ?? {};
|
|
1455
1544
|
}
|
|
1456
|
-
return
|
|
1545
|
+
return applyModelSoftDeleteFilter(filter, { onlyDeleted: true }, softDeleteConfig);
|
|
1546
|
+
}
|
|
1547
|
+
function filterVisibleBySoftDelete(docs, options, config) {
|
|
1548
|
+
if (!config?.enabled) return docs.filter((doc) => Boolean(doc));
|
|
1549
|
+
const rawOptions = options ?? {};
|
|
1550
|
+
applyModelSoftDeleteFilter({}, rawOptions, config);
|
|
1551
|
+
if (rawOptions.withDeleted) return docs.filter((doc) => Boolean(doc));
|
|
1552
|
+
return docs.filter((doc) => {
|
|
1553
|
+
if (!doc) return false;
|
|
1554
|
+
const value = doc[config.field];
|
|
1555
|
+
const deleted = config.type === "boolean" ? value === true : value !== void 0 && value !== null;
|
|
1556
|
+
return rawOptions.onlyDeleted ? deleted : !deleted;
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
function withSoftDeleteProjection(options, config) {
|
|
1560
|
+
if (!config?.enabled) return { options, stripField: false };
|
|
1561
|
+
const rawOptions = options ?? {};
|
|
1562
|
+
const field = config.field;
|
|
1563
|
+
const projection = rawOptions.projection ?? rawOptions.project;
|
|
1564
|
+
if (Array.isArray(projection)) {
|
|
1565
|
+
if (projection.includes(field)) return { options, stripField: false };
|
|
1566
|
+
return { options: { ...rawOptions, projection: [...projection, field] }, stripField: true };
|
|
1567
|
+
}
|
|
1568
|
+
if (!projection || typeof projection !== "object") return { options, stripField: false };
|
|
1569
|
+
const projectionRecord = projection;
|
|
1570
|
+
if (projectionRecord[field] === 1 || projectionRecord[field] === true) return { options, stripField: false };
|
|
1571
|
+
const inclusion = Object.entries(projectionRecord).some(([key, value]) => key !== "_id" && (value === 1 || value === true));
|
|
1572
|
+
const nextProjection = { ...projectionRecord };
|
|
1573
|
+
if (inclusion) nextProjection[field] = 1;
|
|
1574
|
+
else delete nextProjection[field];
|
|
1575
|
+
return { options: { ...rawOptions, projection: nextProjection }, stripField: true };
|
|
1576
|
+
}
|
|
1577
|
+
function applySoftDeleteFindPageOptions(options, config) {
|
|
1578
|
+
const rawOptions = options ?? {};
|
|
1579
|
+
return { ...rawOptions, query: applyModelSoftDeleteFilter(rawOptions.query, rawOptions, config) };
|
|
1580
|
+
}
|
|
1581
|
+
function applySoftDeleteAggregatePipeline(pipeline, options, config) {
|
|
1582
|
+
if (!config?.enabled) return pipeline ?? [];
|
|
1583
|
+
const rawOptions = options ?? {};
|
|
1584
|
+
if (rawOptions.withDeleted) return pipeline ?? [];
|
|
1585
|
+
const softDeleteMatch = applyModelSoftDeleteFilter({}, rawOptions, config);
|
|
1586
|
+
const matchStage = { $match: softDeleteMatch };
|
|
1587
|
+
const stages = [...pipeline ?? []];
|
|
1588
|
+
if (stages.length > 0 && stages[0] && typeof stages[0] === "object" && "$geoNear" in stages[0]) {
|
|
1589
|
+
return [stages[0], matchStage, ...stages.slice(1)];
|
|
1590
|
+
}
|
|
1591
|
+
return [matchStage, ...stages];
|
|
1457
1592
|
}
|
|
1458
1593
|
function findWithDeletedDocuments(context, query, options) {
|
|
1459
1594
|
return new PopulatePromise(async (paths) => {
|
|
@@ -1496,7 +1631,7 @@ function restoreSoftDeletedDocuments(context, filter, options) {
|
|
|
1496
1631
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1497
1632
|
}
|
|
1498
1633
|
return context.collection.updateOne(
|
|
1499
|
-
|
|
1634
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1500
1635
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1501
1636
|
options
|
|
1502
1637
|
);
|
|
@@ -1507,7 +1642,7 @@ function restoreManySoftDeletedDocuments(context, filter, options) {
|
|
|
1507
1642
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1508
1643
|
}
|
|
1509
1644
|
return context.collection.updateMany(
|
|
1510
|
-
|
|
1645
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1511
1646
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1512
1647
|
options
|
|
1513
1648
|
);
|
|
@@ -3123,6 +3258,9 @@ var modelWriteSourceStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
|
3123
3258
|
function isPlainObject2(value) {
|
|
3124
3259
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3125
3260
|
}
|
|
3261
|
+
function ownValue(record, key) {
|
|
3262
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
3263
|
+
}
|
|
3126
3264
|
function invalidConfig(path5, reason) {
|
|
3127
3265
|
throw createError(
|
|
3128
3266
|
ErrorCodes.INVALID_OPERATION,
|
|
@@ -3143,19 +3281,19 @@ function normalizeRule(input, path5) {
|
|
|
3143
3281
|
if (!isPlainObject2(input)) {
|
|
3144
3282
|
invalidConfig(path5, "rule must be a string mode or an object");
|
|
3145
3283
|
}
|
|
3146
|
-
const mode = input
|
|
3284
|
+
const mode = ownValue(input, "mode") ?? DEFAULT_RULE.mode;
|
|
3147
3285
|
if (!ALLOWED_MODES.has(mode)) {
|
|
3148
3286
|
invalidConfig(`${path5}.mode`, 'must be "allow-both" or "model-only"');
|
|
3149
3287
|
}
|
|
3150
|
-
const raw = input
|
|
3288
|
+
const raw = ownValue(input, "raw") ?? DEFAULT_RULE.raw;
|
|
3151
3289
|
if (!ALLOWED_RAW.has(raw)) {
|
|
3152
3290
|
invalidConfig(`${path5}.raw`, 'must be "inherit", "allow", or "block"');
|
|
3153
3291
|
}
|
|
3154
|
-
const management = input
|
|
3292
|
+
const management = ownValue(input, "management") ?? DEFAULT_RULE.management;
|
|
3155
3293
|
if (!ALLOWED_MANAGEMENT.has(management)) {
|
|
3156
3294
|
invalidConfig(`${path5}.management`, 'must be "inherit", "allow", or "block"');
|
|
3157
3295
|
}
|
|
3158
|
-
const onViolation = input
|
|
3296
|
+
const onViolation = ownValue(input, "onViolation") ?? DEFAULT_RULE.onViolation;
|
|
3159
3297
|
if (!ALLOWED_VIOLATION_ACTIONS.has(onViolation)) {
|
|
3160
3298
|
invalidConfig(`${path5}.onViolation`, 'must be "throw" or "warn"');
|
|
3161
3299
|
}
|
|
@@ -3171,15 +3309,15 @@ function validateWritePathPolicyConfig(options) {
|
|
|
3171
3309
|
}
|
|
3172
3310
|
function normalizeWritePathPolicy(options) {
|
|
3173
3311
|
if (options === void 0) {
|
|
3174
|
-
return { default: { ...DEFAULT_RULE }, namespaces:
|
|
3312
|
+
return { default: { ...DEFAULT_RULE }, namespaces: /* @__PURE__ */ Object.create(null), enabled: false };
|
|
3175
3313
|
}
|
|
3176
3314
|
if (!isPlainObject2(options)) {
|
|
3177
3315
|
invalidConfig("writePathPolicy", "must be an object");
|
|
3178
3316
|
}
|
|
3179
3317
|
const typedOptions = options;
|
|
3180
|
-
const defaultRule = normalizeRule(typedOptions
|
|
3181
|
-
const namespacesInput = options
|
|
3182
|
-
const namespaces =
|
|
3318
|
+
const defaultRule = normalizeRule(ownValue(typedOptions, "default"), "writePathPolicy.default");
|
|
3319
|
+
const namespacesInput = ownValue(options, "namespaces");
|
|
3320
|
+
const namespaces = /* @__PURE__ */ Object.create(null);
|
|
3183
3321
|
if (namespacesInput !== void 0) {
|
|
3184
3322
|
if (!isPlainObject2(namespacesInput)) {
|
|
3185
3323
|
invalidConfig("writePathPolicy.namespaces", "must be an object");
|
|
@@ -3227,8 +3365,8 @@ function resolveWritePathRule(policy, namespace) {
|
|
|
3227
3365
|
if (candidate === "default") {
|
|
3228
3366
|
return { key: "default", rule: effectivePolicy.default };
|
|
3229
3367
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3368
|
+
if (Object.prototype.hasOwnProperty.call(effectivePolicy.namespaces, candidate)) {
|
|
3369
|
+
const rule = effectivePolicy.namespaces[candidate];
|
|
3232
3370
|
return { key: candidate, rule };
|
|
3233
3371
|
}
|
|
3234
3372
|
}
|
|
@@ -3371,16 +3509,17 @@ var fallbackModelIndexTasks = /* @__PURE__ */ new Map();
|
|
|
3371
3509
|
function toCompatDefinition(definition) {
|
|
3372
3510
|
return definition;
|
|
3373
3511
|
}
|
|
3374
|
-
function stableIndexStringify(value) {
|
|
3512
|
+
function stableIndexStringify(value, mode = "options") {
|
|
3375
3513
|
if (value instanceof Date) {
|
|
3376
3514
|
return JSON.stringify(value.toISOString());
|
|
3377
3515
|
}
|
|
3378
3516
|
if (Array.isArray(value)) {
|
|
3379
|
-
return `[${value.map((item) => stableIndexStringify(item)).join(",")}]`;
|
|
3517
|
+
return `[${value.map((item) => stableIndexStringify(item, mode)).join(",")}]`;
|
|
3380
3518
|
}
|
|
3381
3519
|
if (value && typeof value === "object") {
|
|
3382
|
-
const entries = Object.entries(value).filter(([, current]) => current !== void 0)
|
|
3383
|
-
|
|
3520
|
+
const entries = Object.entries(value).filter(([, current]) => current !== void 0);
|
|
3521
|
+
if (mode === "options") entries.sort(([left], [right]) => left.localeCompare(right));
|
|
3522
|
+
return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableIndexStringify(current, key === "key" ? "key" : mode)}`).join(",")}}`;
|
|
3384
3523
|
}
|
|
3385
3524
|
return JSON.stringify(value) ?? "undefined";
|
|
3386
3525
|
}
|
|
@@ -3414,7 +3553,7 @@ function declaredOptionEntries(options) {
|
|
|
3414
3553
|
});
|
|
3415
3554
|
}
|
|
3416
3555
|
function indexOptionsMatch(existing, declared) {
|
|
3417
|
-
if (stableIndexStringify(getExistingIndexKey(existing)) !== stableIndexStringify(declared.key)) {
|
|
3556
|
+
if (stableIndexStringify(getExistingIndexKey(existing), "key") !== stableIndexStringify(declared.key, "key")) {
|
|
3418
3557
|
return false;
|
|
3419
3558
|
}
|
|
3420
3559
|
for (const [name, value] of declaredOptionEntries(declared.options)) {
|
|
@@ -3433,8 +3572,8 @@ function findExistingIndexByName(existingIndexes, name) {
|
|
|
3433
3572
|
return existingIndexes.find((index) => index.name === name);
|
|
3434
3573
|
}
|
|
3435
3574
|
function findExistingIndexByKey(existingIndexes, key) {
|
|
3436
|
-
const fingerprint = stableIndexStringify(key);
|
|
3437
|
-
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index)) === fingerprint);
|
|
3575
|
+
const fingerprint = stableIndexStringify(key, "key");
|
|
3576
|
+
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index), "key") === fingerprint);
|
|
3438
3577
|
}
|
|
3439
3578
|
function createIndexEnsureError(message, result, cause) {
|
|
3440
3579
|
return createError(ErrorCodes.MONGODB_ERROR, message, [result], cause);
|
|
@@ -4272,12 +4411,17 @@ function shouldConvertQueryField(autoConvert, path5, depth) {
|
|
|
4272
4411
|
if (!config.enabled || depth > config.maxDepth) return false;
|
|
4273
4412
|
return shouldConvertObjectIdPath(path5, config);
|
|
4274
4413
|
}
|
|
4414
|
+
function isPlainQueryRecord(value) {
|
|
4415
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4416
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4417
|
+
return prototype === Object.prototype || prototype === null;
|
|
4418
|
+
}
|
|
4275
4419
|
function normalizeQueryArray(value, autoConvert, path5, depth) {
|
|
4276
4420
|
return value.map((item, index) => {
|
|
4277
4421
|
if (typeof item === "string" && item.length === 24 && import_mongodb2.ObjectId.isValid(item) && shouldConvertQueryField(autoConvert, path5, depth)) {
|
|
4278
4422
|
return new import_mongodb2.ObjectId(item);
|
|
4279
4423
|
}
|
|
4280
|
-
if (
|
|
4424
|
+
if (isPlainQueryRecord(item)) {
|
|
4281
4425
|
return normalizeQueryFilter(item, autoConvert, `${path5}[${index}]`, depth + 1);
|
|
4282
4426
|
}
|
|
4283
4427
|
return item;
|
|
@@ -4293,7 +4437,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4293
4437
|
}
|
|
4294
4438
|
for (const [key, value] of Object.entries(filter)) {
|
|
4295
4439
|
if (key === "$and" || key === "$or" || key === "$nor") {
|
|
4296
|
-
result[key] = Array.isArray(value) ? value.map((item) => item
|
|
4440
|
+
result[key] = Array.isArray(value) ? value.map((item) => isPlainQueryRecord(item) ? normalizeQueryFilter(item, autoConvert, fieldPath, depth + 1) : item) : value;
|
|
4297
4441
|
continue;
|
|
4298
4442
|
}
|
|
4299
4443
|
const currentPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
@@ -4302,7 +4446,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4302
4446
|
result[key] = shouldConvert ? new import_mongodb2.ObjectId(value) : value;
|
|
4303
4447
|
} else if (Array.isArray(value)) {
|
|
4304
4448
|
result[key] = shouldConvert ? normalizeQueryArray(value, autoConvert, currentPath, depth + 1) : value;
|
|
4305
|
-
} else if (
|
|
4449
|
+
} else if (isPlainQueryRecord(value)) {
|
|
4306
4450
|
const nested = value;
|
|
4307
4451
|
const hasOperators = Object.keys(nested).some((k) => k.startsWith("$"));
|
|
4308
4452
|
if (hasOperators) {
|
|
@@ -4314,7 +4458,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4314
4458
|
);
|
|
4315
4459
|
} else if (shouldConvert && QUERY_OBJECTID_SCALAR_OPERATORS.has(op) && typeof opVal === "string" && opVal.length === 24 && import_mongodb2.ObjectId.isValid(opVal)) {
|
|
4316
4460
|
nestedResult[op] = new import_mongodb2.ObjectId(opVal);
|
|
4317
|
-
} else if (op === "$elemMatch" &&
|
|
4461
|
+
} else if (op === "$elemMatch" && isPlainQueryRecord(opVal)) {
|
|
4318
4462
|
nestedResult[op] = normalizeQueryFilter(opVal, autoConvert, currentPath, depth + 1);
|
|
4319
4463
|
} else {
|
|
4320
4464
|
nestedResult[op] = opVal;
|
|
@@ -4488,36 +4632,29 @@ async function isCollectionCacheBarrierActive(queryCache, collection, defaults =
|
|
|
4488
4632
|
buildCollectionCacheBarrierNamespaces(collection, defaults)
|
|
4489
4633
|
);
|
|
4490
4634
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
if (value
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
if (value instanceof
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
}
|
|
4515
|
-
return sorted;
|
|
4516
|
-
}
|
|
4517
|
-
return value;
|
|
4518
|
-
}
|
|
4519
|
-
function stableCacheKeyString(value) {
|
|
4520
|
-
return JSON.stringify(normalizeCacheKeyValue(value));
|
|
4635
|
+
var ORDERED_OPTION_FIELDS = /* @__PURE__ */ new Set(["query", "filter", "pipeline", "sort", "hint", "projection", "min", "max"]);
|
|
4636
|
+
function normalizeCacheKeyValue(value, mode) {
|
|
4637
|
+
if (value === null) return ["null"];
|
|
4638
|
+
if (value === void 0) return ["undefined"];
|
|
4639
|
+
if (typeof value === "number") return ["number", String(value)];
|
|
4640
|
+
if (typeof value === "bigint") return ["bigint", value.toString()];
|
|
4641
|
+
if (typeof value !== "object") return [typeof value, value];
|
|
4642
|
+
if (value instanceof Date) return ["date", String(value.getTime())];
|
|
4643
|
+
if (value instanceof RegExp) return ["regexp", value.source, value.flags];
|
|
4644
|
+
if (Array.isArray(value)) return ["array", value.map((item) => normalizeCacheKeyValue(item, mode))];
|
|
4645
|
+
const bsonType = value._bsontype;
|
|
4646
|
+
if (typeof bsonType === "string") {
|
|
4647
|
+
return ["bson", bsonType, import_mongodb2.BSON.EJSON.serialize(value, { relaxed: false })];
|
|
4648
|
+
}
|
|
4649
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0 && typeof item !== "function" && typeof item !== "symbol");
|
|
4650
|
+
if (mode === "options") entries.sort(([left], [right]) => left.localeCompare(right));
|
|
4651
|
+
return ["object", entries.map(([key, item]) => [
|
|
4652
|
+
key,
|
|
4653
|
+
normalizeCacheKeyValue(item, mode === "options" && ORDERED_OPTION_FIELDS.has(key) ? "document" : mode)
|
|
4654
|
+
])];
|
|
4655
|
+
}
|
|
4656
|
+
function stableCacheKeyString(value, mode = "options") {
|
|
4657
|
+
return `v2:${JSON.stringify(normalizeCacheKeyValue(value, mode))}`;
|
|
4521
4658
|
}
|
|
4522
4659
|
function buildResultCacheKeyOptions(options = {}) {
|
|
4523
4660
|
const keyOptions = {};
|
|
@@ -4638,7 +4775,7 @@ function buildFindCacheKey(collection, defaults = {}, query, options) {
|
|
|
4638
4775
|
...options ?? {}
|
|
4639
4776
|
});
|
|
4640
4777
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4641
|
-
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4778
|
+
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4642
4779
|
}
|
|
4643
4780
|
function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
4644
4781
|
const executeOptions = normalizeFindProjectionOptions({
|
|
@@ -4646,7 +4783,7 @@ function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
|
4646
4783
|
...options ?? {}
|
|
4647
4784
|
});
|
|
4648
4785
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4649
|
-
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4786
|
+
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4650
4787
|
}
|
|
4651
4788
|
function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
4652
4789
|
const merged = {
|
|
@@ -4656,7 +4793,7 @@ function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
|
4656
4793
|
const { cache: _cache, ...keyOptions } = merged;
|
|
4657
4794
|
void _cache;
|
|
4658
4795
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4659
|
-
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4796
|
+
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4660
4797
|
}
|
|
4661
4798
|
function buildDistinctCacheKey(collection, defaults = {}, field, query, options) {
|
|
4662
4799
|
const rawOptions = options ?? {};
|
|
@@ -4686,7 +4823,7 @@ function buildAggregateCacheKey(collection, defaults = {}, pipeline, options) {
|
|
|
4686
4823
|
...options ?? {}
|
|
4687
4824
|
};
|
|
4688
4825
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4689
|
-
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4826
|
+
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4690
4827
|
}
|
|
4691
4828
|
function buildFindOneByIdCacheKey(collection, defaults = {}, id, options) {
|
|
4692
4829
|
const objectId = parseRequiredObjectId(id);
|
|
@@ -4701,6 +4838,8 @@ function buildFindByIdsCacheKey(collection, defaults = {}, ids, options) {
|
|
|
4701
4838
|
function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
4702
4839
|
const ext = options;
|
|
4703
4840
|
const cursorTypes = ext.cursorTypes && typeof ext.cursorTypes === "object" && !Array.isArray(ext.cursorTypes) ? ext.cursorTypes : void 0;
|
|
4841
|
+
const { let: nestedLet, ...nestedOptions } = options.options ?? {};
|
|
4842
|
+
const effectiveLet = ext.let !== void 0 ? ext.let : nestedLet;
|
|
4704
4843
|
const payload = {
|
|
4705
4844
|
query: normalizeQueryForCache(options.query, defaults),
|
|
4706
4845
|
sort: normalizeSortShape(options.sort),
|
|
@@ -4722,9 +4861,10 @@ function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
|
4722
4861
|
hint: ext.hint,
|
|
4723
4862
|
collation: ext.collation,
|
|
4724
4863
|
batchSize: ext.batchSize,
|
|
4725
|
-
|
|
4864
|
+
let: effectiveLet,
|
|
4865
|
+
options: nestedOptions
|
|
4726
4866
|
};
|
|
4727
|
-
const keyHash = hashPayload(payload);
|
|
4867
|
+
const keyHash = hashPayload(stableCacheKeyString(payload));
|
|
4728
4868
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4729
4869
|
return { key: `findPage:${namespace}:${keyHash}`, keyHash };
|
|
4730
4870
|
}
|
|
@@ -5071,29 +5211,30 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5071
5211
|
if (rawOptions.upsert === true) {
|
|
5072
5212
|
throw createError(ErrorCodes.INVALID_ARGUMENT, 'versionMode "strict" does not support upsert.');
|
|
5073
5213
|
}
|
|
5074
|
-
const docs = await context.collection.find(filter, {
|
|
5075
|
-
...buildModelVersionLookupOptions(rawOptions, {
|
|
5076
|
-
_id: 1,
|
|
5077
|
-
[context.versionConfig.field]: 1
|
|
5078
|
-
})
|
|
5079
|
-
});
|
|
5080
5214
|
let matchedCount = 0;
|
|
5081
5215
|
let modifiedCount = 0;
|
|
5082
5216
|
const conflictedIds = [];
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
const
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5217
|
+
const skippedIds = [];
|
|
5218
|
+
for await (const batch of iterateModelCandidateBatches(context, filter, rawOptions, 200)) {
|
|
5219
|
+
for (const doc of batch) {
|
|
5220
|
+
const id = doc._id;
|
|
5221
|
+
const expectedVersion = assertNumericExpectedVersion(doc[context.versionConfig.field], "updateMany");
|
|
5222
|
+
const result = await context.collection.updateOne(
|
|
5223
|
+
buildStrictCandidateFilter(filter, id, context.versionConfig.field, expectedVersion),
|
|
5224
|
+
update,
|
|
5225
|
+
options
|
|
5226
|
+
);
|
|
5227
|
+
if ((result?.matchedCount ?? 0) === 0) {
|
|
5228
|
+
if (await isModelVersionConflict(context, filter, id, context.versionConfig.field, expectedVersion, options)) {
|
|
5229
|
+
conflictedIds.push(id);
|
|
5230
|
+
} else {
|
|
5231
|
+
skippedIds.push(id);
|
|
5232
|
+
}
|
|
5233
|
+
continue;
|
|
5234
|
+
}
|
|
5235
|
+
matchedCount += result.matchedCount ?? 0;
|
|
5236
|
+
modifiedCount += result.modifiedCount ?? 0;
|
|
5094
5237
|
}
|
|
5095
|
-
matchedCount += result.matchedCount ?? 0;
|
|
5096
|
-
modifiedCount += result.modifiedCount ?? 0;
|
|
5097
5238
|
}
|
|
5098
5239
|
return {
|
|
5099
5240
|
acknowledged: true,
|
|
@@ -5102,7 +5243,9 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5102
5243
|
upsertedCount: 0,
|
|
5103
5244
|
upsertedId: null,
|
|
5104
5245
|
conflictCount: conflictedIds.length,
|
|
5105
|
-
conflictedIds
|
|
5246
|
+
conflictedIds,
|
|
5247
|
+
skippedCount: skippedIds.length,
|
|
5248
|
+
skippedIds
|
|
5106
5249
|
};
|
|
5107
5250
|
}
|
|
5108
5251
|
async function runStrictUpdateBatch(context, filter, update, options) {
|
|
@@ -5125,7 +5268,7 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5125
5268
|
retryAttempts: _retryAttempts,
|
|
5126
5269
|
retryDelay: _retryDelay,
|
|
5127
5270
|
onRetry: _onRetry,
|
|
5128
|
-
sort
|
|
5271
|
+
sort: _sort,
|
|
5129
5272
|
...driverOptions
|
|
5130
5273
|
} = rawOptions;
|
|
5131
5274
|
void _batchSize;
|
|
@@ -5134,38 +5277,42 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5134
5277
|
void _retryAttempts;
|
|
5135
5278
|
void _retryDelay;
|
|
5136
5279
|
void _onRetry;
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
}),
|
|
5142
|
-
sort
|
|
5143
|
-
});
|
|
5280
|
+
void _sort;
|
|
5281
|
+
const countOptions = buildModelVersionLookupOptions(driverOptions, {});
|
|
5282
|
+
delete countOptions.projection;
|
|
5283
|
+
const totalCount = await context.collection.count(filter, countOptions);
|
|
5144
5284
|
const result = {
|
|
5145
5285
|
acknowledged: true,
|
|
5146
|
-
totalCount
|
|
5286
|
+
totalCount,
|
|
5147
5287
|
matchedCount: 0,
|
|
5148
5288
|
modifiedCount: 0,
|
|
5149
5289
|
upsertedCount: 0,
|
|
5150
|
-
batchCount:
|
|
5290
|
+
batchCount: 0,
|
|
5151
5291
|
errors: [],
|
|
5152
5292
|
retries: [],
|
|
5153
5293
|
conflictCount: 0,
|
|
5154
|
-
conflictedIds: []
|
|
5294
|
+
conflictedIds: [],
|
|
5295
|
+
skippedCount: 0,
|
|
5296
|
+
skippedIds: []
|
|
5155
5297
|
};
|
|
5156
|
-
for (
|
|
5157
|
-
|
|
5298
|
+
for await (const batch of iterateModelCandidateBatches(context, filter, driverOptions, batchSize)) {
|
|
5299
|
+
result.batchCount++;
|
|
5158
5300
|
for (const doc of batch) {
|
|
5159
5301
|
const id = doc._id;
|
|
5160
5302
|
const expectedVersion = assertNumericExpectedVersion(doc[context.versionConfig.field], "updateBatch");
|
|
5161
5303
|
const updateResult = await context.collection.updateOne(
|
|
5162
|
-
|
|
5304
|
+
buildStrictCandidateFilter(filter, id, context.versionConfig.field, expectedVersion),
|
|
5163
5305
|
update,
|
|
5164
5306
|
driverOptions
|
|
5165
5307
|
);
|
|
5166
5308
|
if ((updateResult?.matchedCount ?? 0) === 0) {
|
|
5167
|
-
|
|
5168
|
-
|
|
5309
|
+
if (await isModelVersionConflict(context, filter, id, context.versionConfig.field, expectedVersion, driverOptions)) {
|
|
5310
|
+
result.conflictCount = (result.conflictCount ?? 0) + 1;
|
|
5311
|
+
result.conflictedIds?.push(id);
|
|
5312
|
+
} else {
|
|
5313
|
+
result.skippedCount = (result.skippedCount ?? 0) + 1;
|
|
5314
|
+
result.skippedIds?.push(id);
|
|
5315
|
+
}
|
|
5169
5316
|
continue;
|
|
5170
5317
|
}
|
|
5171
5318
|
result.matchedCount += updateResult.matchedCount ?? 0;
|
|
@@ -5173,15 +5320,16 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5173
5320
|
}
|
|
5174
5321
|
if (typeof onProgress === "function") {
|
|
5175
5322
|
onProgress({
|
|
5176
|
-
currentBatch:
|
|
5177
|
-
totalBatches:
|
|
5323
|
+
currentBatch: result.batchCount,
|
|
5324
|
+
totalBatches: Math.ceil(totalCount / batchSize),
|
|
5178
5325
|
modified: result.modifiedCount,
|
|
5179
5326
|
matched: result.matchedCount,
|
|
5180
|
-
total:
|
|
5181
|
-
percentage:
|
|
5327
|
+
total: totalCount,
|
|
5328
|
+
percentage: totalCount > 0 ? Math.round(result.modifiedCount / totalCount * 100) : null,
|
|
5182
5329
|
errors: result.errors.length,
|
|
5183
5330
|
retries: result.retries.length,
|
|
5184
|
-
conflicts: result.conflictCount ?? 0
|
|
5331
|
+
conflicts: result.conflictCount ?? 0,
|
|
5332
|
+
skipped: result.skippedCount ?? 0
|
|
5185
5333
|
});
|
|
5186
5334
|
}
|
|
5187
5335
|
}
|
|
@@ -5194,10 +5342,7 @@ function buildSoftDeletePatch(config, now) {
|
|
|
5194
5342
|
return { [config.field]: config.type === "boolean" ? true : now };
|
|
5195
5343
|
}
|
|
5196
5344
|
function buildSoftDeleteFilter(filter, config) {
|
|
5197
|
-
return {
|
|
5198
|
-
...filter ?? {},
|
|
5199
|
-
...config?.enabled ? { [config.field]: null } : {}
|
|
5200
|
-
};
|
|
5345
|
+
return applyModelSoftDeleteFilter(filter, {}, config);
|
|
5201
5346
|
}
|
|
5202
5347
|
function applyModelIncrementVersion(field, increment, versionConfig) {
|
|
5203
5348
|
if (!versionConfig?.enabled || field === void 0) {
|
|
@@ -5237,7 +5382,8 @@ async function orchestrateModelInsertOne(context, document, options) {
|
|
|
5237
5382
|
payload = validateModelSchemaPayload({
|
|
5238
5383
|
validateEnabled: context.validateEnabled,
|
|
5239
5384
|
schemaCache: context.schemaCache,
|
|
5240
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5385
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5386
|
+
schemaError: context.schemaError
|
|
5241
5387
|
}, payload, options);
|
|
5242
5388
|
payload = applyModelInsertTimestamps(payload, context.timestampsConfig, () => context.nowDate());
|
|
5243
5389
|
payload = applyModelInsertVersion(payload, context.versionConfig);
|
|
@@ -5271,7 +5417,8 @@ async function orchestrateModelInsertMany(context, documents, options) {
|
|
|
5271
5417
|
doc = validateModelSchemaPayload({
|
|
5272
5418
|
validateEnabled: context.validateEnabled,
|
|
5273
5419
|
schemaCache: context.schemaCache,
|
|
5274
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5420
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5421
|
+
schemaError: context.schemaError
|
|
5275
5422
|
}, doc, resolvedOptions, { index });
|
|
5276
5423
|
doc = applyModelInsertTimestamps(doc, context.timestampsConfig, () => context.nowDate());
|
|
5277
5424
|
doc = applyModelInsertVersion(doc, context.versionConfig);
|
|
@@ -5356,7 +5503,8 @@ async function orchestrateModelReplaceOne(context, filter, replacement, options)
|
|
|
5356
5503
|
let nextReplacement = validateModelSchemaPayload({
|
|
5357
5504
|
validateEnabled: context.validateEnabled,
|
|
5358
5505
|
schemaCache: context.schemaCache,
|
|
5359
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5506
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5507
|
+
schemaError: context.schemaError
|
|
5360
5508
|
}, replacement, options);
|
|
5361
5509
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5362
5510
|
replacement,
|
|
@@ -5415,7 +5563,8 @@ async function orchestrateModelFindOneAndReplace(context, filter, replacement, o
|
|
|
5415
5563
|
let nextReplacement = validateModelSchemaPayload({
|
|
5416
5564
|
validateEnabled: context.validateEnabled,
|
|
5417
5565
|
schemaCache: context.schemaCache,
|
|
5418
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5566
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5567
|
+
schemaError: context.schemaError
|
|
5419
5568
|
}, replacement, options);
|
|
5420
5569
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5421
5570
|
replacement,
|
|
@@ -5527,7 +5676,8 @@ async function orchestrateModelInsertBatch(context, docs, options) {
|
|
|
5527
5676
|
record = validateModelSchemaPayload({
|
|
5528
5677
|
validateEnabled: context.validateEnabled,
|
|
5529
5678
|
schemaCache: context.schemaCache,
|
|
5530
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5679
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5680
|
+
schemaError: context.schemaError
|
|
5531
5681
|
}, record, resolvedOptions, { index });
|
|
5532
5682
|
record = applyModelInsertTimestamps(record, context.timestampsConfig, () => context.nowDate());
|
|
5533
5683
|
record = applyModelInsertVersion(record, context.versionConfig);
|
|
@@ -5605,7 +5755,7 @@ async function orchestrateModelDeleteOne(context, filter, options) {
|
|
|
5605
5755
|
let result;
|
|
5606
5756
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5607
5757
|
result = await context.collection.updateOne(
|
|
5608
|
-
|
|
5758
|
+
buildSoftDeleteFilter(filter, softDeleteConfig),
|
|
5609
5759
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5610
5760
|
options
|
|
5611
5761
|
);
|
|
@@ -5639,7 +5789,7 @@ async function orchestrateModelDeleteMany(context, filter, options) {
|
|
|
5639
5789
|
let result;
|
|
5640
5790
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5641
5791
|
result = await context.collection.updateMany(
|
|
5642
|
-
{
|
|
5792
|
+
{ $and: [filter ?? {}, buildModelVisibleSoftDeleteCondition(softDeleteConfig)] },
|
|
5643
5793
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5644
5794
|
options
|
|
5645
5795
|
);
|
|
@@ -5803,7 +5953,7 @@ async function inspectDeclaredRelationUsage(context, targets, inputOptions, targ
|
|
|
5803
5953
|
[inbound.localField]: { $in: targetValues }
|
|
5804
5954
|
};
|
|
5805
5955
|
const sourceSoftDelete = resolveModelSoftDeleteConfig(inbound.definition);
|
|
5806
|
-
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery,
|
|
5956
|
+
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery, buildModelVisibleSoftDeleteCondition(sourceSoftDelete)] } : baseQuery;
|
|
5807
5957
|
try {
|
|
5808
5958
|
const collection = context.runtime.scopedCollection(inbound.sourceCollection, scope);
|
|
5809
5959
|
if (!collection) {
|
|
@@ -5985,7 +6135,7 @@ async function executeProtectedRelationDelete(host, filter, options, force) {
|
|
|
5985
6135
|
const deleteOptions = stripRelationUsageOptions(options);
|
|
5986
6136
|
let target;
|
|
5987
6137
|
try {
|
|
5988
|
-
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter :
|
|
6138
|
+
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter : applyModelSoftDeleteFilter(filter, void 0, host.softDeleteConfig);
|
|
5989
6139
|
const rawTarget = await host.collection.findOne(
|
|
5990
6140
|
targetFilter,
|
|
5991
6141
|
relationTargetReadOptions(options, buildRelationUsageTargetProjection(host.modelName))
|
|
@@ -6069,7 +6219,7 @@ var ModelInstance = class {
|
|
|
6069
6219
|
this.softDeleteConfig = this._softDeleteConfig;
|
|
6070
6220
|
this._versionConfig = resolveModelVersionConfig(options.definition);
|
|
6071
6221
|
this._v1HooksFactory = resolveModelHooksFactory(options.definition);
|
|
6072
|
-
scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6222
|
+
if (!this._schemaError) scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6073
6223
|
runtime: this.runtime,
|
|
6074
6224
|
dbName: options.dbName,
|
|
6075
6225
|
poolName: options.poolName,
|
|
@@ -6129,46 +6279,6 @@ var ModelInstance = class {
|
|
|
6129
6279
|
}
|
|
6130
6280
|
return result;
|
|
6131
6281
|
}
|
|
6132
|
-
isVisibleBySoftDelete(doc, options) {
|
|
6133
|
-
if (!doc || !this._softDeleteConfig?.enabled) {
|
|
6134
|
-
return Boolean(doc);
|
|
6135
|
-
}
|
|
6136
|
-
const rawOptions = options ?? {};
|
|
6137
|
-
if (rawOptions.withDeleted) {
|
|
6138
|
-
return true;
|
|
6139
|
-
}
|
|
6140
|
-
const value = doc[this._softDeleteConfig.field];
|
|
6141
|
-
if (rawOptions.onlyDeleted) {
|
|
6142
|
-
return value !== void 0 && value !== null;
|
|
6143
|
-
}
|
|
6144
|
-
return value === void 0 || value === null;
|
|
6145
|
-
}
|
|
6146
|
-
filterVisibleBySoftDelete(docs, options) {
|
|
6147
|
-
return docs.filter((doc) => this.isVisibleBySoftDelete(doc, options));
|
|
6148
|
-
}
|
|
6149
|
-
applySoftDeleteFindPageOptions(options) {
|
|
6150
|
-
const rawOptions = options ?? {};
|
|
6151
|
-
return {
|
|
6152
|
-
...rawOptions,
|
|
6153
|
-
query: applyModelSoftDeleteFilter(rawOptions.query, rawOptions, this._softDeleteConfig)
|
|
6154
|
-
};
|
|
6155
|
-
}
|
|
6156
|
-
applySoftDeleteAggregatePipeline(pipeline, options) {
|
|
6157
|
-
if (!this._softDeleteConfig?.enabled) {
|
|
6158
|
-
return pipeline ?? [];
|
|
6159
|
-
}
|
|
6160
|
-
const rawOptions = options ?? {};
|
|
6161
|
-
if (rawOptions.withDeleted) {
|
|
6162
|
-
return pipeline ?? [];
|
|
6163
|
-
}
|
|
6164
|
-
const softDeleteMatch = applyModelSoftDeleteFilter({}, rawOptions, this._softDeleteConfig);
|
|
6165
|
-
const matchStage = { $match: softDeleteMatch };
|
|
6166
|
-
const stages = [...pipeline ?? []];
|
|
6167
|
-
if (stages.length > 0 && stages[0] && typeof stages[0] === "object" && "$geoNear" in stages[0]) {
|
|
6168
|
-
return [stages[0], matchStage, ...stages.slice(1)];
|
|
6169
|
-
}
|
|
6170
|
-
return [matchStage, ...stages];
|
|
6171
|
-
}
|
|
6172
6282
|
// ── public API ────────────────────────────────────────────────────────────────
|
|
6173
6283
|
find(query, options) {
|
|
6174
6284
|
return new PopulatePromise(async (paths) => {
|
|
@@ -6204,8 +6314,12 @@ var ModelInstance = class {
|
|
|
6204
6314
|
}
|
|
6205
6315
|
findOneById(id, options) {
|
|
6206
6316
|
return new PopulatePromise(async (paths) => {
|
|
6207
|
-
const
|
|
6208
|
-
|
|
6317
|
+
const lookupById = (nextId, nextOptions) => this._softDeleteConfig?.enabled ? this.collection.findOne(
|
|
6318
|
+
applyModelSoftDeleteFilter({ _id: parseRequiredObjectId(nextId) }, nextOptions, this._softDeleteConfig),
|
|
6319
|
+
nextOptions
|
|
6320
|
+
) : this.collection.findOneById(nextId, nextOptions);
|
|
6321
|
+
const doc = this._v1HooksFactory ? await this.runV1HookedOperation("find", [id, options], lookupById) : await lookupById(id, options);
|
|
6322
|
+
return this.populateSingle(this.hydrateDocument(doc), paths);
|
|
6209
6323
|
});
|
|
6210
6324
|
}
|
|
6211
6325
|
findById(id, options) {
|
|
@@ -6213,13 +6327,25 @@ var ModelInstance = class {
|
|
|
6213
6327
|
}
|
|
6214
6328
|
findByIds(ids, options) {
|
|
6215
6329
|
return new PopulatePromise(async (paths) => {
|
|
6216
|
-
|
|
6217
|
-
|
|
6330
|
+
let stripField = false;
|
|
6331
|
+
const lookupByIds = (nextIds, nextOptions) => {
|
|
6332
|
+
const projection = withSoftDeleteProjection(nextOptions, this._softDeleteConfig);
|
|
6333
|
+
stripField = projection.stripField;
|
|
6334
|
+
return this.collection.findByIds(nextIds, projection.options);
|
|
6335
|
+
};
|
|
6336
|
+
const docs = this._v1HooksFactory ? await this.runV1HookedOperation("find", [ids, options], lookupByIds) : await lookupByIds(ids, options);
|
|
6337
|
+
const visible = filterVisibleBySoftDelete(docs, options, this._softDeleteConfig);
|
|
6338
|
+
const projected = stripField && this._softDeleteConfig ? visible.map((doc) => {
|
|
6339
|
+
const copy = { ...doc };
|
|
6340
|
+
delete copy[this._softDeleteConfig.field];
|
|
6341
|
+
return copy;
|
|
6342
|
+
}) : visible;
|
|
6343
|
+
return this.populateDocuments(this.hydrateDocuments(projected), paths);
|
|
6218
6344
|
});
|
|
6219
6345
|
}
|
|
6220
6346
|
findPage(options) {
|
|
6221
6347
|
return new PopulatePromise(async (paths) => {
|
|
6222
|
-
const filteredOptions =
|
|
6348
|
+
const filteredOptions = applySoftDeleteFindPageOptions(options, this._softDeleteConfig);
|
|
6223
6349
|
const result = this._v1HooksFactory ? await this.runV1HookedOperation("find", [filteredOptions], (nextOptions) => this.collection.findPage(nextOptions)) : await this.collection.findPage(filteredOptions);
|
|
6224
6350
|
return {
|
|
6225
6351
|
...result,
|
|
@@ -6371,7 +6497,7 @@ var ModelInstance = class {
|
|
|
6371
6497
|
return this.runV1HookedOperation("find", [key, filteredQuery, options], (nextKey, nextQuery, nextOptions) => this.collection.distinct(nextKey, nextQuery, nextOptions));
|
|
6372
6498
|
}
|
|
6373
6499
|
aggregate(pipeline, options) {
|
|
6374
|
-
const filteredPipeline =
|
|
6500
|
+
const filteredPipeline = applySoftDeleteAggregatePipeline(pipeline, options, this._softDeleteConfig);
|
|
6375
6501
|
return this.runV1HookedOperation("find", [filteredPipeline, options], (nextPipeline, nextOptions) => {
|
|
6376
6502
|
const execute = () => this.collection.aggregate(nextPipeline, nextOptions);
|
|
6377
6503
|
if (resolveAggregateWriteTarget(Array.isArray(nextPipeline) ? nextPipeline : [])) {
|
|
@@ -6500,13 +6626,14 @@ var ModelInstance = class {
|
|
|
6500
6626
|
schemaValidationContext: {
|
|
6501
6627
|
validateEnabled: this._validateEnabled,
|
|
6502
6628
|
schemaCache: this._schemaCache,
|
|
6503
|
-
schemaValidateFn: this._schemaValidateFn
|
|
6629
|
+
schemaValidateFn: this._schemaValidateFn,
|
|
6630
|
+
schemaError: this._schemaError
|
|
6504
6631
|
},
|
|
6505
6632
|
nowFactory: () => this.nowDate()
|
|
6506
6633
|
}));
|
|
6507
6634
|
}
|
|
6508
6635
|
async removeDocument(document) {
|
|
6509
|
-
return this.runModelWrite(() => removeModelDocument(this.collection, document));
|
|
6636
|
+
return this.runModelWrite(() => removeModelDocument(this.collection, document, (filter) => this.deleteOne(filter)));
|
|
6510
6637
|
}
|
|
6511
6638
|
applyDefaults(document) {
|
|
6512
6639
|
return applyModelDefaults(this.definition, document);
|
|
@@ -6524,6 +6651,7 @@ var ModelInstance = class {
|
|
|
6524
6651
|
validateEnabled: this._validateEnabled,
|
|
6525
6652
|
schemaCache: this._schemaCache,
|
|
6526
6653
|
schemaValidateFn: this._schemaValidateFn,
|
|
6654
|
+
schemaError: this._schemaError,
|
|
6527
6655
|
hooksFactory: this._v1HooksFactory,
|
|
6528
6656
|
runHook: (hookName, context) => this.runHook(hookName, context)
|
|
6529
6657
|
};
|
|
@@ -6539,6 +6667,11 @@ var ModelInstance = class {
|
|
|
6539
6667
|
|
|
6540
6668
|
// src/capabilities/transaction/index.ts
|
|
6541
6669
|
var import_node_crypto3 = require("node:crypto");
|
|
6670
|
+
function assertManagedTransactionWriteAllowed(options) {
|
|
6671
|
+
const session = options?.session;
|
|
6672
|
+
const managed = session?.__monSQLizeTransaction;
|
|
6673
|
+
if (typeof managed?.assertAcceptingWrites === "function") managed.assertAcceptingWrites();
|
|
6674
|
+
}
|
|
6542
6675
|
function toPublicTransactionStatus(state) {
|
|
6543
6676
|
return state === "active" ? "started" : state;
|
|
6544
6677
|
}
|
|
@@ -6639,6 +6772,7 @@ var Transaction = class {
|
|
|
6639
6772
|
this.options = options;
|
|
6640
6773
|
this.id = `tx_${(0, import_node_crypto3.randomBytes)(8).toString("hex")}`;
|
|
6641
6774
|
this.state = "pending";
|
|
6775
|
+
this.acceptingWrites = false;
|
|
6642
6776
|
this.startedAt = null;
|
|
6643
6777
|
this.timeoutTimer = null;
|
|
6644
6778
|
this.pendingInvalidations = /* @__PURE__ */ new Set();
|
|
@@ -6657,6 +6791,7 @@ var Transaction = class {
|
|
|
6657
6791
|
}
|
|
6658
6792
|
this.session.startTransaction(this.options.transactionOptions);
|
|
6659
6793
|
this.state = "active";
|
|
6794
|
+
this.acceptingWrites = true;
|
|
6660
6795
|
this.startedAt = Date.now();
|
|
6661
6796
|
const timeout = this.options.timeout ?? 3e4;
|
|
6662
6797
|
if (timeout > 0) {
|
|
@@ -6679,6 +6814,7 @@ var Transaction = class {
|
|
|
6679
6814
|
if (this.state !== "active") {
|
|
6680
6815
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
6681
6816
|
}
|
|
6817
|
+
this.acceptingWrites = false;
|
|
6682
6818
|
if (typeof this.session.commitTransaction === "function") {
|
|
6683
6819
|
await commitTransactionWithRetry(this.session, this.options.logger);
|
|
6684
6820
|
}
|
|
@@ -6704,6 +6840,7 @@ var Transaction = class {
|
|
|
6704
6840
|
* @since v1.4.0
|
|
6705
6841
|
*/
|
|
6706
6842
|
async abort() {
|
|
6843
|
+
this.acceptingWrites = false;
|
|
6707
6844
|
if (this.state !== "pending" && this.state !== "active") {
|
|
6708
6845
|
return;
|
|
6709
6846
|
}
|
|
@@ -6728,10 +6865,16 @@ var Transaction = class {
|
|
|
6728
6865
|
* @since v1.4.0
|
|
6729
6866
|
*/
|
|
6730
6867
|
async end() {
|
|
6868
|
+
this.acceptingWrites = false;
|
|
6731
6869
|
this.clearTimeout();
|
|
6732
6870
|
this.options.lockManager?.releaseLocks(this.id);
|
|
6733
6871
|
await this.session.endSession();
|
|
6734
6872
|
}
|
|
6873
|
+
assertAcceptingWrites() {
|
|
6874
|
+
if (this.state !== "active" || !this.acceptingWrites) {
|
|
6875
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Managed transaction ${this.id} is no longer accepting writes.`);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6735
6878
|
/**
|
|
6736
6879
|
* Record a cache invalidation intent.
|
|
6737
6880
|
* @since v1.4.0
|
|
@@ -6905,12 +7048,12 @@ var TransactionManager = class {
|
|
|
6905
7048
|
return result;
|
|
6906
7049
|
} catch (error) {
|
|
6907
7050
|
lastError = error;
|
|
6908
|
-
await transaction.abort();
|
|
6909
|
-
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
6910
7051
|
if (transaction.state === "committed") {
|
|
6911
7052
|
throw error;
|
|
6912
7053
|
}
|
|
6913
|
-
|
|
7054
|
+
await transaction.abort();
|
|
7055
|
+
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
7056
|
+
if (!enableRetry || attempt === maxRetries || isUnknownTransactionCommitResult(error) || !isTransientTransactionError(error)) {
|
|
6914
7057
|
throw error;
|
|
6915
7058
|
}
|
|
6916
7059
|
await sleep(retryDelay * Math.pow(retryBackoff, attempt));
|
|
@@ -7011,22 +7154,34 @@ function stringifySessionId(id) {
|
|
|
7011
7154
|
}
|
|
7012
7155
|
return String(id);
|
|
7013
7156
|
}
|
|
7014
|
-
function
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7157
|
+
function transactionErrorChain(error) {
|
|
7158
|
+
const chain = [];
|
|
7159
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7160
|
+
let current = error;
|
|
7161
|
+
while (current && typeof current === "object" && chain.length < 8 && !seen.has(current)) {
|
|
7162
|
+
seen.add(current);
|
|
7163
|
+
const candidate = current;
|
|
7164
|
+
chain.push(candidate);
|
|
7165
|
+
current = candidate.cause;
|
|
7166
|
+
}
|
|
7167
|
+
return chain;
|
|
7168
|
+
}
|
|
7169
|
+
function hasTransactionErrorLabel(candidate, label) {
|
|
7170
|
+
if (Array.isArray(candidate.errorLabels) && candidate.errorLabels.includes(label)) return true;
|
|
7171
|
+
if (typeof candidate.hasErrorLabel === "function") {
|
|
7172
|
+
try {
|
|
7173
|
+
return Boolean(candidate.hasErrorLabel.call(candidate, label));
|
|
7174
|
+
} catch {
|
|
7175
|
+
}
|
|
7021
7176
|
}
|
|
7022
|
-
return
|
|
7177
|
+
return false;
|
|
7023
7178
|
}
|
|
7024
7179
|
function isUnknownTransactionCommitResult(error) {
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
return
|
|
7180
|
+
return transactionErrorChain(error).some((candidate) => hasTransactionErrorLabel(candidate, "UnknownTransactionCommitResult"));
|
|
7181
|
+
}
|
|
7182
|
+
function isTransientTransactionError(error) {
|
|
7183
|
+
if (isUnknownTransactionCommitResult(error)) return false;
|
|
7184
|
+
return transactionErrorChain(error).some((candidate) => hasTransactionErrorLabel(candidate, "TransientTransactionError") || candidate.code === 112 || candidate.code === 117 || candidate.codeName === "WriteConflict" || candidate.codeName === "LockTimeout");
|
|
7030
7185
|
}
|
|
7031
7186
|
async function commitTransactionWithRetry(session, logger) {
|
|
7032
7187
|
const maxAttempts = 3;
|
|
@@ -7542,26 +7697,27 @@ var CountQueue = class {
|
|
|
7542
7697
|
* @returns The result of fn
|
|
7543
7698
|
* @throws A controlled error when the queue is full or the wait times out
|
|
7544
7699
|
*/
|
|
7545
|
-
async execute(fn) {
|
|
7700
|
+
async execute(fn, options = {}) {
|
|
7546
7701
|
const startTime = Date.now();
|
|
7702
|
+
const signal = options.signal;
|
|
7703
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7547
7704
|
if (this.running >= this.concurrency) {
|
|
7548
7705
|
if (this.queue.length >= this.maxQueueSize) {
|
|
7549
7706
|
this.stats.rejected += 1;
|
|
7550
7707
|
throw createError(ErrorCodes.INVALID_OPERATION, `Count queue is full (${this.maxQueueSize})`);
|
|
7551
7708
|
}
|
|
7552
7709
|
this.stats.queued += 1;
|
|
7553
|
-
await this._waitInQueue(startTime);
|
|
7710
|
+
await this._waitInQueue(startTime, signal);
|
|
7554
7711
|
}
|
|
7712
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7555
7713
|
this.running += 1;
|
|
7556
7714
|
this.stats.executed += 1;
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
return await this._executeWithTimeout(fn, remainingMs);
|
|
7561
|
-
} finally {
|
|
7715
|
+
const elapsed = Date.now() - startTime;
|
|
7716
|
+
const remainingMs = Math.max(1, this.timeout - elapsed);
|
|
7717
|
+
return this._executeWithTimeout(fn, remainingMs, signal, () => {
|
|
7562
7718
|
this.running -= 1;
|
|
7563
7719
|
this._wakeNext();
|
|
7564
|
-
}
|
|
7720
|
+
});
|
|
7565
7721
|
}
|
|
7566
7722
|
/**
|
|
7567
7723
|
* Get queue statistics (including live state).
|
|
@@ -7596,17 +7752,28 @@ var CountQueue = class {
|
|
|
7596
7752
|
const entry = this.queue.shift();
|
|
7597
7753
|
if (entry) {
|
|
7598
7754
|
clearTimeout(entry.timer);
|
|
7755
|
+
entry.cleanup();
|
|
7599
7756
|
entry.reject(createError(ErrorCodes.INVALID_OPERATION, "Count queue was cleared before execution"));
|
|
7600
7757
|
}
|
|
7601
7758
|
}
|
|
7602
7759
|
}
|
|
7603
|
-
_waitInQueue(startTime) {
|
|
7760
|
+
_waitInQueue(startTime, signal) {
|
|
7604
7761
|
return new Promise((resolve, reject) => {
|
|
7605
7762
|
let entry;
|
|
7763
|
+
const onAbort = () => {
|
|
7764
|
+
const index = this.queue.indexOf(entry);
|
|
7765
|
+
if (index !== -1) {
|
|
7766
|
+
this.queue.splice(index, 1);
|
|
7767
|
+
clearTimeout(entry.timer);
|
|
7768
|
+
entry.cleanup();
|
|
7769
|
+
reject(createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted"));
|
|
7770
|
+
}
|
|
7771
|
+
};
|
|
7606
7772
|
const timer = setTimeout(() => {
|
|
7607
7773
|
const index = this.queue.indexOf(entry);
|
|
7608
7774
|
if (index !== -1) {
|
|
7609
7775
|
this.queue.splice(index, 1);
|
|
7776
|
+
entry.cleanup();
|
|
7610
7777
|
this.stats.timeout += 1;
|
|
7611
7778
|
reject(createError(ErrorCodes.OPERATION_TIMEOUT, `Count queue wait timeout (${this.timeout}ms)`));
|
|
7612
7779
|
}
|
|
@@ -7619,9 +7786,12 @@ var CountQueue = class {
|
|
|
7619
7786
|
},
|
|
7620
7787
|
reject,
|
|
7621
7788
|
timer,
|
|
7622
|
-
startTime
|
|
7789
|
+
startTime,
|
|
7790
|
+
cleanup: () => signal?.removeEventListener("abort", onAbort)
|
|
7623
7791
|
};
|
|
7624
7792
|
this.queue.push(entry);
|
|
7793
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7794
|
+
if (signal?.aborted) onAbort();
|
|
7625
7795
|
});
|
|
7626
7796
|
}
|
|
7627
7797
|
_wakeNext() {
|
|
@@ -7629,6 +7799,7 @@ var CountQueue = class {
|
|
|
7629
7799
|
const entry = this.queue.shift();
|
|
7630
7800
|
if (entry) {
|
|
7631
7801
|
clearTimeout(entry.timer);
|
|
7802
|
+
entry.cleanup();
|
|
7632
7803
|
entry.resolve();
|
|
7633
7804
|
}
|
|
7634
7805
|
}
|
|
@@ -7645,10 +7816,12 @@ var CountQueue = class {
|
|
|
7645
7816
|
}
|
|
7646
7817
|
return value;
|
|
7647
7818
|
}
|
|
7648
|
-
_executeWithTimeout(fn, timeoutMs
|
|
7819
|
+
_executeWithTimeout(fn, timeoutMs, signal, release) {
|
|
7649
7820
|
let timer = null;
|
|
7650
7821
|
const controller = new AbortController();
|
|
7651
|
-
|
|
7822
|
+
let rejectCancellation;
|
|
7823
|
+
const cancellation = new Promise((_, reject) => {
|
|
7824
|
+
rejectCancellation = reject;
|
|
7652
7825
|
timer = setTimeout(
|
|
7653
7826
|
() => {
|
|
7654
7827
|
controller.abort();
|
|
@@ -7658,14 +7831,22 @@ var CountQueue = class {
|
|
|
7658
7831
|
timeoutMs
|
|
7659
7832
|
);
|
|
7660
7833
|
});
|
|
7834
|
+
const onAbort = () => {
|
|
7835
|
+
controller.abort();
|
|
7836
|
+
rejectCancellation(createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted"));
|
|
7837
|
+
};
|
|
7838
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7661
7839
|
let taskPromise;
|
|
7662
7840
|
try {
|
|
7663
7841
|
taskPromise = Promise.resolve(fn(controller.signal));
|
|
7664
7842
|
} catch (error) {
|
|
7665
7843
|
taskPromise = Promise.reject(error);
|
|
7666
7844
|
}
|
|
7667
|
-
|
|
7845
|
+
void taskPromise.then(release, release);
|
|
7846
|
+
if (signal?.aborted) onAbort();
|
|
7847
|
+
return Promise.race([taskPromise, cancellation]).finally(() => {
|
|
7668
7848
|
if (timer !== null) clearTimeout(timer);
|
|
7849
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7669
7850
|
});
|
|
7670
7851
|
}
|
|
7671
7852
|
_updateWaitTimeStats(waitTime) {
|
|
@@ -8194,6 +8375,9 @@ var ConnectionPoolManager = class {
|
|
|
8194
8375
|
this._closed = false;
|
|
8195
8376
|
this._configs = /* @__PURE__ */ new Map();
|
|
8196
8377
|
this._pendingAdds = /* @__PURE__ */ new Set();
|
|
8378
|
+
this._pendingAddTasks = /* @__PURE__ */ new Set();
|
|
8379
|
+
this._generation = 0;
|
|
8380
|
+
this._closePromise = null;
|
|
8197
8381
|
this.logger = options.logger ?? null;
|
|
8198
8382
|
this.maxPoolsCount = options.maxPoolsCount ?? 10;
|
|
8199
8383
|
this.strategy = options.poolStrategy ?? "auto";
|
|
@@ -8220,35 +8404,21 @@ var ConnectionPoolManager = class {
|
|
|
8220
8404
|
*/
|
|
8221
8405
|
async addPool(config) {
|
|
8222
8406
|
validatePoolConfig(config);
|
|
8407
|
+
if (this._closed) {
|
|
8408
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Connection pool manager is closed");
|
|
8409
|
+
}
|
|
8223
8410
|
if (this.pools.has(config.name) || this._pendingAdds.has(config.name)) {
|
|
8224
8411
|
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8225
8412
|
}
|
|
8226
|
-
if (this.maxPoolsCount > 0 && this.pools.size >= this.maxPoolsCount) {
|
|
8413
|
+
if (this.maxPoolsCount > 0 && this.pools.size + this._pendingAdds.size >= this.maxPoolsCount) {
|
|
8227
8414
|
throw createError(ErrorCodes.INVALID_CONFIG, `Maximum pool count (${this.maxPoolsCount}) reached`);
|
|
8228
8415
|
}
|
|
8229
8416
|
this._pendingAdds.add(config.name);
|
|
8417
|
+
const generation = this._generation;
|
|
8418
|
+
const task = this._finishAddPool(config, generation);
|
|
8419
|
+
this._pendingAddTasks.add(task);
|
|
8230
8420
|
try {
|
|
8231
|
-
|
|
8232
|
-
if (this.pools.has(config.name)) {
|
|
8233
|
-
await client.close().catch(() => {
|
|
8234
|
-
});
|
|
8235
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8236
|
-
}
|
|
8237
|
-
this.pools.set(config.name, {
|
|
8238
|
-
client,
|
|
8239
|
-
config,
|
|
8240
|
-
createdAt: Date.now()
|
|
8241
|
-
});
|
|
8242
|
-
this.healthStatus.set(config.name, {
|
|
8243
|
-
status: "up",
|
|
8244
|
-
consecutiveFailures: 0,
|
|
8245
|
-
lastCheckTime: null,
|
|
8246
|
-
lastError: null,
|
|
8247
|
-
uptime: 0
|
|
8248
|
-
});
|
|
8249
|
-
this.stats.set(config.name, createEmptyPoolStats(config.name));
|
|
8250
|
-
this._configs.set(config.name, config);
|
|
8251
|
-
this._healthChecker.register(config.name, config.healthCheck ?? {});
|
|
8421
|
+
await task;
|
|
8252
8422
|
} catch (err) {
|
|
8253
8423
|
const error = err;
|
|
8254
8424
|
const msg = error.message ?? "";
|
|
@@ -8262,7 +8432,30 @@ var ConnectionPoolManager = class {
|
|
|
8262
8432
|
throw err;
|
|
8263
8433
|
} finally {
|
|
8264
8434
|
this._pendingAdds.delete(config.name);
|
|
8435
|
+
this._pendingAddTasks.delete(task);
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
async _finishAddPool(config, generation) {
|
|
8439
|
+
const client = await this.clientFactory(config);
|
|
8440
|
+
if (this._closed || generation !== this._generation || this.pools.has(config.name)) {
|
|
8441
|
+
await client.close().catch(() => {
|
|
8442
|
+
});
|
|
8443
|
+
throw createError(
|
|
8444
|
+
this._closed || generation !== this._generation ? ErrorCodes.INVALID_OPERATION : ErrorCodes.INVALID_CONFIG,
|
|
8445
|
+
this._closed || generation !== this._generation ? "Connection pool manager closed while adding a pool" : `Pool '${config.name}' already exists`
|
|
8446
|
+
);
|
|
8265
8447
|
}
|
|
8448
|
+
this.pools.set(config.name, { client, config, createdAt: Date.now() });
|
|
8449
|
+
this.healthStatus.set(config.name, {
|
|
8450
|
+
status: "up",
|
|
8451
|
+
consecutiveFailures: 0,
|
|
8452
|
+
lastCheckTime: null,
|
|
8453
|
+
lastError: null,
|
|
8454
|
+
uptime: 0
|
|
8455
|
+
});
|
|
8456
|
+
this.stats.set(config.name, createEmptyPoolStats(config.name));
|
|
8457
|
+
this._configs.set(config.name, config);
|
|
8458
|
+
this._healthChecker.register(config.name, config.healthCheck ?? {});
|
|
8266
8459
|
}
|
|
8267
8460
|
/**
|
|
8268
8461
|
* Remove a connection pool.
|
|
@@ -8419,16 +8612,24 @@ var ConnectionPoolManager = class {
|
|
|
8419
8612
|
* @since v1.0.8
|
|
8420
8613
|
*/
|
|
8421
8614
|
async close() {
|
|
8615
|
+
if (this._closePromise) return this._closePromise;
|
|
8616
|
+
this._closed = true;
|
|
8617
|
+
this._generation += 1;
|
|
8618
|
+
this._closePromise = this._closeAllPools();
|
|
8619
|
+
return this._closePromise;
|
|
8620
|
+
}
|
|
8621
|
+
async _closeAllPools() {
|
|
8422
8622
|
this.stopHealthCheck();
|
|
8423
8623
|
this._healthChecker.stop();
|
|
8424
8624
|
this._stats.close();
|
|
8625
|
+
await Promise.allSettled([...this._pendingAddTasks]);
|
|
8425
8626
|
for (const pool of this.pools.values()) {
|
|
8426
8627
|
await pool.client.close();
|
|
8427
8628
|
}
|
|
8428
8629
|
this.pools.clear();
|
|
8429
8630
|
this.healthStatus.clear();
|
|
8430
8631
|
this.stats.clear();
|
|
8431
|
-
this.
|
|
8632
|
+
this._configs.clear();
|
|
8432
8633
|
}
|
|
8433
8634
|
// ─── v1 compat methods ───────────────────────────────────────────────────────────
|
|
8434
8635
|
/** v1 compat: get pool health Map */
|
|
@@ -8616,6 +8817,7 @@ async function initializeDistributedCacheInvalidator(options, cache, logger) {
|
|
|
8616
8817
|
logger
|
|
8617
8818
|
});
|
|
8618
8819
|
} catch (err) {
|
|
8820
|
+
if (err?.code === ErrorCodes.INVALID_CONFIG) throw err;
|
|
8619
8821
|
logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 check Redis config or package installation completeness.", err);
|
|
8620
8822
|
return null;
|
|
8621
8823
|
}
|
|
@@ -8694,6 +8896,33 @@ async function loadModelFiles(options, logger, opts = {}) {
|
|
|
8694
8896
|
}
|
|
8695
8897
|
}
|
|
8696
8898
|
|
|
8899
|
+
// src/entry/runtime-health.ts
|
|
8900
|
+
function summarizeRuntimeHealth(cacheOption, cache, poolManager, invalidator) {
|
|
8901
|
+
const cacheStats = cache.getStats?.();
|
|
8902
|
+
const poolHealth = poolManager?.getHealthStatus();
|
|
8903
|
+
const distributedStats = invalidator?.getStats() ?? null;
|
|
8904
|
+
const cacheInput = cacheOption;
|
|
8905
|
+
const cacheEnabled = cacheInput?.enabled !== false;
|
|
8906
|
+
const distributedEnabled = cacheInput?.distributed !== void 0 && cacheInput.distributed.enabled !== false;
|
|
8907
|
+
const cacheProbe = cacheEnabled ? cache.getHealthStatus?.() : void 0;
|
|
8908
|
+
const cacheProbeStatus = cacheProbe === true || cacheProbe === "up" ? "up" : cacheProbe === false || cacheProbe === "down" ? "down" : "unknown";
|
|
8909
|
+
const redisState = invalidator;
|
|
8910
|
+
const redisStatuses = [redisState?.pub?.status, redisState?.sub?.status];
|
|
8911
|
+
const distributedStatus = !distributedEnabled || !invalidator ? "unknown" : redisStatuses.every((status) => status === "ready") ? "up" : redisStatuses.some((status) => status === "end" || status === "close") ? "down" : "unknown";
|
|
8912
|
+
const poolStates = Object.values(poolHealth ?? {}).map((health) => health.status);
|
|
8913
|
+
const poolsStatus = poolStates.length === 0 ? "unknown" : poolStates.some((status) => status === "down") ? "down" : poolStates.every((status) => status === "up") ? "up" : "unknown";
|
|
8914
|
+
return {
|
|
8915
|
+
cacheStats,
|
|
8916
|
+
poolHealth,
|
|
8917
|
+
distributedStats,
|
|
8918
|
+
cacheEnabled,
|
|
8919
|
+
distributedEnabled,
|
|
8920
|
+
cacheProbeStatus,
|
|
8921
|
+
distributedStatus,
|
|
8922
|
+
poolsStatus
|
|
8923
|
+
};
|
|
8924
|
+
}
|
|
8925
|
+
|
|
8697
8926
|
// src/entry/runtime-defaults.ts
|
|
8698
8927
|
function isPlainObject3(value) {
|
|
8699
8928
|
if (value === null || typeof value !== "object") return false;
|
|
@@ -9344,36 +9573,36 @@ var FUNC_REGEX = /^(CONCAT|UPPER|LOWER|TRIM|SUBSTR|LENGTH|ABS|CEIL|FLOOR|ROUND|S
|
|
|
9344
9573
|
var IS_FUNC_CALL_RE = /^(CONCAT|UPPER|LOWER|TRIM|SUBSTR|LENGTH|ABS|CEIL|FLOOR|ROUND|SQRT|POW|SIZE|IN|SLICE|FIRST|LAST|ARRAY_ELEM_AT|FILTER|MAP|INDEX_OF|CONCAT_ARRAYS|TYPE|NOT|EXISTS|IS_NUMBER|IS_ARRAY|SUM|AVG|MAX|MIN|COUNT|PUSH|ADD_TO_SET|YEAR|MONTH|DAY_OF_MONTH|HOUR|MINUTE|SECOND|SPLIT|REPLACE|INDEX_OF_STR|LTRIM|RTRIM|SUBSTR_CP|REGEX|MERGE_OBJECTS|TO_INT|TO_STRING|OBJECT_TO_ARRAY|ARRAY_TO_OBJECT|SET_UNION|SWITCH)\s*\(/i;
|
|
9345
9574
|
function compileInnerExpression(expression) {
|
|
9346
9575
|
const expr2 = expression.trim();
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9576
|
+
if (!expr2) throw createError(ErrorCodes.INVALID_EXPRESSION, "Expression cannot be empty");
|
|
9577
|
+
const positions = topLevelPositions(expr2);
|
|
9578
|
+
const unwrapped = stripOuterParentheses(expr2);
|
|
9579
|
+
if (unwrapped !== expr2) return compileInnerExpression(unwrapped);
|
|
9580
|
+
if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(expr2)) return Number(expr2);
|
|
9581
|
+
const conditional = findConditional(expr2, positions);
|
|
9582
|
+
if (conditional) {
|
|
9583
|
+
return { $cond: {
|
|
9584
|
+
if: compileInnerExpression(expr2.slice(0, conditional.question)),
|
|
9585
|
+
then: compileInnerExpression(expr2.slice(conditional.question + 1, conditional.colon)),
|
|
9586
|
+
else: compileInnerExpression(expr2.slice(conditional.colon + 1))
|
|
9587
|
+
} };
|
|
9588
|
+
}
|
|
9589
|
+
const nullCoalParts = splitTopLevel2(expr2, "??", positions);
|
|
9590
|
+
if (nullCoalParts.length > 1) {
|
|
9591
|
+
return nullCoalParts.slice(1).reduce(
|
|
9592
|
+
(left, right) => ({ $ifNull: [left, compileInnerExpression(right)] }),
|
|
9593
|
+
compileInnerExpression(nullCoalParts[0])
|
|
9594
|
+
);
|
|
9354
9595
|
}
|
|
9355
|
-
const orParts = splitTopLevel2(expr2, "||");
|
|
9596
|
+
const orParts = splitTopLevel2(expr2, "||", positions);
|
|
9356
9597
|
if (orParts.length > 1) {
|
|
9357
|
-
return { $or: orParts.map((part) => compileInnerExpression(part
|
|
9358
|
-
}
|
|
9359
|
-
const ternary = /^([^?]+)\s*\?\s*([^:]+)\s*:\s*(.+)$/.exec(expr2);
|
|
9360
|
-
if (ternary) {
|
|
9361
|
-
const [, condition, thenPart, elsePart] = ternary;
|
|
9362
|
-
return {
|
|
9363
|
-
$cond: {
|
|
9364
|
-
if: compileInnerExpression(condition.trim()),
|
|
9365
|
-
then: parseThenElse(thenPart.trim()),
|
|
9366
|
-
else: parseThenElse(elsePart.trim())
|
|
9367
|
-
}
|
|
9368
|
-
};
|
|
9598
|
+
return { $or: orParts.map((part) => compileInnerExpression(part)) };
|
|
9369
9599
|
}
|
|
9370
|
-
const
|
|
9371
|
-
if (
|
|
9372
|
-
return { $
|
|
9600
|
+
const andParts = splitTopLevel2(expr2, "&&", positions);
|
|
9601
|
+
if (andParts.length > 1) {
|
|
9602
|
+
return { $and: andParts.map((part) => compileInnerExpression(part)) };
|
|
9373
9603
|
}
|
|
9374
|
-
const
|
|
9375
|
-
if (
|
|
9376
|
-
const [, left, operator, right] = cmpMatch;
|
|
9604
|
+
const comparison = findBinaryOperator(expr2, positions, ["===", "!==", ">=", "<=", ">", "<"]);
|
|
9605
|
+
if (comparison) {
|
|
9377
9606
|
const operatorMap = {
|
|
9378
9607
|
"===": "$eq",
|
|
9379
9608
|
"!==": "$ne",
|
|
@@ -9382,24 +9611,39 @@ function compileInnerExpression(expression) {
|
|
|
9382
9611
|
">": "$gt",
|
|
9383
9612
|
"<": "$lt"
|
|
9384
9613
|
};
|
|
9385
|
-
return {
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9614
|
+
return { [operatorMap[comparison.operator]]: [
|
|
9615
|
+
compileInnerExpression(expr2.slice(0, comparison.index)),
|
|
9616
|
+
compileInnerExpression(expr2.slice(comparison.index + comparison.operator.length))
|
|
9617
|
+
] };
|
|
9618
|
+
}
|
|
9619
|
+
const additive = findBinaryOperator(expr2, positions, ["+", "-"], true);
|
|
9620
|
+
if (additive) {
|
|
9621
|
+
return { [additive.operator === "+" ? "$add" : "$subtract"]: [
|
|
9622
|
+
compileInnerExpression(expr2.slice(0, additive.index)),
|
|
9623
|
+
compileInnerExpression(expr2.slice(additive.index + 1))
|
|
9624
|
+
] };
|
|
9625
|
+
}
|
|
9626
|
+
const multiplicative = findBinaryOperator(expr2, positions, ["*", "/", "%"], true);
|
|
9627
|
+
if (multiplicative) {
|
|
9628
|
+
const operatorMap = { "*": "$multiply", "/": "$divide", "%": "$mod" };
|
|
9629
|
+
return { [operatorMap[multiplicative.operator]]: [
|
|
9630
|
+
compileInnerExpression(expr2.slice(0, multiplicative.index)),
|
|
9631
|
+
compileInnerExpression(expr2.slice(multiplicative.index + 1))
|
|
9632
|
+
] };
|
|
9391
9633
|
}
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
const [, left, operator, right] = addSubMatch;
|
|
9395
|
-
const operatorMap = { "+": "$add", "-": "$subtract" };
|
|
9396
|
-
return { [operatorMap[operator]]: [parseOperand(left.trim()), parseOperand(right.trim())] };
|
|
9634
|
+
if (expr2.startsWith("-")) {
|
|
9635
|
+
return { $multiply: [-1, compileInnerExpression(expr2.slice(1))] };
|
|
9397
9636
|
}
|
|
9398
|
-
const
|
|
9399
|
-
if (
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9637
|
+
const funcMatch = expr2.match(FUNC_REGEX);
|
|
9638
|
+
if (funcMatch) {
|
|
9639
|
+
try {
|
|
9640
|
+
return dispatchFunction(funcMatch[1].toUpperCase(), funcMatch[2] ?? "");
|
|
9641
|
+
} catch (error) {
|
|
9642
|
+
if (error && typeof error === "object" && "code" in error && error.code === ErrorCodes.INVALID_EXPRESSION) {
|
|
9643
|
+
throw error;
|
|
9644
|
+
}
|
|
9645
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, `Invalid arguments for ${funcMatch[1].toUpperCase()}`);
|
|
9646
|
+
}
|
|
9403
9647
|
}
|
|
9404
9648
|
const genericFuncCallRe = /^[A-Za-z_][A-Za-z0-9_]*\s*\(.+\)$/;
|
|
9405
9649
|
if (genericFuncCallRe.test(expr2)) {
|
|
@@ -9408,9 +9652,6 @@ function compileInnerExpression(expression) {
|
|
|
9408
9652
|
}
|
|
9409
9653
|
return parseValue(expr2);
|
|
9410
9654
|
}
|
|
9411
|
-
function parseThenElse(source) {
|
|
9412
|
-
return source.includes("?") && source.includes(":") ? compileInnerExpression(source) : parseValue(source);
|
|
9413
|
-
}
|
|
9414
9655
|
function parseValue(value) {
|
|
9415
9656
|
const normalized = stripOuterParentheses(value.trim());
|
|
9416
9657
|
if (normalized.startsWith("'") && normalized.endsWith("'") || normalized.startsWith('"') && normalized.endsWith('"')) {
|
|
@@ -9420,18 +9661,22 @@ function parseValue(value) {
|
|
|
9420
9661
|
if (normalized === "true") return true;
|
|
9421
9662
|
if (normalized === "false") return false;
|
|
9422
9663
|
if (!isNaN(Number(normalized)) && normalized !== "") return Number(normalized);
|
|
9423
|
-
if (IS_FUNC_CALL_RE.test(normalized)) {
|
|
9664
|
+
if (normalized !== value.trim() || IS_FUNC_CALL_RE.test(normalized) || FUNC_REGEX.test(normalized)) {
|
|
9424
9665
|
return compileInnerExpression(normalized);
|
|
9425
9666
|
}
|
|
9426
|
-
if (normalized.startsWith("
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9667
|
+
if (normalized.startsWith("[") || normalized.startsWith("{")) {
|
|
9668
|
+
try {
|
|
9669
|
+
return JSON.parse(normalized);
|
|
9670
|
+
} catch {
|
|
9671
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Invalid array or object literal");
|
|
9672
|
+
}
|
|
9673
|
+
}
|
|
9674
|
+
const positions = topLevelPositions(normalized);
|
|
9675
|
+
if (positions.some((index) => /[?+\-*/%><&|]/.test(normalized[index]))) {
|
|
9432
9676
|
return compileInnerExpression(normalized);
|
|
9433
9677
|
}
|
|
9434
|
-
|
|
9678
|
+
if (normalized.startsWith("$")) return normalized;
|
|
9679
|
+
return `$${normalized}`;
|
|
9435
9680
|
}
|
|
9436
9681
|
function dispatchFunction(name, argsStr) {
|
|
9437
9682
|
const args = splitArgsStr(argsStr);
|
|
@@ -9733,72 +9978,82 @@ function compileMapExpression(exprStr, varName) {
|
|
|
9733
9978
|
return compileInnerExpression(replaced);
|
|
9734
9979
|
}
|
|
9735
9980
|
function splitArgsStr(argsStr) {
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
let inString = false;
|
|
9739
|
-
let stringChar = "";
|
|
9740
|
-
let parenDepth = 0;
|
|
9741
|
-
for (let index = 0; index < argsStr.length; index++) {
|
|
9742
|
-
const ch = argsStr[index];
|
|
9743
|
-
if ((ch === '"' || ch === "'") && (index === 0 || argsStr[index - 1] !== "\\")) {
|
|
9744
|
-
if (!inString) {
|
|
9745
|
-
inString = true;
|
|
9746
|
-
stringChar = ch;
|
|
9747
|
-
} else if (ch === stringChar) {
|
|
9748
|
-
inString = false;
|
|
9749
|
-
stringChar = "";
|
|
9750
|
-
}
|
|
9751
|
-
current += ch;
|
|
9752
|
-
} else if (ch === "(" && !inString) {
|
|
9753
|
-
parenDepth++;
|
|
9754
|
-
current += ch;
|
|
9755
|
-
} else if (ch === ")" && !inString) {
|
|
9756
|
-
parenDepth--;
|
|
9757
|
-
current += ch;
|
|
9758
|
-
} else if (ch === "," && !inString && parenDepth === 0) {
|
|
9759
|
-
args.push(current.trim());
|
|
9760
|
-
current = "";
|
|
9761
|
-
} else {
|
|
9762
|
-
current += ch;
|
|
9763
|
-
}
|
|
9764
|
-
}
|
|
9765
|
-
if (current.trim()) args.push(current.trim());
|
|
9766
|
-
return args;
|
|
9981
|
+
if (!argsStr.trim()) return [];
|
|
9982
|
+
return splitTopLevel2(argsStr, ",");
|
|
9767
9983
|
}
|
|
9768
|
-
function
|
|
9769
|
-
const
|
|
9770
|
-
|
|
9771
|
-
let
|
|
9772
|
-
let inString = false;
|
|
9773
|
-
let stringChar = "";
|
|
9984
|
+
function topLevelPositions(source) {
|
|
9985
|
+
const positions = [];
|
|
9986
|
+
const stack = [];
|
|
9987
|
+
let quote = null;
|
|
9774
9988
|
for (let index = 0; index < source.length; index++) {
|
|
9775
9989
|
const ch = source[index];
|
|
9776
|
-
if (
|
|
9777
|
-
if (
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9990
|
+
if (quote) {
|
|
9991
|
+
if (ch === quote && source[index - 1] !== "\\") quote = null;
|
|
9992
|
+
continue;
|
|
9993
|
+
}
|
|
9994
|
+
if (ch === '"' || ch === "'") {
|
|
9995
|
+
quote = ch;
|
|
9996
|
+
continue;
|
|
9997
|
+
}
|
|
9998
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
9999
|
+
stack.push(ch);
|
|
10000
|
+
continue;
|
|
10001
|
+
}
|
|
10002
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
10003
|
+
const opening = stack.pop();
|
|
10004
|
+
if (ch === ")" && opening !== "(" || ch === "]" && opening !== "[" || ch === "}" && opening !== "{") {
|
|
10005
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Unbalanced expression delimiters");
|
|
9783
10006
|
}
|
|
9784
|
-
|
|
9785
|
-
} else if (!inString && ch === "(") {
|
|
9786
|
-
parenDepth++;
|
|
9787
|
-
current += ch;
|
|
9788
|
-
} else if (!inString && ch === ")") {
|
|
9789
|
-
parenDepth--;
|
|
9790
|
-
current += ch;
|
|
9791
|
-
} else if (!inString && parenDepth === 0 && source.startsWith(separator, index)) {
|
|
9792
|
-
parts.push(current);
|
|
9793
|
-
current = "";
|
|
9794
|
-
index += separator.length - 1;
|
|
9795
|
-
} else {
|
|
9796
|
-
current += ch;
|
|
10007
|
+
continue;
|
|
9797
10008
|
}
|
|
10009
|
+
if (stack.length === 0) positions.push(index);
|
|
10010
|
+
}
|
|
10011
|
+
if (quote || stack.length > 0) {
|
|
10012
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Unbalanced expression delimiters");
|
|
10013
|
+
}
|
|
10014
|
+
return positions;
|
|
10015
|
+
}
|
|
10016
|
+
function splitTopLevel2(source, separator, positions = topLevelPositions(source)) {
|
|
10017
|
+
const parts = [];
|
|
10018
|
+
let start = 0;
|
|
10019
|
+
for (const index of positions) {
|
|
10020
|
+
if (index < start || !source.startsWith(separator, index)) continue;
|
|
10021
|
+
parts.push(source.slice(start, index).trim());
|
|
10022
|
+
start = index + separator.length;
|
|
10023
|
+
}
|
|
10024
|
+
if (parts.length === 0) return [source];
|
|
10025
|
+
parts.push(source.slice(start).trim());
|
|
10026
|
+
if (parts.some((part) => !part)) {
|
|
10027
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, `Missing operand around ${separator}`);
|
|
9798
10028
|
}
|
|
9799
|
-
parts.push(current);
|
|
9800
10029
|
return parts;
|
|
9801
10030
|
}
|
|
10031
|
+
function findConditional(source, positions) {
|
|
10032
|
+
const question = positions.find((index) => source[index] === "?" && source[index - 1] !== "?" && source[index + 1] !== "?");
|
|
10033
|
+
if (question === void 0) return null;
|
|
10034
|
+
let nested = 0;
|
|
10035
|
+
for (const index of positions) {
|
|
10036
|
+
if (index <= question) continue;
|
|
10037
|
+
if (source[index] === "?" && source[index - 1] !== "?" && source[index + 1] !== "?") nested++;
|
|
10038
|
+
if (source[index] === ":") {
|
|
10039
|
+
if (nested === 0) return { question, colon: index };
|
|
10040
|
+
nested--;
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Conditional expression is missing a branch");
|
|
10044
|
+
}
|
|
10045
|
+
function findBinaryOperator(source, positions, operators, rightmost = false) {
|
|
10046
|
+
let match = null;
|
|
10047
|
+
for (const index of positions) {
|
|
10048
|
+
const operator = operators.find((candidate) => source.startsWith(candidate, index));
|
|
10049
|
+
if (!operator) continue;
|
|
10050
|
+
if ((operator === "+" || operator === "-") && !source.slice(0, index).trim()) continue;
|
|
10051
|
+
if ((operator === "+" || operator === "-") && /[+\-*/%<>=!?&|:,]$/.test(source.slice(0, index).trimEnd())) continue;
|
|
10052
|
+
match = { index, operator };
|
|
10053
|
+
if (!rightmost) break;
|
|
10054
|
+
}
|
|
10055
|
+
return match;
|
|
10056
|
+
}
|
|
9802
10057
|
function stripOuterParentheses(source) {
|
|
9803
10058
|
let normalized = source.trim();
|
|
9804
10059
|
while (normalized.startsWith("(") && normalized.endsWith(")") && isWrappedByOuterParentheses(normalized)) {
|
|
@@ -9844,10 +10099,15 @@ function isExpressionObject(value) {
|
|
|
9844
10099
|
value && typeof value === "object" && "__expr__" in value && typeof value.__expr__ === "string" && "__compiled__" in value && typeof value.__compiled__ === "boolean"
|
|
9845
10100
|
);
|
|
9846
10101
|
}
|
|
10102
|
+
function isPlainExpressionRecord(value) {
|
|
10103
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
10104
|
+
const prototype = Object.getPrototypeOf(value);
|
|
10105
|
+
return prototype === Object.prototype || prototype === null;
|
|
10106
|
+
}
|
|
9847
10107
|
function hasExpressionInObject(value) {
|
|
9848
10108
|
if (isExpressionObject(value)) return true;
|
|
9849
|
-
if (!value || typeof value !== "object") return false;
|
|
9850
10109
|
if (Array.isArray(value)) return value.some((item) => hasExpressionInObject(item));
|
|
10110
|
+
if (!isPlainExpressionRecord(value)) return false;
|
|
9851
10111
|
return Object.values(value).some((item) => hasExpressionInObject(item));
|
|
9852
10112
|
}
|
|
9853
10113
|
function hasExpressionInPipeline(pipeline) {
|
|
@@ -9864,7 +10124,7 @@ function transformExpressions(value, context) {
|
|
|
9864
10124
|
if (Array.isArray(value)) {
|
|
9865
10125
|
return value.map((item) => transformStageEntry(item));
|
|
9866
10126
|
}
|
|
9867
|
-
if (!value
|
|
10127
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9868
10128
|
const entries = Object.entries(value);
|
|
9869
10129
|
const result = {};
|
|
9870
10130
|
for (const [key, current] of entries) {
|
|
@@ -9873,7 +10133,7 @@ function transformExpressions(value, context) {
|
|
|
9873
10133
|
return result;
|
|
9874
10134
|
}
|
|
9875
10135
|
function transformStageEntry(stage) {
|
|
9876
|
-
if (!
|
|
10136
|
+
if (!isPlainExpressionRecord(stage)) {
|
|
9877
10137
|
return transformExpressions(stage, "project");
|
|
9878
10138
|
}
|
|
9879
10139
|
const entries = Object.entries(stage);
|
|
@@ -9897,7 +10157,7 @@ function transformStageValue(value, context) {
|
|
|
9897
10157
|
if (Array.isArray(value)) {
|
|
9898
10158
|
return value.map((item) => transformStageValue(item, context));
|
|
9899
10159
|
}
|
|
9900
|
-
if (!value
|
|
10160
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9901
10161
|
const entries = Object.entries(value);
|
|
9902
10162
|
const result = {};
|
|
9903
10163
|
for (const [key, current] of entries) {
|
|
@@ -10828,6 +11088,7 @@ var AggregateChain = class {
|
|
|
10828
11088
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).explain(verbosity === true ? "queryPlanner" : verbosity);
|
|
10829
11089
|
}
|
|
10830
11090
|
stream() {
|
|
11091
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10831
11092
|
const stream = this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).stream();
|
|
10832
11093
|
const onWriteComplete = this.writePipelineHooks?.onWriteComplete;
|
|
10833
11094
|
if (onWriteComplete) {
|
|
@@ -10845,6 +11106,7 @@ var AggregateChain = class {
|
|
|
10845
11106
|
throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
|
|
10846
11107
|
}
|
|
10847
11108
|
this.executed = true;
|
|
11109
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10848
11110
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).toArray().then(async (result) => {
|
|
10849
11111
|
await this.writePipelineHooks?.onWriteComplete?.();
|
|
10850
11112
|
return result;
|
|
@@ -11161,48 +11423,59 @@ function stripWriteCacheControlOptions(options) {
|
|
|
11161
11423
|
|
|
11162
11424
|
// src/adapters/mongodb/writes/write-basic.ts
|
|
11163
11425
|
async function insertOneDocument(collection, ...args) {
|
|
11426
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11164
11427
|
return collection.insertOne(...args);
|
|
11165
11428
|
}
|
|
11166
11429
|
async function insertManyDocuments(collection, ...args) {
|
|
11430
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11167
11431
|
return collection.insertMany(...args);
|
|
11168
11432
|
}
|
|
11169
11433
|
async function updateOneDocument(collection, ...args) {
|
|
11434
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11170
11435
|
return collection.updateOne(...args);
|
|
11171
11436
|
}
|
|
11172
11437
|
async function updateManyDocuments(collection, ...args) {
|
|
11438
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11173
11439
|
return collection.updateMany(...args);
|
|
11174
11440
|
}
|
|
11175
11441
|
async function replaceOneDocument(collection, ...args) {
|
|
11442
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11176
11443
|
return collection.replaceOne(...args);
|
|
11177
11444
|
}
|
|
11178
11445
|
async function findOneAndUpdateDocument(collection, filter, update, options) {
|
|
11446
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11179
11447
|
if (options !== void 0) {
|
|
11180
11448
|
return collection.findOneAndUpdate(filter, update, options);
|
|
11181
11449
|
}
|
|
11182
11450
|
return collection.findOneAndUpdate(filter, update);
|
|
11183
11451
|
}
|
|
11184
11452
|
async function findOneAndReplaceDocument(collection, filter, replacement, options) {
|
|
11453
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11185
11454
|
if (options !== void 0) {
|
|
11186
11455
|
return collection.findOneAndReplace(filter, replacement, options);
|
|
11187
11456
|
}
|
|
11188
11457
|
return collection.findOneAndReplace(filter, replacement);
|
|
11189
11458
|
}
|
|
11190
11459
|
async function findOneAndDeleteDocument(collection, filter, options) {
|
|
11460
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11191
11461
|
if (options !== void 0) {
|
|
11192
11462
|
return collection.findOneAndDelete(filter, options);
|
|
11193
11463
|
}
|
|
11194
11464
|
return collection.findOneAndDelete(filter);
|
|
11195
11465
|
}
|
|
11196
11466
|
async function upsertOneDocument(collection, filter, update, options = {}) {
|
|
11467
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11197
11468
|
return collection.updateOne(filter, update, {
|
|
11198
11469
|
...options,
|
|
11199
11470
|
upsert: true
|
|
11200
11471
|
});
|
|
11201
11472
|
}
|
|
11202
11473
|
async function deleteOneDocument(collection, ...args) {
|
|
11474
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11203
11475
|
return collection.deleteOne(...args);
|
|
11204
11476
|
}
|
|
11205
11477
|
async function deleteManyDocuments(collection, ...args) {
|
|
11478
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11206
11479
|
return collection.deleteMany(...args);
|
|
11207
11480
|
}
|
|
11208
11481
|
async function incrementOneDocument(collection, filter, field, incrementOrOptions, maybeOptions) {
|
|
@@ -11229,6 +11502,7 @@ async function incrementOneDocument(collection, filter, field, incrementOrOption
|
|
|
11229
11502
|
includeResultMetadata: true
|
|
11230
11503
|
};
|
|
11231
11504
|
if (normalizedProjection) findOptions.projection = normalizedProjection;
|
|
11505
|
+
assertManagedTransactionWriteAllowed(findOptions);
|
|
11232
11506
|
const rawResult = await collection.findOneAndUpdate(
|
|
11233
11507
|
filter,
|
|
11234
11508
|
updateDocument,
|
|
@@ -11361,14 +11635,25 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
11361
11635
|
return;
|
|
11362
11636
|
}
|
|
11363
11637
|
let nextIndex = 0;
|
|
11638
|
+
let stopAcquiring = false;
|
|
11639
|
+
let firstFatal;
|
|
11364
11640
|
const workerCount = Math.min(concurrency, items.length);
|
|
11365
11641
|
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
11366
|
-
while (nextIndex < items.length) {
|
|
11642
|
+
while (!stopAcquiring && nextIndex < items.length) {
|
|
11367
11643
|
const item = items[nextIndex];
|
|
11368
11644
|
nextIndex += 1;
|
|
11369
|
-
|
|
11645
|
+
try {
|
|
11646
|
+
await worker(item);
|
|
11647
|
+
} catch (error) {
|
|
11648
|
+
if (!stopAcquiring) {
|
|
11649
|
+
firstFatal = error;
|
|
11650
|
+
stopAcquiring = true;
|
|
11651
|
+
}
|
|
11652
|
+
break;
|
|
11653
|
+
}
|
|
11370
11654
|
}
|
|
11371
11655
|
}));
|
|
11656
|
+
if (stopAcquiring) throw firstFatal;
|
|
11372
11657
|
}
|
|
11373
11658
|
async function insertBatchDocuments(collection, documents, options = {}) {
|
|
11374
11659
|
if (!Array.isArray(documents)) {
|
|
@@ -11415,12 +11700,14 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11415
11700
|
insertedIds: {},
|
|
11416
11701
|
retries: []
|
|
11417
11702
|
};
|
|
11703
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11418
11704
|
await runWithConcurrency(batchPlans, effectiveConcurrency, async ({ batchIndex, batch, batchOffset: batchOffset2 }) => {
|
|
11419
11705
|
let pendingBatch = batch;
|
|
11420
11706
|
let pendingIndexes = batch.map((_, index) => index);
|
|
11421
11707
|
let attempts = 0;
|
|
11422
11708
|
while (true) {
|
|
11423
11709
|
try {
|
|
11710
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11424
11711
|
const batchResult = await collection.insertMany(pendingBatch, {
|
|
11425
11712
|
...driverOptions,
|
|
11426
11713
|
ordered
|
|
@@ -11440,6 +11727,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11440
11727
|
});
|
|
11441
11728
|
break;
|
|
11442
11729
|
} catch (cause) {
|
|
11730
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11443
11731
|
const errorRecord = {
|
|
11444
11732
|
batchIndex,
|
|
11445
11733
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11511,6 +11799,10 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11511
11799
|
});
|
|
11512
11800
|
return result;
|
|
11513
11801
|
}
|
|
11802
|
+
function batchWriteFilter(filter, batch) {
|
|
11803
|
+
const ids = { _id: { $in: batch } };
|
|
11804
|
+
return Object.prototype.hasOwnProperty.call(filter, "_id") ? { $and: [filter, ids] } : { ...filter, ...ids };
|
|
11805
|
+
}
|
|
11514
11806
|
async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
11515
11807
|
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
11516
11808
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "filter must be a non-array object");
|
|
@@ -11554,6 +11846,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11554
11846
|
"updateBatch retry requires an idempotent update; non-idempotent operators such as $inc/$push cannot be safely replayed"
|
|
11555
11847
|
);
|
|
11556
11848
|
}
|
|
11849
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11557
11850
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11558
11851
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11559
11852
|
const result = {
|
|
@@ -11570,8 +11863,9 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11570
11863
|
let attempts = 0;
|
|
11571
11864
|
while (true) {
|
|
11572
11865
|
try {
|
|
11866
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11573
11867
|
const batchResult = await collection.updateMany(
|
|
11574
|
-
|
|
11868
|
+
batchWriteFilter(filter, batch),
|
|
11575
11869
|
update,
|
|
11576
11870
|
driverOptions
|
|
11577
11871
|
);
|
|
@@ -11589,6 +11883,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11589
11883
|
});
|
|
11590
11884
|
break;
|
|
11591
11885
|
} catch (cause) {
|
|
11886
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11592
11887
|
const errorRecord = {
|
|
11593
11888
|
batchIndex,
|
|
11594
11889
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11651,6 +11946,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11651
11946
|
if (!Number.isInteger(retryAttempts) || retryAttempts < 0) {
|
|
11652
11947
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "retryAttempts must be a non-negative integer");
|
|
11653
11948
|
}
|
|
11949
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11654
11950
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11655
11951
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11656
11952
|
const result = {
|
|
@@ -11665,8 +11961,9 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11665
11961
|
let attempts = 0;
|
|
11666
11962
|
while (true) {
|
|
11667
11963
|
try {
|
|
11964
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11668
11965
|
const batchResult = await collection.deleteMany(
|
|
11669
|
-
|
|
11966
|
+
batchWriteFilter(filter, batch),
|
|
11670
11967
|
driverOptions
|
|
11671
11968
|
);
|
|
11672
11969
|
result.deletedCount += batchResult.deletedCount;
|
|
@@ -11681,6 +11978,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11681
11978
|
});
|
|
11682
11979
|
break;
|
|
11683
11980
|
} catch (cause) {
|
|
11981
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11684
11982
|
const errorRecord = {
|
|
11685
11983
|
batchIndex,
|
|
11686
11984
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -12209,9 +12507,11 @@ var MongoCollectionAccessor = class {
|
|
|
12209
12507
|
const normalizedQuery = this._cvFilter(query);
|
|
12210
12508
|
const maxTimeMS = this.management.defaults?.maxTimeMS;
|
|
12211
12509
|
const merged = { ...maxTimeMS !== void 0 ? { maxTimeMS } : {}, ...options ?? {} };
|
|
12212
|
-
const cacheTTL = typeof merged.cache === "number" ? merged.cache : 0;
|
|
12510
|
+
const cacheTTL = merged.explain === void 0 || merged.explain === false ? typeof merged.cache === "number" ? merged.cache : 0 : 0;
|
|
12213
12511
|
const { cache: _cache, ...keyOptions } = merged;
|
|
12214
12512
|
void _cache;
|
|
12513
|
+
const callerSignal = keyOptions.signal;
|
|
12514
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12215
12515
|
const executeCount = (signal) => countDocuments(
|
|
12216
12516
|
this.collectionRef,
|
|
12217
12517
|
normalizedQuery ?? {},
|
|
@@ -12222,15 +12522,16 @@ var MongoCollectionAccessor = class {
|
|
|
12222
12522
|
const cacheKey = buildCountCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, keyOptions);
|
|
12223
12523
|
const cached = await Promise.resolve(this.management.queryCache.get(cacheKey));
|
|
12224
12524
|
if (cached !== void 0) {
|
|
12525
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12225
12526
|
return Promise.resolve(wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, cached));
|
|
12226
12527
|
}
|
|
12227
|
-
const runner2 = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12528
|
+
const runner2 = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12228
12529
|
return runner2.then(async (result) => {
|
|
12229
12530
|
await Promise.resolve(this.management.queryCache?.set(cacheKey, result, cacheTTL));
|
|
12230
12531
|
return wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result);
|
|
12231
12532
|
});
|
|
12232
12533
|
}
|
|
12233
|
-
const runner = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12534
|
+
const runner = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12234
12535
|
return runner.then((result) => wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result));
|
|
12235
12536
|
}
|
|
12236
12537
|
/** Runs an aggregation pipeline and returns a chainable aggregate cursor. */
|
|
@@ -13519,20 +13820,21 @@ var DistributedCacheLockManager = class {
|
|
|
13519
13820
|
}
|
|
13520
13821
|
}
|
|
13521
13822
|
async withLock(key, callback, options = {}) {
|
|
13823
|
+
let lock;
|
|
13522
13824
|
try {
|
|
13523
|
-
|
|
13524
|
-
try {
|
|
13525
|
-
return await callback();
|
|
13526
|
-
} finally {
|
|
13527
|
-
await lock.release().catch(() => {
|
|
13528
|
-
});
|
|
13529
|
-
}
|
|
13825
|
+
lock = await this.acquireLock(key, options);
|
|
13530
13826
|
} catch (error) {
|
|
13531
13827
|
if (this._isRedisConnectionError(error) && options.fallbackToNoLock) {
|
|
13532
13828
|
return callback();
|
|
13533
13829
|
}
|
|
13534
13830
|
throw error;
|
|
13535
13831
|
}
|
|
13832
|
+
try {
|
|
13833
|
+
return await callback();
|
|
13834
|
+
} finally {
|
|
13835
|
+
await lock.release().catch(() => {
|
|
13836
|
+
});
|
|
13837
|
+
}
|
|
13536
13838
|
}
|
|
13537
13839
|
async acquireLock(key, options = {}) {
|
|
13538
13840
|
const ttl = options.ttl ?? 1e4;
|
|
@@ -14635,8 +14937,6 @@ var SlowQueryLogManager = class {
|
|
|
14635
14937
|
};
|
|
14636
14938
|
|
|
14637
14939
|
// src/capabilities/sync/index.ts
|
|
14638
|
-
var import_promises = require("node:fs/promises");
|
|
14639
|
-
var import_node_path2 = __toESM(require("node:path"));
|
|
14640
14940
|
var import_mongodb7 = require("mongodb");
|
|
14641
14941
|
|
|
14642
14942
|
// src/capabilities/sync/idempotency.ts
|
|
@@ -14739,25 +15039,19 @@ function validateSyncIdempotencyConfig(config) {
|
|
|
14739
15039
|
}
|
|
14740
15040
|
}
|
|
14741
15041
|
|
|
14742
|
-
// src/capabilities/sync/
|
|
15042
|
+
// src/capabilities/sync/resume-token-store.ts
|
|
15043
|
+
var import_promises = require("node:fs/promises");
|
|
15044
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
14743
15045
|
function normalizeError(error) {
|
|
14744
15046
|
return error instanceof Error ? error : new Error(String(error));
|
|
14745
15047
|
}
|
|
14746
15048
|
function delay2(ms) {
|
|
14747
|
-
if (ms <= 0)
|
|
14748
|
-
return Promise.resolve();
|
|
14749
|
-
}
|
|
15049
|
+
if (ms <= 0) return Promise.resolve();
|
|
14750
15050
|
return new Promise((resolve) => {
|
|
14751
15051
|
const timer = setTimeout(resolve, ms);
|
|
14752
15052
|
timer.unref?.();
|
|
14753
15053
|
});
|
|
14754
15054
|
}
|
|
14755
|
-
function normalizeCollectionFilter(collections) {
|
|
14756
|
-
if (!collections?.length || collections.includes("*")) {
|
|
14757
|
-
return null;
|
|
14758
|
-
}
|
|
14759
|
-
return new Set(collections);
|
|
14760
|
-
}
|
|
14761
15055
|
async function syncDirectory(directory) {
|
|
14762
15056
|
try {
|
|
14763
15057
|
const handle = await (0, import_promises.open)(directory, "r");
|
|
@@ -14769,35 +15063,6 @@ async function syncDirectory(directory) {
|
|
|
14769
15063
|
} catch {
|
|
14770
15064
|
}
|
|
14771
15065
|
}
|
|
14772
|
-
function validateTargetConfig(target, index) {
|
|
14773
|
-
if (!target || typeof target !== "object") {
|
|
14774
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] must be an object.`);
|
|
14775
|
-
}
|
|
14776
|
-
if (typeof target.name !== "string" || target.name.trim() === "") {
|
|
14777
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].name must be a non-empty string.`);
|
|
14778
|
-
}
|
|
14779
|
-
if (!target.apply && !target.uri && !target.pool) {
|
|
14780
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] requires one of apply / uri / pool.`);
|
|
14781
|
-
}
|
|
14782
|
-
if (target.uri !== void 0 && (typeof target.uri !== "string" || target.uri.trim() === "")) {
|
|
14783
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].uri must be a non-empty string when provided.`);
|
|
14784
|
-
}
|
|
14785
|
-
if (target.pool !== void 0 && (typeof target.pool !== "string" || target.pool.trim() === "")) {
|
|
14786
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].pool must be a non-empty string when provided.`);
|
|
14787
|
-
}
|
|
14788
|
-
if (target.databaseName !== void 0 && (typeof target.databaseName !== "string" || target.databaseName.trim() === "")) {
|
|
14789
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].databaseName must be a non-empty string when provided.`);
|
|
14790
|
-
}
|
|
14791
|
-
if (target.collections !== void 0 && (!Array.isArray(target.collections) || target.collections.length === 0)) {
|
|
14792
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must be a non-empty array when provided.`);
|
|
14793
|
-
}
|
|
14794
|
-
if (target.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14795
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must contain non-empty strings.`);
|
|
14796
|
-
}
|
|
14797
|
-
if (target.apply !== void 0 && typeof target.apply !== "function") {
|
|
14798
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].apply must be a function when provided.`);
|
|
14799
|
-
}
|
|
14800
|
-
}
|
|
14801
15066
|
function validateResumeTokenConfig(config) {
|
|
14802
15067
|
if (!config || typeof config !== "object") {
|
|
14803
15068
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken must be an object.");
|
|
@@ -14828,39 +15093,6 @@ function validateResumeTokenConfig(config) {
|
|
|
14828
15093
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken.saveRetryDelayMs must be a non-negative integer.");
|
|
14829
15094
|
}
|
|
14830
15095
|
}
|
|
14831
|
-
function validateSyncConfig(config) {
|
|
14832
|
-
if (!config || typeof config !== "object") {
|
|
14833
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] config must be an object.");
|
|
14834
|
-
}
|
|
14835
|
-
if (typeof config.enabled !== "boolean") {
|
|
14836
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] enabled must be a boolean.");
|
|
14837
|
-
}
|
|
14838
|
-
if (!config.enabled) {
|
|
14839
|
-
return;
|
|
14840
|
-
}
|
|
14841
|
-
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
14842
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] targets must be a non-empty array.");
|
|
14843
|
-
}
|
|
14844
|
-
if (config.collections !== void 0 && (!Array.isArray(config.collections) || config.collections.length === 0)) {
|
|
14845
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must be a non-empty array when provided.");
|
|
14846
|
-
}
|
|
14847
|
-
if (config.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14848
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must contain non-empty strings.");
|
|
14849
|
-
}
|
|
14850
|
-
if (config.filter !== void 0 && typeof config.filter !== "function") {
|
|
14851
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] filter must be a function.");
|
|
14852
|
-
}
|
|
14853
|
-
if (config.transform !== void 0 && typeof config.transform !== "function") {
|
|
14854
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] transform must be a function.");
|
|
14855
|
-
}
|
|
14856
|
-
validateSyncIdempotencyConfig(config.idempotency);
|
|
14857
|
-
config.targets.forEach((target, index) => {
|
|
14858
|
-
validateTargetConfig(target, index);
|
|
14859
|
-
});
|
|
14860
|
-
if (config.resumeToken) {
|
|
14861
|
-
validateResumeTokenConfig(config.resumeToken);
|
|
14862
|
-
}
|
|
14863
|
-
}
|
|
14864
15096
|
var ResumeTokenStore = class {
|
|
14865
15097
|
constructor(options = {}) {
|
|
14866
15098
|
this.storage = options.storage ?? "file";
|
|
@@ -14884,16 +15116,9 @@ var ResumeTokenStore = class {
|
|
|
14884
15116
|
return JSON.parse(payload);
|
|
14885
15117
|
} catch (error) {
|
|
14886
15118
|
const code = error?.code;
|
|
14887
|
-
if (code !== "ENOENT")
|
|
14888
|
-
this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14889
|
-
}
|
|
15119
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14890
15120
|
if (code !== "ENOENT" && this.strictLoad) {
|
|
14891
|
-
throw createError(
|
|
14892
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14893
|
-
"[Sync] failed to load resume token",
|
|
14894
|
-
void 0,
|
|
14895
|
-
normalizeError(error)
|
|
14896
|
-
);
|
|
15121
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to load resume token", void 0, normalizeError(error));
|
|
14897
15122
|
}
|
|
14898
15123
|
return null;
|
|
14899
15124
|
}
|
|
@@ -14919,12 +15144,7 @@ var ResumeTokenStore = class {
|
|
|
14919
15144
|
}
|
|
14920
15145
|
this.logger?.error?.("[Sync] failed to save resume token", lastError);
|
|
14921
15146
|
if (this.strictSave) {
|
|
14922
|
-
throw createError(
|
|
14923
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14924
|
-
"[Sync] failed to save resume token",
|
|
14925
|
-
void 0,
|
|
14926
|
-
lastError ?? void 0
|
|
14927
|
-
);
|
|
15147
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to save resume token", void 0, lastError ?? void 0);
|
|
14928
15148
|
}
|
|
14929
15149
|
}
|
|
14930
15150
|
async writePayload(payload) {
|
|
@@ -14957,9 +15177,7 @@ var ResumeTokenStore = class {
|
|
|
14957
15177
|
await (0, import_promises.copyFile)(this.path, `${this.path}.bak`);
|
|
14958
15178
|
} catch (error) {
|
|
14959
15179
|
const code = error?.code;
|
|
14960
|
-
if (code !== "ENOENT")
|
|
14961
|
-
throw error;
|
|
14962
|
-
}
|
|
15180
|
+
if (code !== "ENOENT") throw error;
|
|
14963
15181
|
}
|
|
14964
15182
|
}
|
|
14965
15183
|
async clear() {
|
|
@@ -14971,12 +15189,80 @@ var ResumeTokenStore = class {
|
|
|
14971
15189
|
await (0, import_promises.unlink)(this.path);
|
|
14972
15190
|
} catch (error) {
|
|
14973
15191
|
const code = error?.code;
|
|
14974
|
-
if (code !== "ENOENT")
|
|
14975
|
-
this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14976
|
-
}
|
|
15192
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14977
15193
|
}
|
|
14978
15194
|
}
|
|
14979
15195
|
};
|
|
15196
|
+
|
|
15197
|
+
// src/capabilities/sync/index.ts
|
|
15198
|
+
function normalizeCollectionFilter(collections) {
|
|
15199
|
+
if (!collections?.length || collections.includes("*")) {
|
|
15200
|
+
return null;
|
|
15201
|
+
}
|
|
15202
|
+
return new Set(collections);
|
|
15203
|
+
}
|
|
15204
|
+
function validateTargetConfig(target, index) {
|
|
15205
|
+
if (!target || typeof target !== "object") {
|
|
15206
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] must be an object.`);
|
|
15207
|
+
}
|
|
15208
|
+
if (typeof target.name !== "string" || target.name.trim() === "") {
|
|
15209
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].name must be a non-empty string.`);
|
|
15210
|
+
}
|
|
15211
|
+
if (!target.apply && !target.uri && !target.pool) {
|
|
15212
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] requires one of apply / uri / pool.`);
|
|
15213
|
+
}
|
|
15214
|
+
if (target.uri !== void 0 && (typeof target.uri !== "string" || target.uri.trim() === "")) {
|
|
15215
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].uri must be a non-empty string when provided.`);
|
|
15216
|
+
}
|
|
15217
|
+
if (target.pool !== void 0 && (typeof target.pool !== "string" || target.pool.trim() === "")) {
|
|
15218
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].pool must be a non-empty string when provided.`);
|
|
15219
|
+
}
|
|
15220
|
+
if (target.databaseName !== void 0 && (typeof target.databaseName !== "string" || target.databaseName.trim() === "")) {
|
|
15221
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].databaseName must be a non-empty string when provided.`);
|
|
15222
|
+
}
|
|
15223
|
+
if (target.collections !== void 0 && (!Array.isArray(target.collections) || target.collections.length === 0)) {
|
|
15224
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must be a non-empty array when provided.`);
|
|
15225
|
+
}
|
|
15226
|
+
if (target.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
15227
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must contain non-empty strings.`);
|
|
15228
|
+
}
|
|
15229
|
+
if (target.apply !== void 0 && typeof target.apply !== "function") {
|
|
15230
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].apply must be a function when provided.`);
|
|
15231
|
+
}
|
|
15232
|
+
}
|
|
15233
|
+
function validateSyncConfig(config) {
|
|
15234
|
+
if (!config || typeof config !== "object") {
|
|
15235
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] config must be an object.");
|
|
15236
|
+
}
|
|
15237
|
+
if (typeof config.enabled !== "boolean") {
|
|
15238
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] enabled must be a boolean.");
|
|
15239
|
+
}
|
|
15240
|
+
if (!config.enabled) {
|
|
15241
|
+
return;
|
|
15242
|
+
}
|
|
15243
|
+
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
15244
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] targets must be a non-empty array.");
|
|
15245
|
+
}
|
|
15246
|
+
if (config.collections !== void 0 && (!Array.isArray(config.collections) || config.collections.length === 0)) {
|
|
15247
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must be a non-empty array when provided.");
|
|
15248
|
+
}
|
|
15249
|
+
if (config.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
15250
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must contain non-empty strings.");
|
|
15251
|
+
}
|
|
15252
|
+
if (config.filter !== void 0 && typeof config.filter !== "function") {
|
|
15253
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] filter must be a function.");
|
|
15254
|
+
}
|
|
15255
|
+
if (config.transform !== void 0 && typeof config.transform !== "function") {
|
|
15256
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] transform must be a function.");
|
|
15257
|
+
}
|
|
15258
|
+
validateSyncIdempotencyConfig(config.idempotency);
|
|
15259
|
+
config.targets.forEach((target, index) => {
|
|
15260
|
+
validateTargetConfig(target, index);
|
|
15261
|
+
});
|
|
15262
|
+
if (config.resumeToken) {
|
|
15263
|
+
validateResumeTokenConfig(config.resumeToken);
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
14980
15266
|
var ChangeStreamSyncManager = class {
|
|
14981
15267
|
constructor(options) {
|
|
14982
15268
|
this.targets = [];
|
|
@@ -14985,7 +15271,10 @@ var ChangeStreamSyncManager = class {
|
|
|
14985
15271
|
this.closingStreamPromise = null;
|
|
14986
15272
|
this.changeQueue = Promise.resolve();
|
|
14987
15273
|
this.running = false;
|
|
15274
|
+
this.stopping = false;
|
|
15275
|
+
this.stopPromise = null;
|
|
14988
15276
|
this.fatalSyncError = null;
|
|
15277
|
+
this.generation = 0;
|
|
14989
15278
|
this.stats = {
|
|
14990
15279
|
eventCount: 0,
|
|
14991
15280
|
syncedCount: 0,
|
|
@@ -15018,40 +15307,62 @@ var ChangeStreamSyncManager = class {
|
|
|
15018
15307
|
* @since v1.0.9
|
|
15019
15308
|
*/
|
|
15020
15309
|
async start() {
|
|
15310
|
+
if (this.stopPromise) await this.stopPromise;
|
|
15021
15311
|
if (this.running || !this.config.enabled) return;
|
|
15022
15312
|
if (this.startPromise) return this.startPromise;
|
|
15313
|
+
const generation = ++this.generation;
|
|
15023
15314
|
const startup = (async () => {
|
|
15024
15315
|
await this.closingStreamPromise;
|
|
15316
|
+
await this.changeQueue.catch(() => void 0);
|
|
15317
|
+
this.assertCurrentGeneration(generation, true);
|
|
15025
15318
|
if (this.running) return;
|
|
15026
15319
|
this.fatalSyncError = null;
|
|
15027
|
-
await this.
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
const
|
|
15036
|
-
this.
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
return;
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15320
|
+
await this.closeTargets();
|
|
15321
|
+
let stagedTargets = [];
|
|
15322
|
+
let stream = null;
|
|
15323
|
+
try {
|
|
15324
|
+
await this.validateEnvironment();
|
|
15325
|
+
this.assertCurrentGeneration(generation);
|
|
15326
|
+
stagedTargets = await this.initializeTargets();
|
|
15327
|
+
this.assertCurrentGeneration(generation);
|
|
15328
|
+
const resumeAfter = await this.tokenStore.load();
|
|
15329
|
+
this.assertCurrentGeneration(generation);
|
|
15330
|
+
const options = { fullDocument: "updateLookup" };
|
|
15331
|
+
if (resumeAfter) options.resumeAfter = resumeAfter;
|
|
15332
|
+
stream = this.db.watch(this.buildPipeline(), options);
|
|
15333
|
+
const activeStream = stream;
|
|
15334
|
+
activeStream.on("change", (event) => this.enqueueChange(event, generation));
|
|
15335
|
+
activeStream.on("error", (error) => {
|
|
15336
|
+
if (generation !== this.generation) return;
|
|
15337
|
+
const normalized = normalizeError(error);
|
|
15338
|
+
this.stats.errorCount += 1;
|
|
15339
|
+
this.stats.lastError = normalized;
|
|
15340
|
+
this.logger?.error?.("[Sync] change stream error", normalized);
|
|
15341
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15342
|
+
});
|
|
15343
|
+
activeStream.on("close", () => {
|
|
15344
|
+
if (generation !== this.generation || !this.running || this.changeStream !== activeStream) {
|
|
15345
|
+
this.logger?.debug?.("[Sync] change stream closed");
|
|
15346
|
+
return;
|
|
15347
|
+
}
|
|
15348
|
+
const error = createError(ErrorCodes.CONNECTION_CLOSED, "[Sync] change stream closed unexpectedly");
|
|
15349
|
+
this.stats.errorCount += 1;
|
|
15350
|
+
this.stats.lastError = error;
|
|
15351
|
+
this.logger?.warn?.("[Sync] change stream closed unexpectedly", error);
|
|
15352
|
+
this.stopAfterFatalSyncError(error, generation);
|
|
15353
|
+
});
|
|
15354
|
+
this.assertCurrentGeneration(generation);
|
|
15355
|
+
this.targets.push(...stagedTargets);
|
|
15356
|
+
stagedTargets = [];
|
|
15357
|
+
this.changeStream = activeStream;
|
|
15358
|
+
stream = null;
|
|
15359
|
+
this.running = true;
|
|
15360
|
+
this.stats.startTime = /* @__PURE__ */ new Date();
|
|
15361
|
+
} catch (error) {
|
|
15362
|
+
if (stream) await this.closeChangeStream(stream).catch(() => void 0);
|
|
15363
|
+
for (const target of stagedTargets.reverse()) await target.close().catch(() => void 0);
|
|
15364
|
+
throw error;
|
|
15365
|
+
}
|
|
15055
15366
|
})();
|
|
15056
15367
|
this.startPromise = startup;
|
|
15057
15368
|
try {
|
|
@@ -15065,16 +15376,37 @@ var ChangeStreamSyncManager = class {
|
|
|
15065
15376
|
* @since v1.0.9
|
|
15066
15377
|
*/
|
|
15067
15378
|
async stop() {
|
|
15068
|
-
this.
|
|
15069
|
-
|
|
15070
|
-
const
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15379
|
+
if (this.stopPromise) return this.stopPromise;
|
|
15380
|
+
this.stopping = true;
|
|
15381
|
+
const cleanup = (async () => {
|
|
15382
|
+
if (this.startPromise && !this.running) this.generation += 1;
|
|
15383
|
+
await this.startPromise?.catch(() => void 0);
|
|
15384
|
+
await this.changeQueue.catch(() => void 0);
|
|
15385
|
+
this.generation += 1;
|
|
15386
|
+
this.running = false;
|
|
15387
|
+
const stream = this.changeStream;
|
|
15388
|
+
this.changeStream = null;
|
|
15389
|
+
if (stream) await this.closeChangeStream(stream);
|
|
15390
|
+
else await this.closingStreamPromise;
|
|
15391
|
+
await this.closeTargets();
|
|
15392
|
+
})();
|
|
15393
|
+
this.stopPromise = cleanup;
|
|
15394
|
+
try {
|
|
15395
|
+
await cleanup;
|
|
15396
|
+
} finally {
|
|
15397
|
+
if (this.stopPromise === cleanup) this.stopPromise = null;
|
|
15398
|
+
this.stopping = false;
|
|
15399
|
+
}
|
|
15400
|
+
}
|
|
15401
|
+
assertCurrentGeneration(generation, allowPreviousFatal = false) {
|
|
15402
|
+
if (generation !== this.generation) {
|
|
15403
|
+
throw createError(ErrorCodes.CONNECTION_CLOSED, "[Sync] startup was superseded by stop or restart");
|
|
15404
|
+
}
|
|
15405
|
+
if (!allowPreviousFatal && this.fatalSyncError) throw this.fatalSyncError;
|
|
15406
|
+
}
|
|
15407
|
+
async closeTargets() {
|
|
15075
15408
|
while (this.targets.length > 0) {
|
|
15076
|
-
|
|
15077
|
-
await target?.close();
|
|
15409
|
+
await this.targets.pop()?.close();
|
|
15078
15410
|
}
|
|
15079
15411
|
}
|
|
15080
15412
|
/**
|
|
@@ -15139,11 +15471,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15139
15471
|
return pipeline;
|
|
15140
15472
|
}
|
|
15141
15473
|
async initializeTargets() {
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15474
|
+
const staged = [];
|
|
15475
|
+
try {
|
|
15476
|
+
for (const target of this.config.targets) staged.push(await this.resolveTarget(target));
|
|
15477
|
+
return staged;
|
|
15478
|
+
} catch (error) {
|
|
15479
|
+
for (const target of staged.reverse()) await target.close().catch(() => void 0);
|
|
15480
|
+
throw error;
|
|
15147
15481
|
}
|
|
15148
15482
|
}
|
|
15149
15483
|
async resolveTarget(target) {
|
|
@@ -15180,14 +15514,15 @@ var ChangeStreamSyncManager = class {
|
|
|
15180
15514
|
const client = await this.clientFactory(target.uri, target.options);
|
|
15181
15515
|
return createMongoTarget(target.name, collections, client, target.databaseName ?? this.db.databaseName, true);
|
|
15182
15516
|
}
|
|
15183
|
-
async handleChange(event) {
|
|
15184
|
-
if (this.fatalSyncError) {
|
|
15517
|
+
async handleChange(event, generation = this.generation) {
|
|
15518
|
+
if (generation !== this.generation || !this.running || this.fatalSyncError) {
|
|
15185
15519
|
this.logger?.warn?.("[Sync] skipping queued change after fatal sync error", this.fatalSyncError);
|
|
15186
15520
|
return;
|
|
15187
15521
|
}
|
|
15188
15522
|
this.stats.eventCount += 1;
|
|
15189
15523
|
this.stats.lastEventTime = /* @__PURE__ */ new Date();
|
|
15190
15524
|
if (this.config.filter && !this.config.filter(event)) {
|
|
15525
|
+
await this.saveProcessedToken(event, generation);
|
|
15191
15526
|
return;
|
|
15192
15527
|
}
|
|
15193
15528
|
let document = event.fullDocument;
|
|
@@ -15199,6 +15534,7 @@ var ChangeStreamSyncManager = class {
|
|
|
15199
15534
|
let duplicateTargets = 0;
|
|
15200
15535
|
const targetErrors = [];
|
|
15201
15536
|
for (const target of this.targets) {
|
|
15537
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15202
15538
|
if (target.collections && !target.collections.has(event.ns.coll)) {
|
|
15203
15539
|
continue;
|
|
15204
15540
|
}
|
|
@@ -15215,11 +15551,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15215
15551
|
if (idempotencyKey && this.idempotency.markMode === "start") {
|
|
15216
15552
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "start");
|
|
15217
15553
|
}
|
|
15554
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15218
15555
|
await target.apply(event, document, {
|
|
15219
15556
|
targetName: target.name,
|
|
15220
15557
|
...idempotencyKey ? { idempotencyKey } : {}
|
|
15221
15558
|
});
|
|
15222
15559
|
if (idempotencyKey && this.idempotency.markMode === "success") {
|
|
15560
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15223
15561
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "success");
|
|
15224
15562
|
}
|
|
15225
15563
|
target.stats.syncCount += 1;
|
|
@@ -15240,26 +15578,30 @@ var ChangeStreamSyncManager = class {
|
|
|
15240
15578
|
}
|
|
15241
15579
|
}
|
|
15242
15580
|
if (targetErrors.length > 0) {
|
|
15243
|
-
this.stopAfterFatalSyncError(targetErrors[0]);
|
|
15581
|
+
this.stopAfterFatalSyncError(targetErrors[0], generation);
|
|
15244
15582
|
return;
|
|
15245
15583
|
}
|
|
15246
|
-
if (
|
|
15247
|
-
|
|
15248
|
-
await this.tokenStore.save(event._id);
|
|
15249
|
-
} catch (error) {
|
|
15250
|
-
const normalized = normalizeError(error);
|
|
15251
|
-
this.stats.tokenSaveErrorCount += 1;
|
|
15252
|
-
this.stats.lastTokenSaveError = normalized;
|
|
15253
|
-
this.stats.lastError = normalized;
|
|
15254
|
-
this.stopAfterFatalSyncError(normalized);
|
|
15255
|
-
throw normalized;
|
|
15256
|
-
}
|
|
15257
|
-
this.stats.syncedCount += 1;
|
|
15584
|
+
if (succeeded === eligibleTargets) {
|
|
15585
|
+
await this.saveProcessedToken(event, generation);
|
|
15258
15586
|
if (duplicateTargets > 0 && duplicateTargets === eligibleTargets) {
|
|
15259
15587
|
this.stats.duplicateEventCount += 1;
|
|
15260
15588
|
}
|
|
15261
15589
|
}
|
|
15262
15590
|
}
|
|
15591
|
+
async saveProcessedToken(event, generation) {
|
|
15592
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15593
|
+
try {
|
|
15594
|
+
await this.tokenStore.save(event._id);
|
|
15595
|
+
} catch (error) {
|
|
15596
|
+
const normalized = normalizeError(error);
|
|
15597
|
+
this.stats.tokenSaveErrorCount += 1;
|
|
15598
|
+
this.stats.lastTokenSaveError = normalized;
|
|
15599
|
+
this.stats.lastError = normalized;
|
|
15600
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15601
|
+
throw normalized;
|
|
15602
|
+
}
|
|
15603
|
+
if (generation === this.generation && !this.fatalSyncError && this.running) this.stats.syncedCount += 1;
|
|
15604
|
+
}
|
|
15263
15605
|
buildTargetIdempotencyKey(targetName, event) {
|
|
15264
15606
|
if (!this.idempotency.enabled) {
|
|
15265
15607
|
return null;
|
|
@@ -15282,7 +15624,8 @@ var ChangeStreamSyncManager = class {
|
|
|
15282
15624
|
ts: Date.now()
|
|
15283
15625
|
}, this.idempotency.ttl));
|
|
15284
15626
|
}
|
|
15285
|
-
stopAfterFatalSyncError(error) {
|
|
15627
|
+
stopAfterFatalSyncError(error, generation) {
|
|
15628
|
+
if (generation !== this.generation) return;
|
|
15286
15629
|
this.fatalSyncError = error;
|
|
15287
15630
|
this.running = false;
|
|
15288
15631
|
const stream = this.changeStream;
|
|
@@ -15300,12 +15643,14 @@ var ChangeStreamSyncManager = class {
|
|
|
15300
15643
|
if (this.closingStreamPromise === closing) this.closingStreamPromise = null;
|
|
15301
15644
|
});
|
|
15302
15645
|
}
|
|
15303
|
-
enqueueChange(event) {
|
|
15304
|
-
this.
|
|
15646
|
+
enqueueChange(event, generation) {
|
|
15647
|
+
if (generation !== this.generation || !this.running || this.stopping || this.fatalSyncError) return;
|
|
15648
|
+
this.changeQueue = this.changeQueue.then(() => this.handleChange(event, generation)).catch((error) => {
|
|
15305
15649
|
const normalized = normalizeError(error);
|
|
15306
15650
|
this.stats.errorCount += 1;
|
|
15307
15651
|
this.stats.lastError = normalized;
|
|
15308
15652
|
this.logger?.error?.("[Sync] change handling failed", normalized);
|
|
15653
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15309
15654
|
});
|
|
15310
15655
|
}
|
|
15311
15656
|
};
|
|
@@ -15734,7 +16079,9 @@ var SSHTunnelSSH2 = class {
|
|
|
15734
16079
|
passphrase,
|
|
15735
16080
|
port = 22,
|
|
15736
16081
|
readyTimeout = 2e4,
|
|
15737
|
-
keepaliveInterval = 3e4
|
|
16082
|
+
keepaliveInterval = 3e4,
|
|
16083
|
+
hostHash,
|
|
16084
|
+
hostVerifier
|
|
15738
16085
|
} = this._sshConfig;
|
|
15739
16086
|
if (!host || !username) {
|
|
15740
16087
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH config requires: host, username");
|
|
@@ -15743,6 +16090,8 @@ var SSHTunnelSSH2 = class {
|
|
|
15743
16090
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH authentication required: provide password, privateKey, or privateKeyPath");
|
|
15744
16091
|
}
|
|
15745
16092
|
const config = { host, port, username, readyTimeout, keepaliveInterval };
|
|
16093
|
+
if (hostHash !== void 0) config.hostHash = hostHash;
|
|
16094
|
+
if (hostVerifier !== void 0) config.hostVerifier = hostVerifier;
|
|
15746
16095
|
if (password) {
|
|
15747
16096
|
config.password = password;
|
|
15748
16097
|
} else if (privateKey) {
|
|
@@ -15761,6 +16110,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15761
16110
|
return new Promise((resolve, reject) => {
|
|
15762
16111
|
const ssh = new SshClient();
|
|
15763
16112
|
let settled = false;
|
|
16113
|
+
const fail = (err) => {
|
|
16114
|
+
if (settled) {
|
|
16115
|
+
this.markDisconnected();
|
|
16116
|
+
return;
|
|
16117
|
+
}
|
|
16118
|
+
settled = true;
|
|
16119
|
+
this.isConnected = false;
|
|
16120
|
+
this.localPort = null;
|
|
16121
|
+
this.server = null;
|
|
16122
|
+
this.sshClient = null;
|
|
16123
|
+
for (const socket of this._activeSockets) socket.destroy();
|
|
16124
|
+
this._activeSockets.clear();
|
|
16125
|
+
void closeServer(server);
|
|
16126
|
+
try {
|
|
16127
|
+
ssh.end();
|
|
16128
|
+
} catch {
|
|
16129
|
+
}
|
|
16130
|
+
reject(err);
|
|
16131
|
+
};
|
|
15764
16132
|
const server = net.createServer((socket) => {
|
|
15765
16133
|
this._activeSockets.add(socket);
|
|
15766
16134
|
socket.on("close", () => this._activeSockets.delete(socket));
|
|
@@ -15786,12 +16154,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15786
16154
|
}
|
|
15787
16155
|
);
|
|
15788
16156
|
});
|
|
15789
|
-
server.on("error",
|
|
15790
|
-
if (!settled) {
|
|
15791
|
-
settled = true;
|
|
15792
|
-
reject(err);
|
|
15793
|
-
}
|
|
15794
|
-
});
|
|
16157
|
+
server.on("error", fail);
|
|
15795
16158
|
server.listen(this._sshConfig.localPort ?? 0, "127.0.0.1", () => {
|
|
15796
16159
|
this.server = server;
|
|
15797
16160
|
const addr = server.address();
|
|
@@ -15806,25 +16169,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15806
16169
|
});
|
|
15807
16170
|
ssh.on("error", (err) => {
|
|
15808
16171
|
if (!settled) {
|
|
15809
|
-
|
|
15810
|
-
server.close();
|
|
15811
|
-
reject(err);
|
|
16172
|
+
fail(err);
|
|
15812
16173
|
return;
|
|
15813
16174
|
}
|
|
15814
16175
|
this.markDisconnected();
|
|
15815
16176
|
});
|
|
15816
16177
|
const handleDisconnect = () => {
|
|
15817
16178
|
if (!settled) {
|
|
15818
|
-
|
|
15819
|
-
server.close();
|
|
15820
|
-
reject(createError(ErrorCodes.CONNECTION_FAILED, "SSH connection closed before the tunnel became ready"));
|
|
16179
|
+
fail(createError(ErrorCodes.CONNECTION_FAILED, "SSH connection closed before the tunnel became ready"));
|
|
15821
16180
|
return;
|
|
15822
16181
|
}
|
|
15823
16182
|
this.markDisconnected();
|
|
15824
16183
|
};
|
|
15825
16184
|
ssh.on("close", handleDisconnect);
|
|
15826
16185
|
ssh.on("end", handleDisconnect);
|
|
15827
|
-
|
|
16186
|
+
try {
|
|
16187
|
+
ssh.connect(authConfig);
|
|
16188
|
+
} catch (err) {
|
|
16189
|
+
fail(err);
|
|
16190
|
+
}
|
|
15828
16191
|
});
|
|
15829
16192
|
});
|
|
15830
16193
|
}
|
|
@@ -15855,6 +16218,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15855
16218
|
ssh?.end();
|
|
15856
16219
|
}
|
|
15857
16220
|
markDisconnected() {
|
|
16221
|
+
const ssh = this.sshClient;
|
|
15858
16222
|
this.isConnected = false;
|
|
15859
16223
|
this.localPort = null;
|
|
15860
16224
|
this.sshClient = null;
|
|
@@ -15865,6 +16229,10 @@ var SSHTunnelSSH2 = class {
|
|
|
15865
16229
|
}
|
|
15866
16230
|
this._activeSockets.clear();
|
|
15867
16231
|
void closeServer(server);
|
|
16232
|
+
try {
|
|
16233
|
+
ssh?.end();
|
|
16234
|
+
} catch {
|
|
16235
|
+
}
|
|
15868
16236
|
}
|
|
15869
16237
|
};
|
|
15870
16238
|
function parseHostFromUri(uri) {
|
|
@@ -15951,12 +16319,17 @@ async function prepareSshTunnelConnectConfig(connectConfig, databaseName, logger
|
|
|
15951
16319
|
const remotePort = Number(cfg["dstPort"] ?? rawCfg["remotePort"] ?? rawCfg["mongoPort"] ?? parsePortFromUri(uri));
|
|
15952
16320
|
const tunnel = new SSHTunnelSSH2(sshCfg, remoteHost, remotePort, { name: databaseName });
|
|
15953
16321
|
logger.info?.(`[SSH] Establishing tunnel to ${remoteHost}:${remotePort} via ${String(cfg["host"])}`);
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
16322
|
+
try {
|
|
16323
|
+
await tunnel.connect();
|
|
16324
|
+
logger.info?.(`[SSH] Tunnel ready \u2014 local address: ${tunnel.getLocalAddress()}`);
|
|
16325
|
+
return {
|
|
16326
|
+
tunnel,
|
|
16327
|
+
connectConfig: connectConfig?.uri ? { ...connectConfig, uri: tunnel.getTunnelUri("mongodb", connectConfig.uri) } : connectConfig
|
|
16328
|
+
};
|
|
16329
|
+
} catch (error) {
|
|
16330
|
+
await tunnel.close();
|
|
16331
|
+
throw error;
|
|
16332
|
+
}
|
|
15960
16333
|
}
|
|
15961
16334
|
|
|
15962
16335
|
// src/entry/runtime-schema-dsl.ts
|
|
@@ -16002,14 +16375,14 @@ async function cleanupFailedRuntimeConnection(resources, logger) {
|
|
|
16002
16375
|
}
|
|
16003
16376
|
|
|
16004
16377
|
// src/capabilities/function-cache/index.ts
|
|
16005
|
-
var
|
|
16378
|
+
var import_cache_hub4 = require("cache-hub");
|
|
16006
16379
|
var import_function_cache = require("cache-hub/function-cache");
|
|
16007
16380
|
function isValidCache(cache) {
|
|
16008
16381
|
return cache !== null && typeof cache === "object" && typeof cache.get === "function" && typeof cache.set === "function";
|
|
16009
16382
|
}
|
|
16010
16383
|
function resolveCacheSource(cacheOrDb) {
|
|
16011
16384
|
if (cacheOrDb === void 0) {
|
|
16012
|
-
return new
|
|
16385
|
+
return new import_cache_hub4.MemoryCache();
|
|
16013
16386
|
}
|
|
16014
16387
|
if (isValidCache(cacheOrDb)) {
|
|
16015
16388
|
return cacheOrDb;
|
|
@@ -16095,7 +16468,7 @@ function withCache(fn, options = {}) {
|
|
|
16095
16468
|
namespace: namespace ?? "fn",
|
|
16096
16469
|
keyBuilder,
|
|
16097
16470
|
condition,
|
|
16098
|
-
cache: externalCache ?? new
|
|
16471
|
+
cache: externalCache ?? new import_cache_hub4.MemoryCache(),
|
|
16099
16472
|
enableStats
|
|
16100
16473
|
});
|
|
16101
16474
|
const baseStats = wrapped.stats.bind(wrapped);
|
|
@@ -16845,9 +17218,8 @@ var MonSQLizeRuntime = class {
|
|
|
16845
17218
|
driverError = error instanceof Error ? error.message : String(error);
|
|
16846
17219
|
}
|
|
16847
17220
|
}
|
|
16848
|
-
const
|
|
16849
|
-
const poolHealth =
|
|
16850
|
-
const distributedStats = this._distributedInvalidator?.getStats() ?? null;
|
|
17221
|
+
const health = summarizeRuntimeHealth(this.options.cache, this._cache, this._poolManager, this._distributedInvalidator);
|
|
17222
|
+
const { cacheStats, poolHealth, distributedStats, cacheEnabled, distributedEnabled, cacheProbeStatus, distributedStatus, poolsStatus } = health;
|
|
16851
17223
|
const connected = this._connected && driverConnected;
|
|
16852
17224
|
return {
|
|
16853
17225
|
status: connected ? "up" : "down",
|
|
@@ -16855,7 +17227,7 @@ var MonSQLizeRuntime = class {
|
|
|
16855
17227
|
driver: { connected: driverConnected, ...driverError ? { error: driverError } : {} },
|
|
16856
17228
|
defaults: this.getDefaults(),
|
|
16857
17229
|
cache: {
|
|
16858
|
-
enabled:
|
|
17230
|
+
enabled: cacheEnabled,
|
|
16859
17231
|
stats: cacheStats,
|
|
16860
17232
|
pools: poolHealth,
|
|
16861
17233
|
distributed: distributedStats
|
|
@@ -16867,18 +17239,18 @@ var MonSQLizeRuntime = class {
|
|
|
16867
17239
|
...driverError ? { error: driverError } : {}
|
|
16868
17240
|
},
|
|
16869
17241
|
cache: {
|
|
16870
|
-
status:
|
|
16871
|
-
enabled:
|
|
17242
|
+
status: cacheProbeStatus,
|
|
17243
|
+
enabled: cacheEnabled,
|
|
16872
17244
|
hasStats: cacheStats !== void 0
|
|
16873
17245
|
},
|
|
16874
17246
|
distributedCacheInvalidator: {
|
|
16875
|
-
status:
|
|
16876
|
-
enabled:
|
|
17247
|
+
status: distributedStatus,
|
|
17248
|
+
enabled: distributedEnabled,
|
|
16877
17249
|
stats: distributedStats
|
|
16878
17250
|
},
|
|
16879
17251
|
pools: {
|
|
16880
|
-
status:
|
|
16881
|
-
enabled:
|
|
17252
|
+
status: poolsStatus,
|
|
17253
|
+
enabled: this._poolManager !== null,
|
|
16882
17254
|
health: poolHealth
|
|
16883
17255
|
}
|
|
16884
17256
|
},
|
|
@@ -17829,6 +18201,27 @@ function ejsonLine(value) {
|
|
|
17829
18201
|
function ejsonManifest(value) {
|
|
17830
18202
|
return import_mongodb11.BSON.EJSON.stringify(value, void 0, 2, { relaxed: false });
|
|
17831
18203
|
}
|
|
18204
|
+
function manifestInteger(value, field, minimum) {
|
|
18205
|
+
const numeric = value instanceof import_mongodb11.Int32 ? value.valueOf() : import_mongodb11.Long.isLong(value) ? value.toBigInt() : value;
|
|
18206
|
+
if (typeof numeric === "bigint") {
|
|
18207
|
+
if (numeric < BigInt(minimum) || numeric > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
18208
|
+
throw new Error(`invalid backup ${field}`);
|
|
18209
|
+
}
|
|
18210
|
+
return Number(numeric);
|
|
18211
|
+
}
|
|
18212
|
+
if (typeof numeric !== "number" || !Number.isSafeInteger(numeric) || numeric < minimum) {
|
|
18213
|
+
throw new Error(`invalid backup ${field}`);
|
|
18214
|
+
}
|
|
18215
|
+
return numeric;
|
|
18216
|
+
}
|
|
18217
|
+
function decodeManifestControls(raw) {
|
|
18218
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("invalid backup manifest");
|
|
18219
|
+
const manifest = raw;
|
|
18220
|
+
manifest.version = manifestInteger(manifest.version, "manifest", 1);
|
|
18221
|
+
manifest.entryCount = manifestInteger(manifest.entryCount, "entry count", 0);
|
|
18222
|
+
if (manifest.maxBytes !== void 0) manifest.maxBytes = manifestInteger(manifest.maxBytes, "max bytes", 1);
|
|
18223
|
+
return manifest;
|
|
18224
|
+
}
|
|
17832
18225
|
function sanitizeName(value) {
|
|
17833
18226
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "data-task";
|
|
17834
18227
|
}
|
|
@@ -18005,10 +18398,9 @@ async function createRestoreSafetyBackup(source, entries, beforeIndexes, targetH
|
|
|
18005
18398
|
}
|
|
18006
18399
|
async function readDataTaskBackup(ref) {
|
|
18007
18400
|
try {
|
|
18008
|
-
const manifest = import_mongodb11.BSON.EJSON.parse(await (0, import_promises2.readFile)(ref.manifestPath, "utf8"), { relaxed:
|
|
18401
|
+
const manifest = decodeManifestControls(import_mongodb11.BSON.EJSON.parse(await (0, import_promises2.readFile)(ref.manifestPath, "utf8"), { relaxed: false }));
|
|
18009
18402
|
if (manifest.kind !== "monsqlize-data-task-backup" || manifest.version !== 1 || manifest.runId !== ref.runId) throw new Error("invalid backup manifest");
|
|
18010
18403
|
if (manifest.compression !== "gzip" && manifest.compression !== "none") throw new Error("invalid backup compression");
|
|
18011
|
-
if (!Number.isSafeInteger(manifest.entryCount) || manifest.entryCount < 0) throw new Error("invalid backup entry count");
|
|
18012
18404
|
if (typeof manifest.checksum !== "string" || !/^[a-f0-9]{64}$/i.test(manifest.checksum)) throw new Error("invalid backup checksum");
|
|
18013
18405
|
if (typeof manifest.dataFile !== "string" || manifest.dataFile.length === 0 || import_node_path4.default.isAbsolute(manifest.dataFile) || import_node_path4.default.basename(manifest.dataFile) !== manifest.dataFile) {
|
|
18014
18406
|
throw new Error("backup data file must stay beside its manifest");
|
|
@@ -18339,6 +18731,7 @@ async function acquireDataTaskLease(host, options) {
|
|
|
18339
18731
|
}
|
|
18340
18732
|
|
|
18341
18733
|
// src/capabilities/data-tasks/job-restore.ts
|
|
18734
|
+
var import_mongodb12 = require("mongodb");
|
|
18342
18735
|
async function findCurrent(collection, identity) {
|
|
18343
18736
|
let chain = collection.find(identity);
|
|
18344
18737
|
if (typeof chain.limit === "function") chain = chain.limit(2);
|
|
@@ -18359,13 +18752,20 @@ function restoreApproval(hashes) {
|
|
|
18359
18752
|
};
|
|
18360
18753
|
return { ...payload, token: hashDataTaskValue(payload) };
|
|
18361
18754
|
}
|
|
18755
|
+
function comparableIndexKey(key) {
|
|
18756
|
+
return Object.fromEntries(Object.entries(key).map(([field, value]) => {
|
|
18757
|
+
if (value instanceof import_mongodb12.Int32) return [field, value.valueOf()];
|
|
18758
|
+
if (import_mongodb12.Long.isLong(value) && value.toBigInt() <= BigInt(Number.MAX_SAFE_INTEGER) && value.toBigInt() >= BigInt(Number.MIN_SAFE_INTEGER)) return [field, Number(value.toBigInt())];
|
|
18759
|
+
return [field, value];
|
|
18760
|
+
}));
|
|
18761
|
+
}
|
|
18362
18762
|
function exactIndex(index, current) {
|
|
18363
|
-
return classifyDataTaskIndexes([{ key: index.key, name: index.name, options: index.options }], current)[0]?.status === "existing";
|
|
18763
|
+
return classifyDataTaskIndexes([{ key: comparableIndexKey(index.key), name: index.name, options: index.options }], current)[0]?.status === "existing";
|
|
18364
18764
|
}
|
|
18365
18765
|
function pendingIndexMatches(index, current) {
|
|
18366
18766
|
return current.filter((candidate) => {
|
|
18367
18767
|
if (index.name && candidate.name !== index.name) return false;
|
|
18368
|
-
return classifyDataTaskIndexes([{ key: index.key, options: index.options }], [candidate])[0]?.status === "existing";
|
|
18768
|
+
return classifyDataTaskIndexes([{ key: comparableIndexKey(index.key), options: index.options }], [candidate])[0]?.status === "existing";
|
|
18369
18769
|
});
|
|
18370
18770
|
}
|
|
18371
18771
|
function plannedDocumentMatches(current, entry) {
|
|
@@ -18633,7 +19033,7 @@ async function restoreDataTaskPlan(plan, target, targetHint, lease) {
|
|
|
18633
19033
|
await updateDataTaskBackup(safety, (manifest) => {
|
|
18634
19034
|
manifest.pendingIndexes = [...manifest.pendingIndexes ?? [], pending];
|
|
18635
19035
|
});
|
|
18636
|
-
await collection.createIndex(index.key, { ...index.options, name: index.name });
|
|
19036
|
+
await collection.createIndex(comparableIndexKey(index.key), { ...index.options, name: index.name });
|
|
18637
19037
|
if (!exactIndex(index, await collection.listIndexes())) {
|
|
18638
19038
|
throw new DataTaskJobError("RESTORE_FAILED", `index "${index.name}" failed immediate readback.`, "restore");
|
|
18639
19039
|
}
|