monsqlize 3.2.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 +8 -2
- package/README.md +56 -6
- package/changelogs/v3.3.0.md +29 -0
- package/changelogs/v3.3.1.md +28 -0
- package/dist/cjs/cli/data-task.cjs +5 -3
- package/dist/cjs/index.cjs +1037 -630
- package/dist/cjs/transaction/Transaction.cjs +33 -5
- package/dist/cjs/transaction/TransactionManager.cjs +38 -16
- package/dist/esm/index.mjs +1042 -634
- 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 +42 -0
- package/dist/types/model.d.ts +42 -0
- package/dist/types/monsqlize.d.mts +32 -9
- package/dist/types/monsqlize.d.ts +32 -9
- package/dist/types/runtime.d.mts +3 -1
- package/dist/types/runtime.d.ts +3 -1
- package/licenses/production-dependencies.json +1 -1
- package/package.json +5 -3
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") {
|
|
@@ -549,6 +541,9 @@ function normalizePopulateConfig(path5) {
|
|
|
549
541
|
}
|
|
550
542
|
|
|
551
543
|
// src/capabilities/model/model-registry.ts
|
|
544
|
+
function defineModel(collectionName, definition) {
|
|
545
|
+
return { collectionName, definition };
|
|
546
|
+
}
|
|
552
547
|
var Model = class {
|
|
553
548
|
static {
|
|
554
549
|
// collection name → registered model definition
|
|
@@ -563,21 +558,19 @@ var Model = class {
|
|
|
563
558
|
/** Collection names that have been redefined (used by the runtime to trigger hot-reload). */
|
|
564
559
|
this._redefinedNames = /* @__PURE__ */ new Set();
|
|
565
560
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
* Validates the definition and resolves the timestamps option.
|
|
570
|
-
*/
|
|
571
|
-
static define(collectionName, definition) {
|
|
561
|
+
static define(collectionNameOrDescriptor, definition) {
|
|
562
|
+
const collectionName = typeof collectionNameOrDescriptor === "string" ? collectionNameOrDescriptor : collectionNameOrDescriptor.collectionName;
|
|
563
|
+
const modelDefinition = typeof collectionNameOrDescriptor === "string" ? definition : collectionNameOrDescriptor.definition;
|
|
572
564
|
const normalizedName = validateCollectionName(collectionName);
|
|
573
565
|
if (this.registry.has(normalizedName)) {
|
|
574
566
|
throw createError(ErrorCodes.MODEL_ALREADY_EXISTS, `Model '${normalizedName}' is already defined.`);
|
|
575
567
|
}
|
|
576
|
-
validateDefinition(
|
|
577
|
-
|
|
568
|
+
validateDefinition(modelDefinition);
|
|
569
|
+
const resolvedDefinition = modelDefinition;
|
|
570
|
+
processTimestamps(resolvedDefinition);
|
|
578
571
|
this.registry.set(normalizedName, {
|
|
579
572
|
collectionName: normalizedName,
|
|
580
|
-
definition
|
|
573
|
+
definition: resolvedDefinition
|
|
581
574
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
582
575
|
});
|
|
583
576
|
this.bumpRevision(normalizedName);
|
|
@@ -734,6 +727,40 @@ function serializeDocument(document) {
|
|
|
734
727
|
}
|
|
735
728
|
|
|
736
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
|
+
}
|
|
737
764
|
var VERSION_LOOKUP_OPTION_KEYS = [
|
|
738
765
|
"session",
|
|
739
766
|
"readConcern",
|
|
@@ -819,11 +846,14 @@ function mapModelSchemaValidationErrors(errors = []) {
|
|
|
819
846
|
}
|
|
820
847
|
function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
821
848
|
const shouldValidate = context.validateEnabled || options?.validate === true;
|
|
822
|
-
if (!shouldValidate) {
|
|
849
|
+
if (!shouldValidate || options?.skipValidation) {
|
|
823
850
|
return document;
|
|
824
851
|
}
|
|
825
|
-
if (
|
|
826
|
-
|
|
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
|
+
);
|
|
827
857
|
}
|
|
828
858
|
if (!context.schemaCache || !context.schemaValidateFn) {
|
|
829
859
|
return document;
|
|
@@ -857,22 +887,31 @@ function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
|
857
887
|
}
|
|
858
888
|
return normalized;
|
|
859
889
|
}
|
|
890
|
+
function buildModelVisibleSoftDeleteCondition(config) {
|
|
891
|
+
return { [config.field]: config.type === "boolean" ? { $ne: true } : null };
|
|
892
|
+
}
|
|
860
893
|
function applyModelSoftDeleteFilter(query, options, softDeleteConfig) {
|
|
861
894
|
if (!softDeleteConfig?.enabled) {
|
|
862
895
|
return query ?? {};
|
|
863
896
|
}
|
|
864
897
|
const resolvedOptions = options ?? {};
|
|
865
898
|
const resolvedQuery = query ?? {};
|
|
866
|
-
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)) {
|
|
867
906
|
return resolvedQuery;
|
|
868
907
|
}
|
|
869
908
|
if (resolvedOptions.withDeleted) {
|
|
870
909
|
return resolvedQuery;
|
|
871
910
|
}
|
|
872
911
|
if (resolvedOptions.onlyDeleted) {
|
|
873
|
-
return { ...resolvedQuery, [softDeleteConfig.field]: { $ne: null } };
|
|
912
|
+
return { ...resolvedQuery, [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : { $ne: null } };
|
|
874
913
|
}
|
|
875
|
-
return { ...resolvedQuery,
|
|
914
|
+
return { ...resolvedQuery, ...buildModelVisibleSoftDeleteCondition(softDeleteConfig) };
|
|
876
915
|
}
|
|
877
916
|
function applyModelInsertTimestamps(document, timestampsConfig, nowFactory) {
|
|
878
917
|
if (!timestampsConfig) {
|
|
@@ -1167,7 +1206,9 @@ function projectPopulatedDocument(document, select, nestedPaths) {
|
|
|
1167
1206
|
return document;
|
|
1168
1207
|
}
|
|
1169
1208
|
const nestedRelationFields = nestedPaths.map((path5) => normalizePopulateConfig(path5).path);
|
|
1170
|
-
|
|
1209
|
+
const projected = pickFields(document, [.../* @__PURE__ */ new Set([...select, ...nestedRelationFields])]);
|
|
1210
|
+
if (!select.includes("_id")) delete projected._id;
|
|
1211
|
+
return projected;
|
|
1171
1212
|
}
|
|
1172
1213
|
async function populateModelPath(context, docs, path5, traversalState) {
|
|
1173
1214
|
const config = normalizePopulateConfig(path5);
|
|
@@ -1189,7 +1230,13 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1189
1230
|
}
|
|
1190
1231
|
const localValues = docs.map((doc) => getByPath(doc, relation.localField));
|
|
1191
1232
|
const keys = unique(localValues.flatMap((value) => flattenComparableValues(value)));
|
|
1192
|
-
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) {
|
|
1193
1240
|
for (const doc of docs) {
|
|
1194
1241
|
doc[config.path] = relation.single ? null : [];
|
|
1195
1242
|
}
|
|
@@ -1209,36 +1256,63 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1209
1256
|
...effectiveSelect,
|
|
1210
1257
|
relation.foreignField,
|
|
1211
1258
|
"_id",
|
|
1212
|
-
...
|
|
1259
|
+
...Object.keys(config.sort ?? {}),
|
|
1260
|
+
...collectNestedLocalFields(relatedModel, nestedPaths),
|
|
1261
|
+
...relatedModel?.softDeleteConfig?.enabled ? [relatedModel.softDeleteConfig.field] : []
|
|
1213
1262
|
])] : [];
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
...config.
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
+
});
|
|
1224
1287
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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;
|
|
1303
|
+
}
|
|
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)));
|
|
1236
1308
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
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;
|
|
1239
1315
|
}
|
|
1240
|
-
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1241
|
-
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1242
1316
|
}
|
|
1243
1317
|
return docs;
|
|
1244
1318
|
} finally {
|
|
@@ -1439,12 +1513,28 @@ async function saveModelDocument(collection, document, options = {}) {
|
|
|
1439
1513
|
document._id = result.insertedId;
|
|
1440
1514
|
return document;
|
|
1441
1515
|
}
|
|
1442
|
-
async function removeModelDocument(collection, document) {
|
|
1516
|
+
async function removeModelDocument(collection, document, deleteOne = (filter) => collection.deleteOne(filter)) {
|
|
1443
1517
|
if (document._id === void 0) {
|
|
1444
1518
|
return false;
|
|
1445
1519
|
}
|
|
1446
|
-
const result = await
|
|
1447
|
-
|
|
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;
|
|
1448
1538
|
}
|
|
1449
1539
|
|
|
1450
1540
|
// src/capabilities/model/model-soft-delete-helpers.ts
|
|
@@ -1452,7 +1542,53 @@ function deletedFilter(filter, softDeleteConfig) {
|
|
|
1452
1542
|
if (!softDeleteConfig) {
|
|
1453
1543
|
return filter ?? {};
|
|
1454
1544
|
}
|
|
1455
|
-
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];
|
|
1456
1592
|
}
|
|
1457
1593
|
function findWithDeletedDocuments(context, query, options) {
|
|
1458
1594
|
return new PopulatePromise(async (paths) => {
|
|
@@ -1495,7 +1631,7 @@ function restoreSoftDeletedDocuments(context, filter, options) {
|
|
|
1495
1631
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1496
1632
|
}
|
|
1497
1633
|
return context.collection.updateOne(
|
|
1498
|
-
|
|
1634
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1499
1635
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1500
1636
|
options
|
|
1501
1637
|
);
|
|
@@ -1506,7 +1642,7 @@ function restoreManySoftDeletedDocuments(context, filter, options) {
|
|
|
1506
1642
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1507
1643
|
}
|
|
1508
1644
|
return context.collection.updateMany(
|
|
1509
|
-
|
|
1645
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1510
1646
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1511
1647
|
options
|
|
1512
1648
|
);
|
|
@@ -3122,6 +3258,9 @@ var modelWriteSourceStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
|
3122
3258
|
function isPlainObject2(value) {
|
|
3123
3259
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3124
3260
|
}
|
|
3261
|
+
function ownValue(record, key) {
|
|
3262
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
3263
|
+
}
|
|
3125
3264
|
function invalidConfig(path5, reason) {
|
|
3126
3265
|
throw createError(
|
|
3127
3266
|
ErrorCodes.INVALID_OPERATION,
|
|
@@ -3142,19 +3281,19 @@ function normalizeRule(input, path5) {
|
|
|
3142
3281
|
if (!isPlainObject2(input)) {
|
|
3143
3282
|
invalidConfig(path5, "rule must be a string mode or an object");
|
|
3144
3283
|
}
|
|
3145
|
-
const mode = input
|
|
3284
|
+
const mode = ownValue(input, "mode") ?? DEFAULT_RULE.mode;
|
|
3146
3285
|
if (!ALLOWED_MODES.has(mode)) {
|
|
3147
3286
|
invalidConfig(`${path5}.mode`, 'must be "allow-both" or "model-only"');
|
|
3148
3287
|
}
|
|
3149
|
-
const raw = input
|
|
3288
|
+
const raw = ownValue(input, "raw") ?? DEFAULT_RULE.raw;
|
|
3150
3289
|
if (!ALLOWED_RAW.has(raw)) {
|
|
3151
3290
|
invalidConfig(`${path5}.raw`, 'must be "inherit", "allow", or "block"');
|
|
3152
3291
|
}
|
|
3153
|
-
const management = input
|
|
3292
|
+
const management = ownValue(input, "management") ?? DEFAULT_RULE.management;
|
|
3154
3293
|
if (!ALLOWED_MANAGEMENT.has(management)) {
|
|
3155
3294
|
invalidConfig(`${path5}.management`, 'must be "inherit", "allow", or "block"');
|
|
3156
3295
|
}
|
|
3157
|
-
const onViolation = input
|
|
3296
|
+
const onViolation = ownValue(input, "onViolation") ?? DEFAULT_RULE.onViolation;
|
|
3158
3297
|
if (!ALLOWED_VIOLATION_ACTIONS.has(onViolation)) {
|
|
3159
3298
|
invalidConfig(`${path5}.onViolation`, 'must be "throw" or "warn"');
|
|
3160
3299
|
}
|
|
@@ -3170,15 +3309,15 @@ function validateWritePathPolicyConfig(options) {
|
|
|
3170
3309
|
}
|
|
3171
3310
|
function normalizeWritePathPolicy(options) {
|
|
3172
3311
|
if (options === void 0) {
|
|
3173
|
-
return { default: { ...DEFAULT_RULE }, namespaces:
|
|
3312
|
+
return { default: { ...DEFAULT_RULE }, namespaces: /* @__PURE__ */ Object.create(null), enabled: false };
|
|
3174
3313
|
}
|
|
3175
3314
|
if (!isPlainObject2(options)) {
|
|
3176
3315
|
invalidConfig("writePathPolicy", "must be an object");
|
|
3177
3316
|
}
|
|
3178
3317
|
const typedOptions = options;
|
|
3179
|
-
const defaultRule = normalizeRule(typedOptions
|
|
3180
|
-
const namespacesInput = options
|
|
3181
|
-
const namespaces =
|
|
3318
|
+
const defaultRule = normalizeRule(ownValue(typedOptions, "default"), "writePathPolicy.default");
|
|
3319
|
+
const namespacesInput = ownValue(options, "namespaces");
|
|
3320
|
+
const namespaces = /* @__PURE__ */ Object.create(null);
|
|
3182
3321
|
if (namespacesInput !== void 0) {
|
|
3183
3322
|
if (!isPlainObject2(namespacesInput)) {
|
|
3184
3323
|
invalidConfig("writePathPolicy.namespaces", "must be an object");
|
|
@@ -3226,8 +3365,8 @@ function resolveWritePathRule(policy, namespace) {
|
|
|
3226
3365
|
if (candidate === "default") {
|
|
3227
3366
|
return { key: "default", rule: effectivePolicy.default };
|
|
3228
3367
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3368
|
+
if (Object.prototype.hasOwnProperty.call(effectivePolicy.namespaces, candidate)) {
|
|
3369
|
+
const rule = effectivePolicy.namespaces[candidate];
|
|
3231
3370
|
return { key: candidate, rule };
|
|
3232
3371
|
}
|
|
3233
3372
|
}
|
|
@@ -3370,16 +3509,17 @@ var fallbackModelIndexTasks = /* @__PURE__ */ new Map();
|
|
|
3370
3509
|
function toCompatDefinition(definition) {
|
|
3371
3510
|
return definition;
|
|
3372
3511
|
}
|
|
3373
|
-
function stableIndexStringify(value) {
|
|
3512
|
+
function stableIndexStringify(value, mode = "options") {
|
|
3374
3513
|
if (value instanceof Date) {
|
|
3375
3514
|
return JSON.stringify(value.toISOString());
|
|
3376
3515
|
}
|
|
3377
3516
|
if (Array.isArray(value)) {
|
|
3378
|
-
return `[${value.map((item) => stableIndexStringify(item)).join(",")}]`;
|
|
3517
|
+
return `[${value.map((item) => stableIndexStringify(item, mode)).join(",")}]`;
|
|
3379
3518
|
}
|
|
3380
3519
|
if (value && typeof value === "object") {
|
|
3381
|
-
const entries = Object.entries(value).filter(([, current]) => current !== void 0)
|
|
3382
|
-
|
|
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(",")}}`;
|
|
3383
3523
|
}
|
|
3384
3524
|
return JSON.stringify(value) ?? "undefined";
|
|
3385
3525
|
}
|
|
@@ -3413,7 +3553,7 @@ function declaredOptionEntries(options) {
|
|
|
3413
3553
|
});
|
|
3414
3554
|
}
|
|
3415
3555
|
function indexOptionsMatch(existing, declared) {
|
|
3416
|
-
if (stableIndexStringify(getExistingIndexKey(existing)) !== stableIndexStringify(declared.key)) {
|
|
3556
|
+
if (stableIndexStringify(getExistingIndexKey(existing), "key") !== stableIndexStringify(declared.key, "key")) {
|
|
3417
3557
|
return false;
|
|
3418
3558
|
}
|
|
3419
3559
|
for (const [name, value] of declaredOptionEntries(declared.options)) {
|
|
@@ -3432,8 +3572,8 @@ function findExistingIndexByName(existingIndexes, name) {
|
|
|
3432
3572
|
return existingIndexes.find((index) => index.name === name);
|
|
3433
3573
|
}
|
|
3434
3574
|
function findExistingIndexByKey(existingIndexes, key) {
|
|
3435
|
-
const fingerprint = stableIndexStringify(key);
|
|
3436
|
-
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index)) === fingerprint);
|
|
3575
|
+
const fingerprint = stableIndexStringify(key, "key");
|
|
3576
|
+
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index), "key") === fingerprint);
|
|
3437
3577
|
}
|
|
3438
3578
|
function createIndexEnsureError(message, result, cause) {
|
|
3439
3579
|
return createError(ErrorCodes.MONGODB_ERROR, message, [result], cause);
|
|
@@ -4271,12 +4411,17 @@ function shouldConvertQueryField(autoConvert, path5, depth) {
|
|
|
4271
4411
|
if (!config.enabled || depth > config.maxDepth) return false;
|
|
4272
4412
|
return shouldConvertObjectIdPath(path5, config);
|
|
4273
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
|
+
}
|
|
4274
4419
|
function normalizeQueryArray(value, autoConvert, path5, depth) {
|
|
4275
4420
|
return value.map((item, index) => {
|
|
4276
4421
|
if (typeof item === "string" && item.length === 24 && import_mongodb2.ObjectId.isValid(item) && shouldConvertQueryField(autoConvert, path5, depth)) {
|
|
4277
4422
|
return new import_mongodb2.ObjectId(item);
|
|
4278
4423
|
}
|
|
4279
|
-
if (
|
|
4424
|
+
if (isPlainQueryRecord(item)) {
|
|
4280
4425
|
return normalizeQueryFilter(item, autoConvert, `${path5}[${index}]`, depth + 1);
|
|
4281
4426
|
}
|
|
4282
4427
|
return item;
|
|
@@ -4292,7 +4437,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4292
4437
|
}
|
|
4293
4438
|
for (const [key, value] of Object.entries(filter)) {
|
|
4294
4439
|
if (key === "$and" || key === "$or" || key === "$nor") {
|
|
4295
|
-
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;
|
|
4296
4441
|
continue;
|
|
4297
4442
|
}
|
|
4298
4443
|
const currentPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
@@ -4301,7 +4446,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4301
4446
|
result[key] = shouldConvert ? new import_mongodb2.ObjectId(value) : value;
|
|
4302
4447
|
} else if (Array.isArray(value)) {
|
|
4303
4448
|
result[key] = shouldConvert ? normalizeQueryArray(value, autoConvert, currentPath, depth + 1) : value;
|
|
4304
|
-
} else if (
|
|
4449
|
+
} else if (isPlainQueryRecord(value)) {
|
|
4305
4450
|
const nested = value;
|
|
4306
4451
|
const hasOperators = Object.keys(nested).some((k) => k.startsWith("$"));
|
|
4307
4452
|
if (hasOperators) {
|
|
@@ -4313,7 +4458,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4313
4458
|
);
|
|
4314
4459
|
} else if (shouldConvert && QUERY_OBJECTID_SCALAR_OPERATORS.has(op) && typeof opVal === "string" && opVal.length === 24 && import_mongodb2.ObjectId.isValid(opVal)) {
|
|
4315
4460
|
nestedResult[op] = new import_mongodb2.ObjectId(opVal);
|
|
4316
|
-
} else if (op === "$elemMatch" &&
|
|
4461
|
+
} else if (op === "$elemMatch" && isPlainQueryRecord(opVal)) {
|
|
4317
4462
|
nestedResult[op] = normalizeQueryFilter(opVal, autoConvert, currentPath, depth + 1);
|
|
4318
4463
|
} else {
|
|
4319
4464
|
nestedResult[op] = opVal;
|
|
@@ -4487,36 +4632,29 @@ async function isCollectionCacheBarrierActive(queryCache, collection, defaults =
|
|
|
4487
4632
|
buildCollectionCacheBarrierNamespaces(collection, defaults)
|
|
4488
4633
|
);
|
|
4489
4634
|
}
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
if (value
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
if (value instanceof
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
}
|
|
4514
|
-
return sorted;
|
|
4515
|
-
}
|
|
4516
|
-
return value;
|
|
4517
|
-
}
|
|
4518
|
-
function stableCacheKeyString(value) {
|
|
4519
|
-
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))}`;
|
|
4520
4658
|
}
|
|
4521
4659
|
function buildResultCacheKeyOptions(options = {}) {
|
|
4522
4660
|
const keyOptions = {};
|
|
@@ -4637,7 +4775,7 @@ function buildFindCacheKey(collection, defaults = {}, query, options) {
|
|
|
4637
4775
|
...options ?? {}
|
|
4638
4776
|
});
|
|
4639
4777
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4640
|
-
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4778
|
+
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4641
4779
|
}
|
|
4642
4780
|
function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
4643
4781
|
const executeOptions = normalizeFindProjectionOptions({
|
|
@@ -4645,7 +4783,7 @@ function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
|
4645
4783
|
...options ?? {}
|
|
4646
4784
|
});
|
|
4647
4785
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4648
|
-
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4786
|
+
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4649
4787
|
}
|
|
4650
4788
|
function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
4651
4789
|
const merged = {
|
|
@@ -4655,7 +4793,7 @@ function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
|
4655
4793
|
const { cache: _cache, ...keyOptions } = merged;
|
|
4656
4794
|
void _cache;
|
|
4657
4795
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4658
|
-
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4796
|
+
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4659
4797
|
}
|
|
4660
4798
|
function buildDistinctCacheKey(collection, defaults = {}, field, query, options) {
|
|
4661
4799
|
const rawOptions = options ?? {};
|
|
@@ -4685,7 +4823,7 @@ function buildAggregateCacheKey(collection, defaults = {}, pipeline, options) {
|
|
|
4685
4823
|
...options ?? {}
|
|
4686
4824
|
};
|
|
4687
4825
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4688
|
-
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4826
|
+
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4689
4827
|
}
|
|
4690
4828
|
function buildFindOneByIdCacheKey(collection, defaults = {}, id, options) {
|
|
4691
4829
|
const objectId = parseRequiredObjectId(id);
|
|
@@ -4700,6 +4838,8 @@ function buildFindByIdsCacheKey(collection, defaults = {}, ids, options) {
|
|
|
4700
4838
|
function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
4701
4839
|
const ext = options;
|
|
4702
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;
|
|
4703
4843
|
const payload = {
|
|
4704
4844
|
query: normalizeQueryForCache(options.query, defaults),
|
|
4705
4845
|
sort: normalizeSortShape(options.sort),
|
|
@@ -4721,9 +4861,10 @@ function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
|
4721
4861
|
hint: ext.hint,
|
|
4722
4862
|
collation: ext.collation,
|
|
4723
4863
|
batchSize: ext.batchSize,
|
|
4724
|
-
|
|
4864
|
+
let: effectiveLet,
|
|
4865
|
+
options: nestedOptions
|
|
4725
4866
|
};
|
|
4726
|
-
const keyHash = hashPayload(payload);
|
|
4867
|
+
const keyHash = hashPayload(stableCacheKeyString(payload));
|
|
4727
4868
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4728
4869
|
return { key: `findPage:${namespace}:${keyHash}`, keyHash };
|
|
4729
4870
|
}
|
|
@@ -5070,29 +5211,30 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5070
5211
|
if (rawOptions.upsert === true) {
|
|
5071
5212
|
throw createError(ErrorCodes.INVALID_ARGUMENT, 'versionMode "strict" does not support upsert.');
|
|
5072
5213
|
}
|
|
5073
|
-
const docs = await context.collection.find(filter, {
|
|
5074
|
-
...buildModelVersionLookupOptions(rawOptions, {
|
|
5075
|
-
_id: 1,
|
|
5076
|
-
[context.versionConfig.field]: 1
|
|
5077
|
-
})
|
|
5078
|
-
});
|
|
5079
5214
|
let matchedCount = 0;
|
|
5080
5215
|
let modifiedCount = 0;
|
|
5081
5216
|
const conflictedIds = [];
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
const
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
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;
|
|
5093
5237
|
}
|
|
5094
|
-
matchedCount += result.matchedCount ?? 0;
|
|
5095
|
-
modifiedCount += result.modifiedCount ?? 0;
|
|
5096
5238
|
}
|
|
5097
5239
|
return {
|
|
5098
5240
|
acknowledged: true,
|
|
@@ -5101,7 +5243,9 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5101
5243
|
upsertedCount: 0,
|
|
5102
5244
|
upsertedId: null,
|
|
5103
5245
|
conflictCount: conflictedIds.length,
|
|
5104
|
-
conflictedIds
|
|
5246
|
+
conflictedIds,
|
|
5247
|
+
skippedCount: skippedIds.length,
|
|
5248
|
+
skippedIds
|
|
5105
5249
|
};
|
|
5106
5250
|
}
|
|
5107
5251
|
async function runStrictUpdateBatch(context, filter, update, options) {
|
|
@@ -5124,7 +5268,7 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5124
5268
|
retryAttempts: _retryAttempts,
|
|
5125
5269
|
retryDelay: _retryDelay,
|
|
5126
5270
|
onRetry: _onRetry,
|
|
5127
|
-
sort
|
|
5271
|
+
sort: _sort,
|
|
5128
5272
|
...driverOptions
|
|
5129
5273
|
} = rawOptions;
|
|
5130
5274
|
void _batchSize;
|
|
@@ -5133,38 +5277,42 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5133
5277
|
void _retryAttempts;
|
|
5134
5278
|
void _retryDelay;
|
|
5135
5279
|
void _onRetry;
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
}),
|
|
5141
|
-
sort
|
|
5142
|
-
});
|
|
5280
|
+
void _sort;
|
|
5281
|
+
const countOptions = buildModelVersionLookupOptions(driverOptions, {});
|
|
5282
|
+
delete countOptions.projection;
|
|
5283
|
+
const totalCount = await context.collection.count(filter, countOptions);
|
|
5143
5284
|
const result = {
|
|
5144
5285
|
acknowledged: true,
|
|
5145
|
-
totalCount
|
|
5286
|
+
totalCount,
|
|
5146
5287
|
matchedCount: 0,
|
|
5147
5288
|
modifiedCount: 0,
|
|
5148
5289
|
upsertedCount: 0,
|
|
5149
|
-
batchCount:
|
|
5290
|
+
batchCount: 0,
|
|
5150
5291
|
errors: [],
|
|
5151
5292
|
retries: [],
|
|
5152
5293
|
conflictCount: 0,
|
|
5153
|
-
conflictedIds: []
|
|
5294
|
+
conflictedIds: [],
|
|
5295
|
+
skippedCount: 0,
|
|
5296
|
+
skippedIds: []
|
|
5154
5297
|
};
|
|
5155
|
-
for (
|
|
5156
|
-
|
|
5298
|
+
for await (const batch of iterateModelCandidateBatches(context, filter, driverOptions, batchSize)) {
|
|
5299
|
+
result.batchCount++;
|
|
5157
5300
|
for (const doc of batch) {
|
|
5158
5301
|
const id = doc._id;
|
|
5159
5302
|
const expectedVersion = assertNumericExpectedVersion(doc[context.versionConfig.field], "updateBatch");
|
|
5160
5303
|
const updateResult = await context.collection.updateOne(
|
|
5161
|
-
|
|
5304
|
+
buildStrictCandidateFilter(filter, id, context.versionConfig.field, expectedVersion),
|
|
5162
5305
|
update,
|
|
5163
5306
|
driverOptions
|
|
5164
5307
|
);
|
|
5165
5308
|
if ((updateResult?.matchedCount ?? 0) === 0) {
|
|
5166
|
-
|
|
5167
|
-
|
|
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
|
+
}
|
|
5168
5316
|
continue;
|
|
5169
5317
|
}
|
|
5170
5318
|
result.matchedCount += updateResult.matchedCount ?? 0;
|
|
@@ -5172,15 +5320,16 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5172
5320
|
}
|
|
5173
5321
|
if (typeof onProgress === "function") {
|
|
5174
5322
|
onProgress({
|
|
5175
|
-
currentBatch:
|
|
5176
|
-
totalBatches:
|
|
5323
|
+
currentBatch: result.batchCount,
|
|
5324
|
+
totalBatches: Math.ceil(totalCount / batchSize),
|
|
5177
5325
|
modified: result.modifiedCount,
|
|
5178
5326
|
matched: result.matchedCount,
|
|
5179
|
-
total:
|
|
5180
|
-
percentage:
|
|
5327
|
+
total: totalCount,
|
|
5328
|
+
percentage: totalCount > 0 ? Math.round(result.modifiedCount / totalCount * 100) : null,
|
|
5181
5329
|
errors: result.errors.length,
|
|
5182
5330
|
retries: result.retries.length,
|
|
5183
|
-
conflicts: result.conflictCount ?? 0
|
|
5331
|
+
conflicts: result.conflictCount ?? 0,
|
|
5332
|
+
skipped: result.skippedCount ?? 0
|
|
5184
5333
|
});
|
|
5185
5334
|
}
|
|
5186
5335
|
}
|
|
@@ -5193,10 +5342,7 @@ function buildSoftDeletePatch(config, now) {
|
|
|
5193
5342
|
return { [config.field]: config.type === "boolean" ? true : now };
|
|
5194
5343
|
}
|
|
5195
5344
|
function buildSoftDeleteFilter(filter, config) {
|
|
5196
|
-
return {
|
|
5197
|
-
...filter ?? {},
|
|
5198
|
-
...config?.enabled ? { [config.field]: null } : {}
|
|
5199
|
-
};
|
|
5345
|
+
return applyModelSoftDeleteFilter(filter, {}, config);
|
|
5200
5346
|
}
|
|
5201
5347
|
function applyModelIncrementVersion(field, increment, versionConfig) {
|
|
5202
5348
|
if (!versionConfig?.enabled || field === void 0) {
|
|
@@ -5236,7 +5382,8 @@ async function orchestrateModelInsertOne(context, document, options) {
|
|
|
5236
5382
|
payload = validateModelSchemaPayload({
|
|
5237
5383
|
validateEnabled: context.validateEnabled,
|
|
5238
5384
|
schemaCache: context.schemaCache,
|
|
5239
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5385
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5386
|
+
schemaError: context.schemaError
|
|
5240
5387
|
}, payload, options);
|
|
5241
5388
|
payload = applyModelInsertTimestamps(payload, context.timestampsConfig, () => context.nowDate());
|
|
5242
5389
|
payload = applyModelInsertVersion(payload, context.versionConfig);
|
|
@@ -5270,7 +5417,8 @@ async function orchestrateModelInsertMany(context, documents, options) {
|
|
|
5270
5417
|
doc = validateModelSchemaPayload({
|
|
5271
5418
|
validateEnabled: context.validateEnabled,
|
|
5272
5419
|
schemaCache: context.schemaCache,
|
|
5273
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5420
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5421
|
+
schemaError: context.schemaError
|
|
5274
5422
|
}, doc, resolvedOptions, { index });
|
|
5275
5423
|
doc = applyModelInsertTimestamps(doc, context.timestampsConfig, () => context.nowDate());
|
|
5276
5424
|
doc = applyModelInsertVersion(doc, context.versionConfig);
|
|
@@ -5355,7 +5503,8 @@ async function orchestrateModelReplaceOne(context, filter, replacement, options)
|
|
|
5355
5503
|
let nextReplacement = validateModelSchemaPayload({
|
|
5356
5504
|
validateEnabled: context.validateEnabled,
|
|
5357
5505
|
schemaCache: context.schemaCache,
|
|
5358
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5506
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5507
|
+
schemaError: context.schemaError
|
|
5359
5508
|
}, replacement, options);
|
|
5360
5509
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5361
5510
|
replacement,
|
|
@@ -5414,7 +5563,8 @@ async function orchestrateModelFindOneAndReplace(context, filter, replacement, o
|
|
|
5414
5563
|
let nextReplacement = validateModelSchemaPayload({
|
|
5415
5564
|
validateEnabled: context.validateEnabled,
|
|
5416
5565
|
schemaCache: context.schemaCache,
|
|
5417
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5566
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5567
|
+
schemaError: context.schemaError
|
|
5418
5568
|
}, replacement, options);
|
|
5419
5569
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5420
5570
|
replacement,
|
|
@@ -5526,7 +5676,8 @@ async function orchestrateModelInsertBatch(context, docs, options) {
|
|
|
5526
5676
|
record = validateModelSchemaPayload({
|
|
5527
5677
|
validateEnabled: context.validateEnabled,
|
|
5528
5678
|
schemaCache: context.schemaCache,
|
|
5529
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5679
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5680
|
+
schemaError: context.schemaError
|
|
5530
5681
|
}, record, resolvedOptions, { index });
|
|
5531
5682
|
record = applyModelInsertTimestamps(record, context.timestampsConfig, () => context.nowDate());
|
|
5532
5683
|
record = applyModelInsertVersion(record, context.versionConfig);
|
|
@@ -5604,7 +5755,7 @@ async function orchestrateModelDeleteOne(context, filter, options) {
|
|
|
5604
5755
|
let result;
|
|
5605
5756
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5606
5757
|
result = await context.collection.updateOne(
|
|
5607
|
-
|
|
5758
|
+
buildSoftDeleteFilter(filter, softDeleteConfig),
|
|
5608
5759
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5609
5760
|
options
|
|
5610
5761
|
);
|
|
@@ -5638,7 +5789,7 @@ async function orchestrateModelDeleteMany(context, filter, options) {
|
|
|
5638
5789
|
let result;
|
|
5639
5790
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5640
5791
|
result = await context.collection.updateMany(
|
|
5641
|
-
{
|
|
5792
|
+
{ $and: [filter ?? {}, buildModelVisibleSoftDeleteCondition(softDeleteConfig)] },
|
|
5642
5793
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5643
5794
|
options
|
|
5644
5795
|
);
|
|
@@ -5802,7 +5953,7 @@ async function inspectDeclaredRelationUsage(context, targets, inputOptions, targ
|
|
|
5802
5953
|
[inbound.localField]: { $in: targetValues }
|
|
5803
5954
|
};
|
|
5804
5955
|
const sourceSoftDelete = resolveModelSoftDeleteConfig(inbound.definition);
|
|
5805
|
-
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery,
|
|
5956
|
+
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery, buildModelVisibleSoftDeleteCondition(sourceSoftDelete)] } : baseQuery;
|
|
5806
5957
|
try {
|
|
5807
5958
|
const collection = context.runtime.scopedCollection(inbound.sourceCollection, scope);
|
|
5808
5959
|
if (!collection) {
|
|
@@ -5984,7 +6135,7 @@ async function executeProtectedRelationDelete(host, filter, options, force) {
|
|
|
5984
6135
|
const deleteOptions = stripRelationUsageOptions(options);
|
|
5985
6136
|
let target;
|
|
5986
6137
|
try {
|
|
5987
|
-
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter :
|
|
6138
|
+
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter : applyModelSoftDeleteFilter(filter, void 0, host.softDeleteConfig);
|
|
5988
6139
|
const rawTarget = await host.collection.findOne(
|
|
5989
6140
|
targetFilter,
|
|
5990
6141
|
relationTargetReadOptions(options, buildRelationUsageTargetProjection(host.modelName))
|
|
@@ -6068,7 +6219,7 @@ var ModelInstance = class {
|
|
|
6068
6219
|
this.softDeleteConfig = this._softDeleteConfig;
|
|
6069
6220
|
this._versionConfig = resolveModelVersionConfig(options.definition);
|
|
6070
6221
|
this._v1HooksFactory = resolveModelHooksFactory(options.definition);
|
|
6071
|
-
scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6222
|
+
if (!this._schemaError) scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6072
6223
|
runtime: this.runtime,
|
|
6073
6224
|
dbName: options.dbName,
|
|
6074
6225
|
poolName: options.poolName,
|
|
@@ -6128,46 +6279,6 @@ var ModelInstance = class {
|
|
|
6128
6279
|
}
|
|
6129
6280
|
return result;
|
|
6130
6281
|
}
|
|
6131
|
-
isVisibleBySoftDelete(doc, options) {
|
|
6132
|
-
if (!doc || !this._softDeleteConfig?.enabled) {
|
|
6133
|
-
return Boolean(doc);
|
|
6134
|
-
}
|
|
6135
|
-
const rawOptions = options ?? {};
|
|
6136
|
-
if (rawOptions.withDeleted) {
|
|
6137
|
-
return true;
|
|
6138
|
-
}
|
|
6139
|
-
const value = doc[this._softDeleteConfig.field];
|
|
6140
|
-
if (rawOptions.onlyDeleted) {
|
|
6141
|
-
return value !== void 0 && value !== null;
|
|
6142
|
-
}
|
|
6143
|
-
return value === void 0 || value === null;
|
|
6144
|
-
}
|
|
6145
|
-
filterVisibleBySoftDelete(docs, options) {
|
|
6146
|
-
return docs.filter((doc) => this.isVisibleBySoftDelete(doc, options));
|
|
6147
|
-
}
|
|
6148
|
-
applySoftDeleteFindPageOptions(options) {
|
|
6149
|
-
const rawOptions = options ?? {};
|
|
6150
|
-
return {
|
|
6151
|
-
...rawOptions,
|
|
6152
|
-
query: applyModelSoftDeleteFilter(rawOptions.query, rawOptions, this._softDeleteConfig)
|
|
6153
|
-
};
|
|
6154
|
-
}
|
|
6155
|
-
applySoftDeleteAggregatePipeline(pipeline, options) {
|
|
6156
|
-
if (!this._softDeleteConfig?.enabled) {
|
|
6157
|
-
return pipeline ?? [];
|
|
6158
|
-
}
|
|
6159
|
-
const rawOptions = options ?? {};
|
|
6160
|
-
if (rawOptions.withDeleted) {
|
|
6161
|
-
return pipeline ?? [];
|
|
6162
|
-
}
|
|
6163
|
-
const softDeleteMatch = applyModelSoftDeleteFilter({}, rawOptions, this._softDeleteConfig);
|
|
6164
|
-
const matchStage = { $match: softDeleteMatch };
|
|
6165
|
-
const stages = [...pipeline ?? []];
|
|
6166
|
-
if (stages.length > 0 && stages[0] && typeof stages[0] === "object" && "$geoNear" in stages[0]) {
|
|
6167
|
-
return [stages[0], matchStage, ...stages.slice(1)];
|
|
6168
|
-
}
|
|
6169
|
-
return [matchStage, ...stages];
|
|
6170
|
-
}
|
|
6171
6282
|
// ── public API ────────────────────────────────────────────────────────────────
|
|
6172
6283
|
find(query, options) {
|
|
6173
6284
|
return new PopulatePromise(async (paths) => {
|
|
@@ -6203,8 +6314,12 @@ var ModelInstance = class {
|
|
|
6203
6314
|
}
|
|
6204
6315
|
findOneById(id, options) {
|
|
6205
6316
|
return new PopulatePromise(async (paths) => {
|
|
6206
|
-
const
|
|
6207
|
-
|
|
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);
|
|
6208
6323
|
});
|
|
6209
6324
|
}
|
|
6210
6325
|
findById(id, options) {
|
|
@@ -6212,13 +6327,25 @@ var ModelInstance = class {
|
|
|
6212
6327
|
}
|
|
6213
6328
|
findByIds(ids, options) {
|
|
6214
6329
|
return new PopulatePromise(async (paths) => {
|
|
6215
|
-
|
|
6216
|
-
|
|
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);
|
|
6217
6344
|
});
|
|
6218
6345
|
}
|
|
6219
6346
|
findPage(options) {
|
|
6220
6347
|
return new PopulatePromise(async (paths) => {
|
|
6221
|
-
const filteredOptions =
|
|
6348
|
+
const filteredOptions = applySoftDeleteFindPageOptions(options, this._softDeleteConfig);
|
|
6222
6349
|
const result = this._v1HooksFactory ? await this.runV1HookedOperation("find", [filteredOptions], (nextOptions) => this.collection.findPage(nextOptions)) : await this.collection.findPage(filteredOptions);
|
|
6223
6350
|
return {
|
|
6224
6351
|
...result,
|
|
@@ -6370,7 +6497,7 @@ var ModelInstance = class {
|
|
|
6370
6497
|
return this.runV1HookedOperation("find", [key, filteredQuery, options], (nextKey, nextQuery, nextOptions) => this.collection.distinct(nextKey, nextQuery, nextOptions));
|
|
6371
6498
|
}
|
|
6372
6499
|
aggregate(pipeline, options) {
|
|
6373
|
-
const filteredPipeline =
|
|
6500
|
+
const filteredPipeline = applySoftDeleteAggregatePipeline(pipeline, options, this._softDeleteConfig);
|
|
6374
6501
|
return this.runV1HookedOperation("find", [filteredPipeline, options], (nextPipeline, nextOptions) => {
|
|
6375
6502
|
const execute = () => this.collection.aggregate(nextPipeline, nextOptions);
|
|
6376
6503
|
if (resolveAggregateWriteTarget(Array.isArray(nextPipeline) ? nextPipeline : [])) {
|
|
@@ -6499,13 +6626,14 @@ var ModelInstance = class {
|
|
|
6499
6626
|
schemaValidationContext: {
|
|
6500
6627
|
validateEnabled: this._validateEnabled,
|
|
6501
6628
|
schemaCache: this._schemaCache,
|
|
6502
|
-
schemaValidateFn: this._schemaValidateFn
|
|
6629
|
+
schemaValidateFn: this._schemaValidateFn,
|
|
6630
|
+
schemaError: this._schemaError
|
|
6503
6631
|
},
|
|
6504
6632
|
nowFactory: () => this.nowDate()
|
|
6505
6633
|
}));
|
|
6506
6634
|
}
|
|
6507
6635
|
async removeDocument(document) {
|
|
6508
|
-
return this.runModelWrite(() => removeModelDocument(this.collection, document));
|
|
6636
|
+
return this.runModelWrite(() => removeModelDocument(this.collection, document, (filter) => this.deleteOne(filter)));
|
|
6509
6637
|
}
|
|
6510
6638
|
applyDefaults(document) {
|
|
6511
6639
|
return applyModelDefaults(this.definition, document);
|
|
@@ -6523,6 +6651,7 @@ var ModelInstance = class {
|
|
|
6523
6651
|
validateEnabled: this._validateEnabled,
|
|
6524
6652
|
schemaCache: this._schemaCache,
|
|
6525
6653
|
schemaValidateFn: this._schemaValidateFn,
|
|
6654
|
+
schemaError: this._schemaError,
|
|
6526
6655
|
hooksFactory: this._v1HooksFactory,
|
|
6527
6656
|
runHook: (hookName, context) => this.runHook(hookName, context)
|
|
6528
6657
|
};
|
|
@@ -6538,6 +6667,11 @@ var ModelInstance = class {
|
|
|
6538
6667
|
|
|
6539
6668
|
// src/capabilities/transaction/index.ts
|
|
6540
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
|
+
}
|
|
6541
6675
|
function toPublicTransactionStatus(state) {
|
|
6542
6676
|
return state === "active" ? "started" : state;
|
|
6543
6677
|
}
|
|
@@ -6638,6 +6772,7 @@ var Transaction = class {
|
|
|
6638
6772
|
this.options = options;
|
|
6639
6773
|
this.id = `tx_${(0, import_node_crypto3.randomBytes)(8).toString("hex")}`;
|
|
6640
6774
|
this.state = "pending";
|
|
6775
|
+
this.acceptingWrites = false;
|
|
6641
6776
|
this.startedAt = null;
|
|
6642
6777
|
this.timeoutTimer = null;
|
|
6643
6778
|
this.pendingInvalidations = /* @__PURE__ */ new Set();
|
|
@@ -6656,6 +6791,7 @@ var Transaction = class {
|
|
|
6656
6791
|
}
|
|
6657
6792
|
this.session.startTransaction(this.options.transactionOptions);
|
|
6658
6793
|
this.state = "active";
|
|
6794
|
+
this.acceptingWrites = true;
|
|
6659
6795
|
this.startedAt = Date.now();
|
|
6660
6796
|
const timeout = this.options.timeout ?? 3e4;
|
|
6661
6797
|
if (timeout > 0) {
|
|
@@ -6678,6 +6814,7 @@ var Transaction = class {
|
|
|
6678
6814
|
if (this.state !== "active") {
|
|
6679
6815
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
6680
6816
|
}
|
|
6817
|
+
this.acceptingWrites = false;
|
|
6681
6818
|
if (typeof this.session.commitTransaction === "function") {
|
|
6682
6819
|
await commitTransactionWithRetry(this.session, this.options.logger);
|
|
6683
6820
|
}
|
|
@@ -6703,6 +6840,7 @@ var Transaction = class {
|
|
|
6703
6840
|
* @since v1.4.0
|
|
6704
6841
|
*/
|
|
6705
6842
|
async abort() {
|
|
6843
|
+
this.acceptingWrites = false;
|
|
6706
6844
|
if (this.state !== "pending" && this.state !== "active") {
|
|
6707
6845
|
return;
|
|
6708
6846
|
}
|
|
@@ -6727,10 +6865,16 @@ var Transaction = class {
|
|
|
6727
6865
|
* @since v1.4.0
|
|
6728
6866
|
*/
|
|
6729
6867
|
async end() {
|
|
6868
|
+
this.acceptingWrites = false;
|
|
6730
6869
|
this.clearTimeout();
|
|
6731
6870
|
this.options.lockManager?.releaseLocks(this.id);
|
|
6732
6871
|
await this.session.endSession();
|
|
6733
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
|
+
}
|
|
6734
6878
|
/**
|
|
6735
6879
|
* Record a cache invalidation intent.
|
|
6736
6880
|
* @since v1.4.0
|
|
@@ -6904,12 +7048,12 @@ var TransactionManager = class {
|
|
|
6904
7048
|
return result;
|
|
6905
7049
|
} catch (error) {
|
|
6906
7050
|
lastError = error;
|
|
6907
|
-
await transaction.abort();
|
|
6908
|
-
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
6909
7051
|
if (transaction.state === "committed") {
|
|
6910
7052
|
throw error;
|
|
6911
7053
|
}
|
|
6912
|
-
|
|
7054
|
+
await transaction.abort();
|
|
7055
|
+
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
7056
|
+
if (!enableRetry || attempt === maxRetries || isUnknownTransactionCommitResult(error) || !isTransientTransactionError(error)) {
|
|
6913
7057
|
throw error;
|
|
6914
7058
|
}
|
|
6915
7059
|
await sleep(retryDelay * Math.pow(retryBackoff, attempt));
|
|
@@ -7010,22 +7154,34 @@ function stringifySessionId(id) {
|
|
|
7010
7154
|
}
|
|
7011
7155
|
return String(id);
|
|
7012
7156
|
}
|
|
7013
|
-
function
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
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
|
+
}
|
|
7020
7176
|
}
|
|
7021
|
-
return
|
|
7177
|
+
return false;
|
|
7022
7178
|
}
|
|
7023
7179
|
function isUnknownTransactionCommitResult(error) {
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
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");
|
|
7029
7185
|
}
|
|
7030
7186
|
async function commitTransactionWithRetry(session, logger) {
|
|
7031
7187
|
const maxAttempts = 3;
|
|
@@ -7541,26 +7697,27 @@ var CountQueue = class {
|
|
|
7541
7697
|
* @returns The result of fn
|
|
7542
7698
|
* @throws A controlled error when the queue is full or the wait times out
|
|
7543
7699
|
*/
|
|
7544
|
-
async execute(fn) {
|
|
7700
|
+
async execute(fn, options = {}) {
|
|
7545
7701
|
const startTime = Date.now();
|
|
7702
|
+
const signal = options.signal;
|
|
7703
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7546
7704
|
if (this.running >= this.concurrency) {
|
|
7547
7705
|
if (this.queue.length >= this.maxQueueSize) {
|
|
7548
7706
|
this.stats.rejected += 1;
|
|
7549
7707
|
throw createError(ErrorCodes.INVALID_OPERATION, `Count queue is full (${this.maxQueueSize})`);
|
|
7550
7708
|
}
|
|
7551
7709
|
this.stats.queued += 1;
|
|
7552
|
-
await this._waitInQueue(startTime);
|
|
7710
|
+
await this._waitInQueue(startTime, signal);
|
|
7553
7711
|
}
|
|
7712
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7554
7713
|
this.running += 1;
|
|
7555
7714
|
this.stats.executed += 1;
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
return await this._executeWithTimeout(fn, remainingMs);
|
|
7560
|
-
} finally {
|
|
7715
|
+
const elapsed = Date.now() - startTime;
|
|
7716
|
+
const remainingMs = Math.max(1, this.timeout - elapsed);
|
|
7717
|
+
return this._executeWithTimeout(fn, remainingMs, signal, () => {
|
|
7561
7718
|
this.running -= 1;
|
|
7562
7719
|
this._wakeNext();
|
|
7563
|
-
}
|
|
7720
|
+
});
|
|
7564
7721
|
}
|
|
7565
7722
|
/**
|
|
7566
7723
|
* Get queue statistics (including live state).
|
|
@@ -7595,17 +7752,28 @@ var CountQueue = class {
|
|
|
7595
7752
|
const entry = this.queue.shift();
|
|
7596
7753
|
if (entry) {
|
|
7597
7754
|
clearTimeout(entry.timer);
|
|
7755
|
+
entry.cleanup();
|
|
7598
7756
|
entry.reject(createError(ErrorCodes.INVALID_OPERATION, "Count queue was cleared before execution"));
|
|
7599
7757
|
}
|
|
7600
7758
|
}
|
|
7601
7759
|
}
|
|
7602
|
-
_waitInQueue(startTime) {
|
|
7760
|
+
_waitInQueue(startTime, signal) {
|
|
7603
7761
|
return new Promise((resolve, reject) => {
|
|
7604
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
|
+
};
|
|
7605
7772
|
const timer = setTimeout(() => {
|
|
7606
7773
|
const index = this.queue.indexOf(entry);
|
|
7607
7774
|
if (index !== -1) {
|
|
7608
7775
|
this.queue.splice(index, 1);
|
|
7776
|
+
entry.cleanup();
|
|
7609
7777
|
this.stats.timeout += 1;
|
|
7610
7778
|
reject(createError(ErrorCodes.OPERATION_TIMEOUT, `Count queue wait timeout (${this.timeout}ms)`));
|
|
7611
7779
|
}
|
|
@@ -7618,9 +7786,12 @@ var CountQueue = class {
|
|
|
7618
7786
|
},
|
|
7619
7787
|
reject,
|
|
7620
7788
|
timer,
|
|
7621
|
-
startTime
|
|
7789
|
+
startTime,
|
|
7790
|
+
cleanup: () => signal?.removeEventListener("abort", onAbort)
|
|
7622
7791
|
};
|
|
7623
7792
|
this.queue.push(entry);
|
|
7793
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7794
|
+
if (signal?.aborted) onAbort();
|
|
7624
7795
|
});
|
|
7625
7796
|
}
|
|
7626
7797
|
_wakeNext() {
|
|
@@ -7628,6 +7799,7 @@ var CountQueue = class {
|
|
|
7628
7799
|
const entry = this.queue.shift();
|
|
7629
7800
|
if (entry) {
|
|
7630
7801
|
clearTimeout(entry.timer);
|
|
7802
|
+
entry.cleanup();
|
|
7631
7803
|
entry.resolve();
|
|
7632
7804
|
}
|
|
7633
7805
|
}
|
|
@@ -7644,10 +7816,12 @@ var CountQueue = class {
|
|
|
7644
7816
|
}
|
|
7645
7817
|
return value;
|
|
7646
7818
|
}
|
|
7647
|
-
_executeWithTimeout(fn, timeoutMs
|
|
7819
|
+
_executeWithTimeout(fn, timeoutMs, signal, release) {
|
|
7648
7820
|
let timer = null;
|
|
7649
7821
|
const controller = new AbortController();
|
|
7650
|
-
|
|
7822
|
+
let rejectCancellation;
|
|
7823
|
+
const cancellation = new Promise((_, reject) => {
|
|
7824
|
+
rejectCancellation = reject;
|
|
7651
7825
|
timer = setTimeout(
|
|
7652
7826
|
() => {
|
|
7653
7827
|
controller.abort();
|
|
@@ -7657,14 +7831,22 @@ var CountQueue = class {
|
|
|
7657
7831
|
timeoutMs
|
|
7658
7832
|
);
|
|
7659
7833
|
});
|
|
7834
|
+
const onAbort = () => {
|
|
7835
|
+
controller.abort();
|
|
7836
|
+
rejectCancellation(createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted"));
|
|
7837
|
+
};
|
|
7838
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7660
7839
|
let taskPromise;
|
|
7661
7840
|
try {
|
|
7662
7841
|
taskPromise = Promise.resolve(fn(controller.signal));
|
|
7663
7842
|
} catch (error) {
|
|
7664
7843
|
taskPromise = Promise.reject(error);
|
|
7665
7844
|
}
|
|
7666
|
-
|
|
7845
|
+
void taskPromise.then(release, release);
|
|
7846
|
+
if (signal?.aborted) onAbort();
|
|
7847
|
+
return Promise.race([taskPromise, cancellation]).finally(() => {
|
|
7667
7848
|
if (timer !== null) clearTimeout(timer);
|
|
7849
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7668
7850
|
});
|
|
7669
7851
|
}
|
|
7670
7852
|
_updateWaitTimeStats(waitTime) {
|
|
@@ -7677,6 +7859,7 @@ var CountQueue = class {
|
|
|
7677
7859
|
};
|
|
7678
7860
|
|
|
7679
7861
|
// src/capabilities/pool/pool-health-checker.ts
|
|
7862
|
+
var SILENT_HEALTH_CHECKER_LOGGER = {};
|
|
7680
7863
|
var HealthChecker = class {
|
|
7681
7864
|
constructor(options = {}) {
|
|
7682
7865
|
this._healthStatus = /* @__PURE__ */ new Map();
|
|
@@ -7686,7 +7869,7 @@ var HealthChecker = class {
|
|
|
7686
7869
|
this._inProgress = /* @__PURE__ */ new Set();
|
|
7687
7870
|
this._started = false;
|
|
7688
7871
|
this._poolManager = options.poolManager ?? null;
|
|
7689
|
-
this._logger = options.logger ??
|
|
7872
|
+
this._logger = options.logger ?? SILENT_HEALTH_CHECKER_LOGGER;
|
|
7690
7873
|
}
|
|
7691
7874
|
register(poolNameOrConfig, configOrClient) {
|
|
7692
7875
|
let poolName;
|
|
@@ -7810,6 +7993,7 @@ var HealthChecker = class {
|
|
|
7810
7993
|
};
|
|
7811
7994
|
|
|
7812
7995
|
// src/capabilities/pool/pool-selector.ts
|
|
7996
|
+
var SILENT_POOL_SELECTOR_LOGGER = {};
|
|
7813
7997
|
var POOL_STRATEGY_ALIASES = {
|
|
7814
7998
|
"round-robin": "roundRobin",
|
|
7815
7999
|
"least-connections": "leastConnections",
|
|
@@ -7823,7 +8007,7 @@ var PoolSelector = class {
|
|
|
7823
8007
|
constructor(options = {}) {
|
|
7824
8008
|
this._roundRobinIndex = /* @__PURE__ */ new Map();
|
|
7825
8009
|
this._strategy = normalizePoolStrategy(options.strategy);
|
|
7826
|
-
this._logger = options.logger ??
|
|
8010
|
+
this._logger = options.logger ?? SILENT_POOL_SELECTOR_LOGGER;
|
|
7827
8011
|
}
|
|
7828
8012
|
select(pools, context) {
|
|
7829
8013
|
if (!pools || pools.length === 0) {
|
|
@@ -7954,11 +8138,12 @@ var PoolSelector = class {
|
|
|
7954
8138
|
};
|
|
7955
8139
|
|
|
7956
8140
|
// src/capabilities/pool/pool-stats-manager.ts
|
|
8141
|
+
var SILENT_POOL_STATS_LOGGER = {};
|
|
7957
8142
|
var PoolStatsManager = class {
|
|
7958
8143
|
constructor(options = {}) {
|
|
7959
8144
|
this._stats = /* @__PURE__ */ new Map();
|
|
7960
8145
|
this._buffer = [];
|
|
7961
|
-
this._logger = options.logger ??
|
|
8146
|
+
this._logger = options.logger ?? SILENT_POOL_STATS_LOGGER;
|
|
7962
8147
|
this._batchInterval = setInterval(() => {
|
|
7963
8148
|
this._flush();
|
|
7964
8149
|
}, 100);
|
|
@@ -8190,6 +8375,9 @@ var ConnectionPoolManager = class {
|
|
|
8190
8375
|
this._closed = false;
|
|
8191
8376
|
this._configs = /* @__PURE__ */ new Map();
|
|
8192
8377
|
this._pendingAdds = /* @__PURE__ */ new Set();
|
|
8378
|
+
this._pendingAddTasks = /* @__PURE__ */ new Set();
|
|
8379
|
+
this._generation = 0;
|
|
8380
|
+
this._closePromise = null;
|
|
8193
8381
|
this.logger = options.logger ?? null;
|
|
8194
8382
|
this.maxPoolsCount = options.maxPoolsCount ?? 10;
|
|
8195
8383
|
this.strategy = options.poolStrategy ?? "auto";
|
|
@@ -8206,9 +8394,9 @@ var ConnectionPoolManager = class {
|
|
|
8206
8394
|
this._fallbackConfig = this.fallback;
|
|
8207
8395
|
this._fallback = this.fallback;
|
|
8208
8396
|
this._pools = this.pools;
|
|
8209
|
-
this._selector = new PoolSelector({ strategy: this.strategy });
|
|
8210
|
-
this._healthChecker = new HealthChecker({ poolManager: this });
|
|
8211
|
-
this._stats = new PoolStatsManager({ logger:
|
|
8397
|
+
this._selector = new PoolSelector({ strategy: this.strategy, logger: this.logger ?? void 0 });
|
|
8398
|
+
this._healthChecker = new HealthChecker({ poolManager: this, logger: this.logger ?? void 0 });
|
|
8399
|
+
this._stats = new PoolStatsManager({ logger: this.logger ?? void 0 });
|
|
8212
8400
|
}
|
|
8213
8401
|
/**
|
|
8214
8402
|
* Add a connection pool.
|
|
@@ -8216,35 +8404,21 @@ var ConnectionPoolManager = class {
|
|
|
8216
8404
|
*/
|
|
8217
8405
|
async addPool(config) {
|
|
8218
8406
|
validatePoolConfig(config);
|
|
8407
|
+
if (this._closed) {
|
|
8408
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Connection pool manager is closed");
|
|
8409
|
+
}
|
|
8219
8410
|
if (this.pools.has(config.name) || this._pendingAdds.has(config.name)) {
|
|
8220
8411
|
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8221
8412
|
}
|
|
8222
|
-
if (this.maxPoolsCount > 0 && this.pools.size >= this.maxPoolsCount) {
|
|
8413
|
+
if (this.maxPoolsCount > 0 && this.pools.size + this._pendingAdds.size >= this.maxPoolsCount) {
|
|
8223
8414
|
throw createError(ErrorCodes.INVALID_CONFIG, `Maximum pool count (${this.maxPoolsCount}) reached`);
|
|
8224
8415
|
}
|
|
8225
8416
|
this._pendingAdds.add(config.name);
|
|
8417
|
+
const generation = this._generation;
|
|
8418
|
+
const task = this._finishAddPool(config, generation);
|
|
8419
|
+
this._pendingAddTasks.add(task);
|
|
8226
8420
|
try {
|
|
8227
|
-
|
|
8228
|
-
if (this.pools.has(config.name)) {
|
|
8229
|
-
await client.close().catch(() => {
|
|
8230
|
-
});
|
|
8231
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8232
|
-
}
|
|
8233
|
-
this.pools.set(config.name, {
|
|
8234
|
-
client,
|
|
8235
|
-
config,
|
|
8236
|
-
createdAt: Date.now()
|
|
8237
|
-
});
|
|
8238
|
-
this.healthStatus.set(config.name, {
|
|
8239
|
-
status: "up",
|
|
8240
|
-
consecutiveFailures: 0,
|
|
8241
|
-
lastCheckTime: null,
|
|
8242
|
-
lastError: null,
|
|
8243
|
-
uptime: 0
|
|
8244
|
-
});
|
|
8245
|
-
this.stats.set(config.name, createEmptyPoolStats(config.name));
|
|
8246
|
-
this._configs.set(config.name, config);
|
|
8247
|
-
this._healthChecker.register(config.name, config.healthCheck ?? {});
|
|
8421
|
+
await task;
|
|
8248
8422
|
} catch (err) {
|
|
8249
8423
|
const error = err;
|
|
8250
8424
|
const msg = error.message ?? "";
|
|
@@ -8258,8 +8432,31 @@ var ConnectionPoolManager = class {
|
|
|
8258
8432
|
throw err;
|
|
8259
8433
|
} finally {
|
|
8260
8434
|
this._pendingAdds.delete(config.name);
|
|
8435
|
+
this._pendingAddTasks.delete(task);
|
|
8261
8436
|
}
|
|
8262
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
|
+
);
|
|
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 ?? {});
|
|
8459
|
+
}
|
|
8263
8460
|
/**
|
|
8264
8461
|
* Remove a connection pool.
|
|
8265
8462
|
* @since v1.0.8
|
|
@@ -8415,16 +8612,24 @@ var ConnectionPoolManager = class {
|
|
|
8415
8612
|
* @since v1.0.8
|
|
8416
8613
|
*/
|
|
8417
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() {
|
|
8418
8622
|
this.stopHealthCheck();
|
|
8419
8623
|
this._healthChecker.stop();
|
|
8420
8624
|
this._stats.close();
|
|
8625
|
+
await Promise.allSettled([...this._pendingAddTasks]);
|
|
8421
8626
|
for (const pool of this.pools.values()) {
|
|
8422
8627
|
await pool.client.close();
|
|
8423
8628
|
}
|
|
8424
8629
|
this.pools.clear();
|
|
8425
8630
|
this.healthStatus.clear();
|
|
8426
8631
|
this.stats.clear();
|
|
8427
|
-
this.
|
|
8632
|
+
this._configs.clear();
|
|
8428
8633
|
}
|
|
8429
8634
|
// ─── v1 compat methods ───────────────────────────────────────────────────────────
|
|
8430
8635
|
/** v1 compat: get pool health Map */
|
|
@@ -8612,6 +8817,7 @@ async function initializeDistributedCacheInvalidator(options, cache, logger) {
|
|
|
8612
8817
|
logger
|
|
8613
8818
|
});
|
|
8614
8819
|
} catch (err) {
|
|
8820
|
+
if (err?.code === ErrorCodes.INVALID_CONFIG) throw err;
|
|
8615
8821
|
logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 check Redis config or package installation completeness.", err);
|
|
8616
8822
|
return null;
|
|
8617
8823
|
}
|
|
@@ -8690,6 +8896,33 @@ async function loadModelFiles(options, logger, opts = {}) {
|
|
|
8690
8896
|
}
|
|
8691
8897
|
}
|
|
8692
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
|
+
|
|
8693
8926
|
// src/entry/runtime-defaults.ts
|
|
8694
8927
|
function isPlainObject3(value) {
|
|
8695
8928
|
if (value === null || typeof value !== "object") return false;
|
|
@@ -9340,36 +9573,36 @@ var FUNC_REGEX = /^(CONCAT|UPPER|LOWER|TRIM|SUBSTR|LENGTH|ABS|CEIL|FLOOR|ROUND|S
|
|
|
9340
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;
|
|
9341
9574
|
function compileInnerExpression(expression) {
|
|
9342
9575
|
const expr2 = expression.trim();
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
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
|
+
);
|
|
9350
9595
|
}
|
|
9351
|
-
const orParts = splitTopLevel2(expr2, "||");
|
|
9596
|
+
const orParts = splitTopLevel2(expr2, "||", positions);
|
|
9352
9597
|
if (orParts.length > 1) {
|
|
9353
|
-
return { $or: orParts.map((part) => compileInnerExpression(part
|
|
9598
|
+
return { $or: orParts.map((part) => compileInnerExpression(part)) };
|
|
9354
9599
|
}
|
|
9355
|
-
const
|
|
9356
|
-
if (
|
|
9357
|
-
|
|
9358
|
-
return {
|
|
9359
|
-
$cond: {
|
|
9360
|
-
if: compileInnerExpression(condition.trim()),
|
|
9361
|
-
then: parseThenElse(thenPart.trim()),
|
|
9362
|
-
else: parseThenElse(elsePart.trim())
|
|
9363
|
-
}
|
|
9364
|
-
};
|
|
9365
|
-
}
|
|
9366
|
-
const nullCoalParts = splitTopLevel2(expr2, "??");
|
|
9367
|
-
if (nullCoalParts.length > 1) {
|
|
9368
|
-
return { $ifNull: [parseValue(nullCoalParts[0].trim()), parseValue(nullCoalParts[1].trim())] };
|
|
9600
|
+
const andParts = splitTopLevel2(expr2, "&&", positions);
|
|
9601
|
+
if (andParts.length > 1) {
|
|
9602
|
+
return { $and: andParts.map((part) => compileInnerExpression(part)) };
|
|
9369
9603
|
}
|
|
9370
|
-
const
|
|
9371
|
-
if (
|
|
9372
|
-
const [, left, operator, right] = cmpMatch;
|
|
9604
|
+
const comparison = findBinaryOperator(expr2, positions, ["===", "!==", ">=", "<=", ">", "<"]);
|
|
9605
|
+
if (comparison) {
|
|
9373
9606
|
const operatorMap = {
|
|
9374
9607
|
"===": "$eq",
|
|
9375
9608
|
"!==": "$ne",
|
|
@@ -9378,24 +9611,39 @@ function compileInnerExpression(expression) {
|
|
|
9378
9611
|
">": "$gt",
|
|
9379
9612
|
"<": "$lt"
|
|
9380
9613
|
};
|
|
9381
|
-
return {
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
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
|
+
] };
|
|
9387
9633
|
}
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
const [, left, operator, right] = addSubMatch;
|
|
9391
|
-
const operatorMap = { "+": "$add", "-": "$subtract" };
|
|
9392
|
-
return { [operatorMap[operator]]: [parseOperand(left.trim()), parseOperand(right.trim())] };
|
|
9634
|
+
if (expr2.startsWith("-")) {
|
|
9635
|
+
return { $multiply: [-1, compileInnerExpression(expr2.slice(1))] };
|
|
9393
9636
|
}
|
|
9394
|
-
const
|
|
9395
|
-
if (
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
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
|
+
}
|
|
9399
9647
|
}
|
|
9400
9648
|
const genericFuncCallRe = /^[A-Za-z_][A-Za-z0-9_]*\s*\(.+\)$/;
|
|
9401
9649
|
if (genericFuncCallRe.test(expr2)) {
|
|
@@ -9404,9 +9652,6 @@ function compileInnerExpression(expression) {
|
|
|
9404
9652
|
}
|
|
9405
9653
|
return parseValue(expr2);
|
|
9406
9654
|
}
|
|
9407
|
-
function parseThenElse(source) {
|
|
9408
|
-
return source.includes("?") && source.includes(":") ? compileInnerExpression(source) : parseValue(source);
|
|
9409
|
-
}
|
|
9410
9655
|
function parseValue(value) {
|
|
9411
9656
|
const normalized = stripOuterParentheses(value.trim());
|
|
9412
9657
|
if (normalized.startsWith("'") && normalized.endsWith("'") || normalized.startsWith('"') && normalized.endsWith('"')) {
|
|
@@ -9416,18 +9661,22 @@ function parseValue(value) {
|
|
|
9416
9661
|
if (normalized === "true") return true;
|
|
9417
9662
|
if (normalized === "false") return false;
|
|
9418
9663
|
if (!isNaN(Number(normalized)) && normalized !== "") return Number(normalized);
|
|
9419
|
-
if (IS_FUNC_CALL_RE.test(normalized)) {
|
|
9664
|
+
if (normalized !== value.trim() || IS_FUNC_CALL_RE.test(normalized) || FUNC_REGEX.test(normalized)) {
|
|
9420
9665
|
return compileInnerExpression(normalized);
|
|
9421
9666
|
}
|
|
9422
|
-
if (normalized.startsWith("
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
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]))) {
|
|
9428
9676
|
return compileInnerExpression(normalized);
|
|
9429
9677
|
}
|
|
9430
|
-
|
|
9678
|
+
if (normalized.startsWith("$")) return normalized;
|
|
9679
|
+
return `$${normalized}`;
|
|
9431
9680
|
}
|
|
9432
9681
|
function dispatchFunction(name, argsStr) {
|
|
9433
9682
|
const args = splitArgsStr(argsStr);
|
|
@@ -9729,72 +9978,82 @@ function compileMapExpression(exprStr, varName) {
|
|
|
9729
9978
|
return compileInnerExpression(replaced);
|
|
9730
9979
|
}
|
|
9731
9980
|
function splitArgsStr(argsStr) {
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
let inString = false;
|
|
9735
|
-
let stringChar = "";
|
|
9736
|
-
let parenDepth = 0;
|
|
9737
|
-
for (let index = 0; index < argsStr.length; index++) {
|
|
9738
|
-
const ch = argsStr[index];
|
|
9739
|
-
if ((ch === '"' || ch === "'") && (index === 0 || argsStr[index - 1] !== "\\")) {
|
|
9740
|
-
if (!inString) {
|
|
9741
|
-
inString = true;
|
|
9742
|
-
stringChar = ch;
|
|
9743
|
-
} else if (ch === stringChar) {
|
|
9744
|
-
inString = false;
|
|
9745
|
-
stringChar = "";
|
|
9746
|
-
}
|
|
9747
|
-
current += ch;
|
|
9748
|
-
} else if (ch === "(" && !inString) {
|
|
9749
|
-
parenDepth++;
|
|
9750
|
-
current += ch;
|
|
9751
|
-
} else if (ch === ")" && !inString) {
|
|
9752
|
-
parenDepth--;
|
|
9753
|
-
current += ch;
|
|
9754
|
-
} else if (ch === "," && !inString && parenDepth === 0) {
|
|
9755
|
-
args.push(current.trim());
|
|
9756
|
-
current = "";
|
|
9757
|
-
} else {
|
|
9758
|
-
current += ch;
|
|
9759
|
-
}
|
|
9760
|
-
}
|
|
9761
|
-
if (current.trim()) args.push(current.trim());
|
|
9762
|
-
return args;
|
|
9981
|
+
if (!argsStr.trim()) return [];
|
|
9982
|
+
return splitTopLevel2(argsStr, ",");
|
|
9763
9983
|
}
|
|
9764
|
-
function
|
|
9765
|
-
const
|
|
9766
|
-
|
|
9767
|
-
let
|
|
9768
|
-
let inString = false;
|
|
9769
|
-
let stringChar = "";
|
|
9984
|
+
function topLevelPositions(source) {
|
|
9985
|
+
const positions = [];
|
|
9986
|
+
const stack = [];
|
|
9987
|
+
let quote = null;
|
|
9770
9988
|
for (let index = 0; index < source.length; index++) {
|
|
9771
9989
|
const ch = source[index];
|
|
9772
|
-
if (
|
|
9773
|
-
if (
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
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");
|
|
9779
10006
|
}
|
|
9780
|
-
|
|
9781
|
-
} else if (!inString && ch === "(") {
|
|
9782
|
-
parenDepth++;
|
|
9783
|
-
current += ch;
|
|
9784
|
-
} else if (!inString && ch === ")") {
|
|
9785
|
-
parenDepth--;
|
|
9786
|
-
current += ch;
|
|
9787
|
-
} else if (!inString && parenDepth === 0 && source.startsWith(separator, index)) {
|
|
9788
|
-
parts.push(current);
|
|
9789
|
-
current = "";
|
|
9790
|
-
index += separator.length - 1;
|
|
9791
|
-
} else {
|
|
9792
|
-
current += ch;
|
|
10007
|
+
continue;
|
|
9793
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}`);
|
|
9794
10028
|
}
|
|
9795
|
-
parts.push(current);
|
|
9796
10029
|
return parts;
|
|
9797
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
|
+
}
|
|
9798
10057
|
function stripOuterParentheses(source) {
|
|
9799
10058
|
let normalized = source.trim();
|
|
9800
10059
|
while (normalized.startsWith("(") && normalized.endsWith(")") && isWrappedByOuterParentheses(normalized)) {
|
|
@@ -9840,10 +10099,15 @@ function isExpressionObject(value) {
|
|
|
9840
10099
|
value && typeof value === "object" && "__expr__" in value && typeof value.__expr__ === "string" && "__compiled__" in value && typeof value.__compiled__ === "boolean"
|
|
9841
10100
|
);
|
|
9842
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
|
+
}
|
|
9843
10107
|
function hasExpressionInObject(value) {
|
|
9844
10108
|
if (isExpressionObject(value)) return true;
|
|
9845
|
-
if (!value || typeof value !== "object") return false;
|
|
9846
10109
|
if (Array.isArray(value)) return value.some((item) => hasExpressionInObject(item));
|
|
10110
|
+
if (!isPlainExpressionRecord(value)) return false;
|
|
9847
10111
|
return Object.values(value).some((item) => hasExpressionInObject(item));
|
|
9848
10112
|
}
|
|
9849
10113
|
function hasExpressionInPipeline(pipeline) {
|
|
@@ -9860,7 +10124,7 @@ function transformExpressions(value, context) {
|
|
|
9860
10124
|
if (Array.isArray(value)) {
|
|
9861
10125
|
return value.map((item) => transformStageEntry(item));
|
|
9862
10126
|
}
|
|
9863
|
-
if (!value
|
|
10127
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9864
10128
|
const entries = Object.entries(value);
|
|
9865
10129
|
const result = {};
|
|
9866
10130
|
for (const [key, current] of entries) {
|
|
@@ -9869,7 +10133,7 @@ function transformExpressions(value, context) {
|
|
|
9869
10133
|
return result;
|
|
9870
10134
|
}
|
|
9871
10135
|
function transformStageEntry(stage) {
|
|
9872
|
-
if (!
|
|
10136
|
+
if (!isPlainExpressionRecord(stage)) {
|
|
9873
10137
|
return transformExpressions(stage, "project");
|
|
9874
10138
|
}
|
|
9875
10139
|
const entries = Object.entries(stage);
|
|
@@ -9893,7 +10157,7 @@ function transformStageValue(value, context) {
|
|
|
9893
10157
|
if (Array.isArray(value)) {
|
|
9894
10158
|
return value.map((item) => transformStageValue(item, context));
|
|
9895
10159
|
}
|
|
9896
|
-
if (!value
|
|
10160
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9897
10161
|
const entries = Object.entries(value);
|
|
9898
10162
|
const result = {};
|
|
9899
10163
|
for (const [key, current] of entries) {
|
|
@@ -10824,6 +11088,7 @@ var AggregateChain = class {
|
|
|
10824
11088
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).explain(verbosity === true ? "queryPlanner" : verbosity);
|
|
10825
11089
|
}
|
|
10826
11090
|
stream() {
|
|
11091
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10827
11092
|
const stream = this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).stream();
|
|
10828
11093
|
const onWriteComplete = this.writePipelineHooks?.onWriteComplete;
|
|
10829
11094
|
if (onWriteComplete) {
|
|
@@ -10841,6 +11106,7 @@ var AggregateChain = class {
|
|
|
10841
11106
|
throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
|
|
10842
11107
|
}
|
|
10843
11108
|
this.executed = true;
|
|
11109
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10844
11110
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).toArray().then(async (result) => {
|
|
10845
11111
|
await this.writePipelineHooks?.onWriteComplete?.();
|
|
10846
11112
|
return result;
|
|
@@ -11157,48 +11423,59 @@ function stripWriteCacheControlOptions(options) {
|
|
|
11157
11423
|
|
|
11158
11424
|
// src/adapters/mongodb/writes/write-basic.ts
|
|
11159
11425
|
async function insertOneDocument(collection, ...args) {
|
|
11426
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11160
11427
|
return collection.insertOne(...args);
|
|
11161
11428
|
}
|
|
11162
11429
|
async function insertManyDocuments(collection, ...args) {
|
|
11430
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11163
11431
|
return collection.insertMany(...args);
|
|
11164
11432
|
}
|
|
11165
11433
|
async function updateOneDocument(collection, ...args) {
|
|
11434
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11166
11435
|
return collection.updateOne(...args);
|
|
11167
11436
|
}
|
|
11168
11437
|
async function updateManyDocuments(collection, ...args) {
|
|
11438
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11169
11439
|
return collection.updateMany(...args);
|
|
11170
11440
|
}
|
|
11171
11441
|
async function replaceOneDocument(collection, ...args) {
|
|
11442
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11172
11443
|
return collection.replaceOne(...args);
|
|
11173
11444
|
}
|
|
11174
11445
|
async function findOneAndUpdateDocument(collection, filter, update, options) {
|
|
11446
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11175
11447
|
if (options !== void 0) {
|
|
11176
11448
|
return collection.findOneAndUpdate(filter, update, options);
|
|
11177
11449
|
}
|
|
11178
11450
|
return collection.findOneAndUpdate(filter, update);
|
|
11179
11451
|
}
|
|
11180
11452
|
async function findOneAndReplaceDocument(collection, filter, replacement, options) {
|
|
11453
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11181
11454
|
if (options !== void 0) {
|
|
11182
11455
|
return collection.findOneAndReplace(filter, replacement, options);
|
|
11183
11456
|
}
|
|
11184
11457
|
return collection.findOneAndReplace(filter, replacement);
|
|
11185
11458
|
}
|
|
11186
11459
|
async function findOneAndDeleteDocument(collection, filter, options) {
|
|
11460
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11187
11461
|
if (options !== void 0) {
|
|
11188
11462
|
return collection.findOneAndDelete(filter, options);
|
|
11189
11463
|
}
|
|
11190
11464
|
return collection.findOneAndDelete(filter);
|
|
11191
11465
|
}
|
|
11192
11466
|
async function upsertOneDocument(collection, filter, update, options = {}) {
|
|
11467
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11193
11468
|
return collection.updateOne(filter, update, {
|
|
11194
11469
|
...options,
|
|
11195
11470
|
upsert: true
|
|
11196
11471
|
});
|
|
11197
11472
|
}
|
|
11198
11473
|
async function deleteOneDocument(collection, ...args) {
|
|
11474
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11199
11475
|
return collection.deleteOne(...args);
|
|
11200
11476
|
}
|
|
11201
11477
|
async function deleteManyDocuments(collection, ...args) {
|
|
11478
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11202
11479
|
return collection.deleteMany(...args);
|
|
11203
11480
|
}
|
|
11204
11481
|
async function incrementOneDocument(collection, filter, field, incrementOrOptions, maybeOptions) {
|
|
@@ -11225,6 +11502,7 @@ async function incrementOneDocument(collection, filter, field, incrementOrOption
|
|
|
11225
11502
|
includeResultMetadata: true
|
|
11226
11503
|
};
|
|
11227
11504
|
if (normalizedProjection) findOptions.projection = normalizedProjection;
|
|
11505
|
+
assertManagedTransactionWriteAllowed(findOptions);
|
|
11228
11506
|
const rawResult = await collection.findOneAndUpdate(
|
|
11229
11507
|
filter,
|
|
11230
11508
|
updateDocument,
|
|
@@ -11357,14 +11635,25 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
11357
11635
|
return;
|
|
11358
11636
|
}
|
|
11359
11637
|
let nextIndex = 0;
|
|
11638
|
+
let stopAcquiring = false;
|
|
11639
|
+
let firstFatal;
|
|
11360
11640
|
const workerCount = Math.min(concurrency, items.length);
|
|
11361
11641
|
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
11362
|
-
while (nextIndex < items.length) {
|
|
11642
|
+
while (!stopAcquiring && nextIndex < items.length) {
|
|
11363
11643
|
const item = items[nextIndex];
|
|
11364
11644
|
nextIndex += 1;
|
|
11365
|
-
|
|
11645
|
+
try {
|
|
11646
|
+
await worker(item);
|
|
11647
|
+
} catch (error) {
|
|
11648
|
+
if (!stopAcquiring) {
|
|
11649
|
+
firstFatal = error;
|
|
11650
|
+
stopAcquiring = true;
|
|
11651
|
+
}
|
|
11652
|
+
break;
|
|
11653
|
+
}
|
|
11366
11654
|
}
|
|
11367
11655
|
}));
|
|
11656
|
+
if (stopAcquiring) throw firstFatal;
|
|
11368
11657
|
}
|
|
11369
11658
|
async function insertBatchDocuments(collection, documents, options = {}) {
|
|
11370
11659
|
if (!Array.isArray(documents)) {
|
|
@@ -11411,12 +11700,14 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11411
11700
|
insertedIds: {},
|
|
11412
11701
|
retries: []
|
|
11413
11702
|
};
|
|
11703
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11414
11704
|
await runWithConcurrency(batchPlans, effectiveConcurrency, async ({ batchIndex, batch, batchOffset: batchOffset2 }) => {
|
|
11415
11705
|
let pendingBatch = batch;
|
|
11416
11706
|
let pendingIndexes = batch.map((_, index) => index);
|
|
11417
11707
|
let attempts = 0;
|
|
11418
11708
|
while (true) {
|
|
11419
11709
|
try {
|
|
11710
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11420
11711
|
const batchResult = await collection.insertMany(pendingBatch, {
|
|
11421
11712
|
...driverOptions,
|
|
11422
11713
|
ordered
|
|
@@ -11436,6 +11727,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11436
11727
|
});
|
|
11437
11728
|
break;
|
|
11438
11729
|
} catch (cause) {
|
|
11730
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11439
11731
|
const errorRecord = {
|
|
11440
11732
|
batchIndex,
|
|
11441
11733
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11507,6 +11799,10 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11507
11799
|
});
|
|
11508
11800
|
return result;
|
|
11509
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
|
+
}
|
|
11510
11806
|
async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
11511
11807
|
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
11512
11808
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "filter must be a non-array object");
|
|
@@ -11550,6 +11846,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11550
11846
|
"updateBatch retry requires an idempotent update; non-idempotent operators such as $inc/$push cannot be safely replayed"
|
|
11551
11847
|
);
|
|
11552
11848
|
}
|
|
11849
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11553
11850
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11554
11851
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11555
11852
|
const result = {
|
|
@@ -11566,8 +11863,9 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11566
11863
|
let attempts = 0;
|
|
11567
11864
|
while (true) {
|
|
11568
11865
|
try {
|
|
11866
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11569
11867
|
const batchResult = await collection.updateMany(
|
|
11570
|
-
|
|
11868
|
+
batchWriteFilter(filter, batch),
|
|
11571
11869
|
update,
|
|
11572
11870
|
driverOptions
|
|
11573
11871
|
);
|
|
@@ -11585,6 +11883,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11585
11883
|
});
|
|
11586
11884
|
break;
|
|
11587
11885
|
} catch (cause) {
|
|
11886
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11588
11887
|
const errorRecord = {
|
|
11589
11888
|
batchIndex,
|
|
11590
11889
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11647,6 +11946,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11647
11946
|
if (!Number.isInteger(retryAttempts) || retryAttempts < 0) {
|
|
11648
11947
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "retryAttempts must be a non-negative integer");
|
|
11649
11948
|
}
|
|
11949
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11650
11950
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11651
11951
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11652
11952
|
const result = {
|
|
@@ -11661,8 +11961,9 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11661
11961
|
let attempts = 0;
|
|
11662
11962
|
while (true) {
|
|
11663
11963
|
try {
|
|
11964
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11664
11965
|
const batchResult = await collection.deleteMany(
|
|
11665
|
-
|
|
11966
|
+
batchWriteFilter(filter, batch),
|
|
11666
11967
|
driverOptions
|
|
11667
11968
|
);
|
|
11668
11969
|
result.deletedCount += batchResult.deletedCount;
|
|
@@ -11677,6 +11978,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11677
11978
|
});
|
|
11678
11979
|
break;
|
|
11679
11980
|
} catch (cause) {
|
|
11981
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11680
11982
|
const errorRecord = {
|
|
11681
11983
|
batchIndex,
|
|
11682
11984
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -12205,9 +12507,11 @@ var MongoCollectionAccessor = class {
|
|
|
12205
12507
|
const normalizedQuery = this._cvFilter(query);
|
|
12206
12508
|
const maxTimeMS = this.management.defaults?.maxTimeMS;
|
|
12207
12509
|
const merged = { ...maxTimeMS !== void 0 ? { maxTimeMS } : {}, ...options ?? {} };
|
|
12208
|
-
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;
|
|
12209
12511
|
const { cache: _cache, ...keyOptions } = merged;
|
|
12210
12512
|
void _cache;
|
|
12513
|
+
const callerSignal = keyOptions.signal;
|
|
12514
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12211
12515
|
const executeCount = (signal) => countDocuments(
|
|
12212
12516
|
this.collectionRef,
|
|
12213
12517
|
normalizedQuery ?? {},
|
|
@@ -12218,15 +12522,16 @@ var MongoCollectionAccessor = class {
|
|
|
12218
12522
|
const cacheKey = buildCountCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, keyOptions);
|
|
12219
12523
|
const cached = await Promise.resolve(this.management.queryCache.get(cacheKey));
|
|
12220
12524
|
if (cached !== void 0) {
|
|
12525
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12221
12526
|
return Promise.resolve(wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, cached));
|
|
12222
12527
|
}
|
|
12223
|
-
const runner2 = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12528
|
+
const runner2 = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12224
12529
|
return runner2.then(async (result) => {
|
|
12225
12530
|
await Promise.resolve(this.management.queryCache?.set(cacheKey, result, cacheTTL));
|
|
12226
12531
|
return wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result);
|
|
12227
12532
|
});
|
|
12228
12533
|
}
|
|
12229
|
-
const runner = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12534
|
+
const runner = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12230
12535
|
return runner.then((result) => wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result));
|
|
12231
12536
|
}
|
|
12232
12537
|
/** Runs an aggregation pipeline and returns a chainable aggregate cursor. */
|
|
@@ -12783,10 +13088,10 @@ function assertCompatPoolExists(poolManager, poolName) {
|
|
|
12783
13088
|
function createPoolScope(runtime, poolName) {
|
|
12784
13089
|
return {
|
|
12785
13090
|
collection: (name) => runtime.scopedCollection(name, { pool: poolName }),
|
|
12786
|
-
model: (
|
|
13091
|
+
model: (model) => runtime.scopedModel(model, { pool: poolName }),
|
|
12787
13092
|
use: (dbName) => ({
|
|
12788
13093
|
collection: (name) => runtime.scopedCollection(name, { pool: poolName, database: dbName }),
|
|
12789
|
-
model: (
|
|
13094
|
+
model: (model) => runtime.scopedModel(model, { pool: poolName, database: dbName })
|
|
12790
13095
|
})
|
|
12791
13096
|
};
|
|
12792
13097
|
}
|
|
@@ -13515,20 +13820,21 @@ var DistributedCacheLockManager = class {
|
|
|
13515
13820
|
}
|
|
13516
13821
|
}
|
|
13517
13822
|
async withLock(key, callback, options = {}) {
|
|
13823
|
+
let lock;
|
|
13518
13824
|
try {
|
|
13519
|
-
|
|
13520
|
-
try {
|
|
13521
|
-
return await callback();
|
|
13522
|
-
} finally {
|
|
13523
|
-
await lock.release().catch(() => {
|
|
13524
|
-
});
|
|
13525
|
-
}
|
|
13825
|
+
lock = await this.acquireLock(key, options);
|
|
13526
13826
|
} catch (error) {
|
|
13527
13827
|
if (this._isRedisConnectionError(error) && options.fallbackToNoLock) {
|
|
13528
13828
|
return callback();
|
|
13529
13829
|
}
|
|
13530
13830
|
throw error;
|
|
13531
13831
|
}
|
|
13832
|
+
try {
|
|
13833
|
+
return await callback();
|
|
13834
|
+
} finally {
|
|
13835
|
+
await lock.release().catch(() => {
|
|
13836
|
+
});
|
|
13837
|
+
}
|
|
13532
13838
|
}
|
|
13533
13839
|
async acquireLock(key, options = {}) {
|
|
13534
13840
|
const ttl = options.ttl ?? 1e4;
|
|
@@ -14631,8 +14937,6 @@ var SlowQueryLogManager = class {
|
|
|
14631
14937
|
};
|
|
14632
14938
|
|
|
14633
14939
|
// src/capabilities/sync/index.ts
|
|
14634
|
-
var import_promises = require("node:fs/promises");
|
|
14635
|
-
var import_node_path2 = __toESM(require("node:path"));
|
|
14636
14940
|
var import_mongodb7 = require("mongodb");
|
|
14637
14941
|
|
|
14638
14942
|
// src/capabilities/sync/idempotency.ts
|
|
@@ -14735,25 +15039,19 @@ function validateSyncIdempotencyConfig(config) {
|
|
|
14735
15039
|
}
|
|
14736
15040
|
}
|
|
14737
15041
|
|
|
14738
|
-
// 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"));
|
|
14739
15045
|
function normalizeError(error) {
|
|
14740
15046
|
return error instanceof Error ? error : new Error(String(error));
|
|
14741
15047
|
}
|
|
14742
15048
|
function delay2(ms) {
|
|
14743
|
-
if (ms <= 0)
|
|
14744
|
-
return Promise.resolve();
|
|
14745
|
-
}
|
|
15049
|
+
if (ms <= 0) return Promise.resolve();
|
|
14746
15050
|
return new Promise((resolve) => {
|
|
14747
15051
|
const timer = setTimeout(resolve, ms);
|
|
14748
15052
|
timer.unref?.();
|
|
14749
15053
|
});
|
|
14750
15054
|
}
|
|
14751
|
-
function normalizeCollectionFilter(collections) {
|
|
14752
|
-
if (!collections?.length || collections.includes("*")) {
|
|
14753
|
-
return null;
|
|
14754
|
-
}
|
|
14755
|
-
return new Set(collections);
|
|
14756
|
-
}
|
|
14757
15055
|
async function syncDirectory(directory) {
|
|
14758
15056
|
try {
|
|
14759
15057
|
const handle = await (0, import_promises.open)(directory, "r");
|
|
@@ -14765,35 +15063,6 @@ async function syncDirectory(directory) {
|
|
|
14765
15063
|
} catch {
|
|
14766
15064
|
}
|
|
14767
15065
|
}
|
|
14768
|
-
function validateTargetConfig(target, index) {
|
|
14769
|
-
if (!target || typeof target !== "object") {
|
|
14770
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] must be an object.`);
|
|
14771
|
-
}
|
|
14772
|
-
if (typeof target.name !== "string" || target.name.trim() === "") {
|
|
14773
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].name must be a non-empty string.`);
|
|
14774
|
-
}
|
|
14775
|
-
if (!target.apply && !target.uri && !target.pool) {
|
|
14776
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] requires one of apply / uri / pool.`);
|
|
14777
|
-
}
|
|
14778
|
-
if (target.uri !== void 0 && (typeof target.uri !== "string" || target.uri.trim() === "")) {
|
|
14779
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].uri must be a non-empty string when provided.`);
|
|
14780
|
-
}
|
|
14781
|
-
if (target.pool !== void 0 && (typeof target.pool !== "string" || target.pool.trim() === "")) {
|
|
14782
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].pool must be a non-empty string when provided.`);
|
|
14783
|
-
}
|
|
14784
|
-
if (target.databaseName !== void 0 && (typeof target.databaseName !== "string" || target.databaseName.trim() === "")) {
|
|
14785
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].databaseName must be a non-empty string when provided.`);
|
|
14786
|
-
}
|
|
14787
|
-
if (target.collections !== void 0 && (!Array.isArray(target.collections) || target.collections.length === 0)) {
|
|
14788
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must be a non-empty array when provided.`);
|
|
14789
|
-
}
|
|
14790
|
-
if (target.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14791
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must contain non-empty strings.`);
|
|
14792
|
-
}
|
|
14793
|
-
if (target.apply !== void 0 && typeof target.apply !== "function") {
|
|
14794
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].apply must be a function when provided.`);
|
|
14795
|
-
}
|
|
14796
|
-
}
|
|
14797
15066
|
function validateResumeTokenConfig(config) {
|
|
14798
15067
|
if (!config || typeof config !== "object") {
|
|
14799
15068
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken must be an object.");
|
|
@@ -14824,39 +15093,6 @@ function validateResumeTokenConfig(config) {
|
|
|
14824
15093
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken.saveRetryDelayMs must be a non-negative integer.");
|
|
14825
15094
|
}
|
|
14826
15095
|
}
|
|
14827
|
-
function validateSyncConfig(config) {
|
|
14828
|
-
if (!config || typeof config !== "object") {
|
|
14829
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] config must be an object.");
|
|
14830
|
-
}
|
|
14831
|
-
if (typeof config.enabled !== "boolean") {
|
|
14832
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] enabled must be a boolean.");
|
|
14833
|
-
}
|
|
14834
|
-
if (!config.enabled) {
|
|
14835
|
-
return;
|
|
14836
|
-
}
|
|
14837
|
-
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
14838
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] targets must be a non-empty array.");
|
|
14839
|
-
}
|
|
14840
|
-
if (config.collections !== void 0 && (!Array.isArray(config.collections) || config.collections.length === 0)) {
|
|
14841
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must be a non-empty array when provided.");
|
|
14842
|
-
}
|
|
14843
|
-
if (config.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14844
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must contain non-empty strings.");
|
|
14845
|
-
}
|
|
14846
|
-
if (config.filter !== void 0 && typeof config.filter !== "function") {
|
|
14847
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] filter must be a function.");
|
|
14848
|
-
}
|
|
14849
|
-
if (config.transform !== void 0 && typeof config.transform !== "function") {
|
|
14850
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] transform must be a function.");
|
|
14851
|
-
}
|
|
14852
|
-
validateSyncIdempotencyConfig(config.idempotency);
|
|
14853
|
-
config.targets.forEach((target, index) => {
|
|
14854
|
-
validateTargetConfig(target, index);
|
|
14855
|
-
});
|
|
14856
|
-
if (config.resumeToken) {
|
|
14857
|
-
validateResumeTokenConfig(config.resumeToken);
|
|
14858
|
-
}
|
|
14859
|
-
}
|
|
14860
15096
|
var ResumeTokenStore = class {
|
|
14861
15097
|
constructor(options = {}) {
|
|
14862
15098
|
this.storage = options.storage ?? "file";
|
|
@@ -14880,16 +15116,9 @@ var ResumeTokenStore = class {
|
|
|
14880
15116
|
return JSON.parse(payload);
|
|
14881
15117
|
} catch (error) {
|
|
14882
15118
|
const code = error?.code;
|
|
14883
|
-
if (code !== "ENOENT")
|
|
14884
|
-
this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14885
|
-
}
|
|
15119
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14886
15120
|
if (code !== "ENOENT" && this.strictLoad) {
|
|
14887
|
-
throw createError(
|
|
14888
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14889
|
-
"[Sync] failed to load resume token",
|
|
14890
|
-
void 0,
|
|
14891
|
-
normalizeError(error)
|
|
14892
|
-
);
|
|
15121
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to load resume token", void 0, normalizeError(error));
|
|
14893
15122
|
}
|
|
14894
15123
|
return null;
|
|
14895
15124
|
}
|
|
@@ -14915,12 +15144,7 @@ var ResumeTokenStore = class {
|
|
|
14915
15144
|
}
|
|
14916
15145
|
this.logger?.error?.("[Sync] failed to save resume token", lastError);
|
|
14917
15146
|
if (this.strictSave) {
|
|
14918
|
-
throw createError(
|
|
14919
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14920
|
-
"[Sync] failed to save resume token",
|
|
14921
|
-
void 0,
|
|
14922
|
-
lastError ?? void 0
|
|
14923
|
-
);
|
|
15147
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to save resume token", void 0, lastError ?? void 0);
|
|
14924
15148
|
}
|
|
14925
15149
|
}
|
|
14926
15150
|
async writePayload(payload) {
|
|
@@ -14953,9 +15177,7 @@ var ResumeTokenStore = class {
|
|
|
14953
15177
|
await (0, import_promises.copyFile)(this.path, `${this.path}.bak`);
|
|
14954
15178
|
} catch (error) {
|
|
14955
15179
|
const code = error?.code;
|
|
14956
|
-
if (code !== "ENOENT")
|
|
14957
|
-
throw error;
|
|
14958
|
-
}
|
|
15180
|
+
if (code !== "ENOENT") throw error;
|
|
14959
15181
|
}
|
|
14960
15182
|
}
|
|
14961
15183
|
async clear() {
|
|
@@ -14967,12 +15189,80 @@ var ResumeTokenStore = class {
|
|
|
14967
15189
|
await (0, import_promises.unlink)(this.path);
|
|
14968
15190
|
} catch (error) {
|
|
14969
15191
|
const code = error?.code;
|
|
14970
|
-
if (code !== "ENOENT")
|
|
14971
|
-
this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14972
|
-
}
|
|
15192
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14973
15193
|
}
|
|
14974
15194
|
}
|
|
14975
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
|
+
}
|
|
14976
15266
|
var ChangeStreamSyncManager = class {
|
|
14977
15267
|
constructor(options) {
|
|
14978
15268
|
this.targets = [];
|
|
@@ -14981,7 +15271,10 @@ var ChangeStreamSyncManager = class {
|
|
|
14981
15271
|
this.closingStreamPromise = null;
|
|
14982
15272
|
this.changeQueue = Promise.resolve();
|
|
14983
15273
|
this.running = false;
|
|
15274
|
+
this.stopping = false;
|
|
15275
|
+
this.stopPromise = null;
|
|
14984
15276
|
this.fatalSyncError = null;
|
|
15277
|
+
this.generation = 0;
|
|
14985
15278
|
this.stats = {
|
|
14986
15279
|
eventCount: 0,
|
|
14987
15280
|
syncedCount: 0,
|
|
@@ -15014,40 +15307,62 @@ var ChangeStreamSyncManager = class {
|
|
|
15014
15307
|
* @since v1.0.9
|
|
15015
15308
|
*/
|
|
15016
15309
|
async start() {
|
|
15310
|
+
if (this.stopPromise) await this.stopPromise;
|
|
15017
15311
|
if (this.running || !this.config.enabled) return;
|
|
15018
15312
|
if (this.startPromise) return this.startPromise;
|
|
15313
|
+
const generation = ++this.generation;
|
|
15019
15314
|
const startup = (async () => {
|
|
15020
15315
|
await this.closingStreamPromise;
|
|
15316
|
+
await this.changeQueue.catch(() => void 0);
|
|
15317
|
+
this.assertCurrentGeneration(generation, true);
|
|
15021
15318
|
if (this.running) return;
|
|
15022
15319
|
this.fatalSyncError = null;
|
|
15023
|
-
await this.
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
const
|
|
15032
|
-
this.
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
return;
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
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
|
+
}
|
|
15051
15366
|
})();
|
|
15052
15367
|
this.startPromise = startup;
|
|
15053
15368
|
try {
|
|
@@ -15061,16 +15376,37 @@ var ChangeStreamSyncManager = class {
|
|
|
15061
15376
|
* @since v1.0.9
|
|
15062
15377
|
*/
|
|
15063
15378
|
async stop() {
|
|
15064
|
-
this.
|
|
15065
|
-
|
|
15066
|
-
const
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
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() {
|
|
15071
15408
|
while (this.targets.length > 0) {
|
|
15072
|
-
|
|
15073
|
-
await target?.close();
|
|
15409
|
+
await this.targets.pop()?.close();
|
|
15074
15410
|
}
|
|
15075
15411
|
}
|
|
15076
15412
|
/**
|
|
@@ -15135,11 +15471,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15135
15471
|
return pipeline;
|
|
15136
15472
|
}
|
|
15137
15473
|
async initializeTargets() {
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15141
|
-
|
|
15142
|
-
|
|
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;
|
|
15143
15481
|
}
|
|
15144
15482
|
}
|
|
15145
15483
|
async resolveTarget(target) {
|
|
@@ -15176,14 +15514,15 @@ var ChangeStreamSyncManager = class {
|
|
|
15176
15514
|
const client = await this.clientFactory(target.uri, target.options);
|
|
15177
15515
|
return createMongoTarget(target.name, collections, client, target.databaseName ?? this.db.databaseName, true);
|
|
15178
15516
|
}
|
|
15179
|
-
async handleChange(event) {
|
|
15180
|
-
if (this.fatalSyncError) {
|
|
15517
|
+
async handleChange(event, generation = this.generation) {
|
|
15518
|
+
if (generation !== this.generation || !this.running || this.fatalSyncError) {
|
|
15181
15519
|
this.logger?.warn?.("[Sync] skipping queued change after fatal sync error", this.fatalSyncError);
|
|
15182
15520
|
return;
|
|
15183
15521
|
}
|
|
15184
15522
|
this.stats.eventCount += 1;
|
|
15185
15523
|
this.stats.lastEventTime = /* @__PURE__ */ new Date();
|
|
15186
15524
|
if (this.config.filter && !this.config.filter(event)) {
|
|
15525
|
+
await this.saveProcessedToken(event, generation);
|
|
15187
15526
|
return;
|
|
15188
15527
|
}
|
|
15189
15528
|
let document = event.fullDocument;
|
|
@@ -15195,6 +15534,7 @@ var ChangeStreamSyncManager = class {
|
|
|
15195
15534
|
let duplicateTargets = 0;
|
|
15196
15535
|
const targetErrors = [];
|
|
15197
15536
|
for (const target of this.targets) {
|
|
15537
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15198
15538
|
if (target.collections && !target.collections.has(event.ns.coll)) {
|
|
15199
15539
|
continue;
|
|
15200
15540
|
}
|
|
@@ -15211,11 +15551,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15211
15551
|
if (idempotencyKey && this.idempotency.markMode === "start") {
|
|
15212
15552
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "start");
|
|
15213
15553
|
}
|
|
15554
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15214
15555
|
await target.apply(event, document, {
|
|
15215
15556
|
targetName: target.name,
|
|
15216
15557
|
...idempotencyKey ? { idempotencyKey } : {}
|
|
15217
15558
|
});
|
|
15218
15559
|
if (idempotencyKey && this.idempotency.markMode === "success") {
|
|
15560
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15219
15561
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "success");
|
|
15220
15562
|
}
|
|
15221
15563
|
target.stats.syncCount += 1;
|
|
@@ -15236,26 +15578,30 @@ var ChangeStreamSyncManager = class {
|
|
|
15236
15578
|
}
|
|
15237
15579
|
}
|
|
15238
15580
|
if (targetErrors.length > 0) {
|
|
15239
|
-
this.stopAfterFatalSyncError(targetErrors[0]);
|
|
15581
|
+
this.stopAfterFatalSyncError(targetErrors[0], generation);
|
|
15240
15582
|
return;
|
|
15241
15583
|
}
|
|
15242
|
-
if (
|
|
15243
|
-
|
|
15244
|
-
await this.tokenStore.save(event._id);
|
|
15245
|
-
} catch (error) {
|
|
15246
|
-
const normalized = normalizeError(error);
|
|
15247
|
-
this.stats.tokenSaveErrorCount += 1;
|
|
15248
|
-
this.stats.lastTokenSaveError = normalized;
|
|
15249
|
-
this.stats.lastError = normalized;
|
|
15250
|
-
this.stopAfterFatalSyncError(normalized);
|
|
15251
|
-
throw normalized;
|
|
15252
|
-
}
|
|
15253
|
-
this.stats.syncedCount += 1;
|
|
15584
|
+
if (succeeded === eligibleTargets) {
|
|
15585
|
+
await this.saveProcessedToken(event, generation);
|
|
15254
15586
|
if (duplicateTargets > 0 && duplicateTargets === eligibleTargets) {
|
|
15255
15587
|
this.stats.duplicateEventCount += 1;
|
|
15256
15588
|
}
|
|
15257
15589
|
}
|
|
15258
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
|
+
}
|
|
15259
15605
|
buildTargetIdempotencyKey(targetName, event) {
|
|
15260
15606
|
if (!this.idempotency.enabled) {
|
|
15261
15607
|
return null;
|
|
@@ -15278,7 +15624,8 @@ var ChangeStreamSyncManager = class {
|
|
|
15278
15624
|
ts: Date.now()
|
|
15279
15625
|
}, this.idempotency.ttl));
|
|
15280
15626
|
}
|
|
15281
|
-
stopAfterFatalSyncError(error) {
|
|
15627
|
+
stopAfterFatalSyncError(error, generation) {
|
|
15628
|
+
if (generation !== this.generation) return;
|
|
15282
15629
|
this.fatalSyncError = error;
|
|
15283
15630
|
this.running = false;
|
|
15284
15631
|
const stream = this.changeStream;
|
|
@@ -15296,12 +15643,14 @@ var ChangeStreamSyncManager = class {
|
|
|
15296
15643
|
if (this.closingStreamPromise === closing) this.closingStreamPromise = null;
|
|
15297
15644
|
});
|
|
15298
15645
|
}
|
|
15299
|
-
enqueueChange(event) {
|
|
15300
|
-
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) => {
|
|
15301
15649
|
const normalized = normalizeError(error);
|
|
15302
15650
|
this.stats.errorCount += 1;
|
|
15303
15651
|
this.stats.lastError = normalized;
|
|
15304
15652
|
this.logger?.error?.("[Sync] change handling failed", normalized);
|
|
15653
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15305
15654
|
});
|
|
15306
15655
|
}
|
|
15307
15656
|
};
|
|
@@ -15730,7 +16079,9 @@ var SSHTunnelSSH2 = class {
|
|
|
15730
16079
|
passphrase,
|
|
15731
16080
|
port = 22,
|
|
15732
16081
|
readyTimeout = 2e4,
|
|
15733
|
-
keepaliveInterval = 3e4
|
|
16082
|
+
keepaliveInterval = 3e4,
|
|
16083
|
+
hostHash,
|
|
16084
|
+
hostVerifier
|
|
15734
16085
|
} = this._sshConfig;
|
|
15735
16086
|
if (!host || !username) {
|
|
15736
16087
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH config requires: host, username");
|
|
@@ -15739,6 +16090,8 @@ var SSHTunnelSSH2 = class {
|
|
|
15739
16090
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH authentication required: provide password, privateKey, or privateKeyPath");
|
|
15740
16091
|
}
|
|
15741
16092
|
const config = { host, port, username, readyTimeout, keepaliveInterval };
|
|
16093
|
+
if (hostHash !== void 0) config.hostHash = hostHash;
|
|
16094
|
+
if (hostVerifier !== void 0) config.hostVerifier = hostVerifier;
|
|
15742
16095
|
if (password) {
|
|
15743
16096
|
config.password = password;
|
|
15744
16097
|
} else if (privateKey) {
|
|
@@ -15757,6 +16110,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15757
16110
|
return new Promise((resolve, reject) => {
|
|
15758
16111
|
const ssh = new SshClient();
|
|
15759
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
|
+
};
|
|
15760
16132
|
const server = net.createServer((socket) => {
|
|
15761
16133
|
this._activeSockets.add(socket);
|
|
15762
16134
|
socket.on("close", () => this._activeSockets.delete(socket));
|
|
@@ -15782,12 +16154,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15782
16154
|
}
|
|
15783
16155
|
);
|
|
15784
16156
|
});
|
|
15785
|
-
server.on("error",
|
|
15786
|
-
if (!settled) {
|
|
15787
|
-
settled = true;
|
|
15788
|
-
reject(err);
|
|
15789
|
-
}
|
|
15790
|
-
});
|
|
16157
|
+
server.on("error", fail);
|
|
15791
16158
|
server.listen(this._sshConfig.localPort ?? 0, "127.0.0.1", () => {
|
|
15792
16159
|
this.server = server;
|
|
15793
16160
|
const addr = server.address();
|
|
@@ -15802,25 +16169,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15802
16169
|
});
|
|
15803
16170
|
ssh.on("error", (err) => {
|
|
15804
16171
|
if (!settled) {
|
|
15805
|
-
|
|
15806
|
-
server.close();
|
|
15807
|
-
reject(err);
|
|
16172
|
+
fail(err);
|
|
15808
16173
|
return;
|
|
15809
16174
|
}
|
|
15810
16175
|
this.markDisconnected();
|
|
15811
16176
|
});
|
|
15812
16177
|
const handleDisconnect = () => {
|
|
15813
16178
|
if (!settled) {
|
|
15814
|
-
|
|
15815
|
-
server.close();
|
|
15816
|
-
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"));
|
|
15817
16180
|
return;
|
|
15818
16181
|
}
|
|
15819
16182
|
this.markDisconnected();
|
|
15820
16183
|
};
|
|
15821
16184
|
ssh.on("close", handleDisconnect);
|
|
15822
16185
|
ssh.on("end", handleDisconnect);
|
|
15823
|
-
|
|
16186
|
+
try {
|
|
16187
|
+
ssh.connect(authConfig);
|
|
16188
|
+
} catch (err) {
|
|
16189
|
+
fail(err);
|
|
16190
|
+
}
|
|
15824
16191
|
});
|
|
15825
16192
|
});
|
|
15826
16193
|
}
|
|
@@ -15851,6 +16218,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15851
16218
|
ssh?.end();
|
|
15852
16219
|
}
|
|
15853
16220
|
markDisconnected() {
|
|
16221
|
+
const ssh = this.sshClient;
|
|
15854
16222
|
this.isConnected = false;
|
|
15855
16223
|
this.localPort = null;
|
|
15856
16224
|
this.sshClient = null;
|
|
@@ -15861,6 +16229,10 @@ var SSHTunnelSSH2 = class {
|
|
|
15861
16229
|
}
|
|
15862
16230
|
this._activeSockets.clear();
|
|
15863
16231
|
void closeServer(server);
|
|
16232
|
+
try {
|
|
16233
|
+
ssh?.end();
|
|
16234
|
+
} catch {
|
|
16235
|
+
}
|
|
15864
16236
|
}
|
|
15865
16237
|
};
|
|
15866
16238
|
function parseHostFromUri(uri) {
|
|
@@ -15947,12 +16319,17 @@ async function prepareSshTunnelConnectConfig(connectConfig, databaseName, logger
|
|
|
15947
16319
|
const remotePort = Number(cfg["dstPort"] ?? rawCfg["remotePort"] ?? rawCfg["mongoPort"] ?? parsePortFromUri(uri));
|
|
15948
16320
|
const tunnel = new SSHTunnelSSH2(sshCfg, remoteHost, remotePort, { name: databaseName });
|
|
15949
16321
|
logger.info?.(`[SSH] Establishing tunnel to ${remoteHost}:${remotePort} via ${String(cfg["host"])}`);
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
15955
|
-
|
|
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
|
+
}
|
|
15956
16333
|
}
|
|
15957
16334
|
|
|
15958
16335
|
// src/entry/runtime-schema-dsl.ts
|
|
@@ -15998,14 +16375,14 @@ async function cleanupFailedRuntimeConnection(resources, logger) {
|
|
|
15998
16375
|
}
|
|
15999
16376
|
|
|
16000
16377
|
// src/capabilities/function-cache/index.ts
|
|
16001
|
-
var
|
|
16378
|
+
var import_cache_hub4 = require("cache-hub");
|
|
16002
16379
|
var import_function_cache = require("cache-hub/function-cache");
|
|
16003
16380
|
function isValidCache(cache) {
|
|
16004
16381
|
return cache !== null && typeof cache === "object" && typeof cache.get === "function" && typeof cache.set === "function";
|
|
16005
16382
|
}
|
|
16006
16383
|
function resolveCacheSource(cacheOrDb) {
|
|
16007
16384
|
if (cacheOrDb === void 0) {
|
|
16008
|
-
return new
|
|
16385
|
+
return new import_cache_hub4.MemoryCache();
|
|
16009
16386
|
}
|
|
16010
16387
|
if (isValidCache(cacheOrDb)) {
|
|
16011
16388
|
return cacheOrDb;
|
|
@@ -16091,7 +16468,7 @@ function withCache(fn, options = {}) {
|
|
|
16091
16468
|
namespace: namespace ?? "fn",
|
|
16092
16469
|
keyBuilder,
|
|
16093
16470
|
condition,
|
|
16094
|
-
cache: externalCache ?? new
|
|
16471
|
+
cache: externalCache ?? new import_cache_hub4.MemoryCache(),
|
|
16095
16472
|
enableStats
|
|
16096
16473
|
});
|
|
16097
16474
|
const baseStats = wrapped.stats.bind(wrapped);
|
|
@@ -16841,9 +17218,8 @@ var MonSQLizeRuntime = class {
|
|
|
16841
17218
|
driverError = error instanceof Error ? error.message : String(error);
|
|
16842
17219
|
}
|
|
16843
17220
|
}
|
|
16844
|
-
const
|
|
16845
|
-
const poolHealth =
|
|
16846
|
-
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;
|
|
16847
17223
|
const connected = this._connected && driverConnected;
|
|
16848
17224
|
return {
|
|
16849
17225
|
status: connected ? "up" : "down",
|
|
@@ -16851,7 +17227,7 @@ var MonSQLizeRuntime = class {
|
|
|
16851
17227
|
driver: { connected: driverConnected, ...driverError ? { error: driverError } : {} },
|
|
16852
17228
|
defaults: this.getDefaults(),
|
|
16853
17229
|
cache: {
|
|
16854
|
-
enabled:
|
|
17230
|
+
enabled: cacheEnabled,
|
|
16855
17231
|
stats: cacheStats,
|
|
16856
17232
|
pools: poolHealth,
|
|
16857
17233
|
distributed: distributedStats
|
|
@@ -16863,18 +17239,18 @@ var MonSQLizeRuntime = class {
|
|
|
16863
17239
|
...driverError ? { error: driverError } : {}
|
|
16864
17240
|
},
|
|
16865
17241
|
cache: {
|
|
16866
|
-
status:
|
|
16867
|
-
enabled:
|
|
17242
|
+
status: cacheProbeStatus,
|
|
17243
|
+
enabled: cacheEnabled,
|
|
16868
17244
|
hasStats: cacheStats !== void 0
|
|
16869
17245
|
},
|
|
16870
17246
|
distributedCacheInvalidator: {
|
|
16871
|
-
status:
|
|
16872
|
-
enabled:
|
|
17247
|
+
status: distributedStatus,
|
|
17248
|
+
enabled: distributedEnabled,
|
|
16873
17249
|
stats: distributedStats
|
|
16874
17250
|
},
|
|
16875
17251
|
pools: {
|
|
16876
|
-
status:
|
|
16877
|
-
enabled:
|
|
17252
|
+
status: poolsStatus,
|
|
17253
|
+
enabled: this._poolManager !== null,
|
|
16878
17254
|
health: poolHealth
|
|
16879
17255
|
}
|
|
16880
17256
|
},
|
|
@@ -16942,7 +17318,7 @@ var MonSQLizeRuntime = class {
|
|
|
16942
17318
|
requireCompatDbInstance(this);
|
|
16943
17319
|
return {
|
|
16944
17320
|
collection: (collectionName) => this.scopedCollection(collectionName, { database: name }),
|
|
16945
|
-
model: (
|
|
17321
|
+
model: (model) => this.scopedModel(model, { database: name })
|
|
16946
17322
|
};
|
|
16947
17323
|
}
|
|
16948
17324
|
pool(poolName) {
|
|
@@ -16974,7 +17350,8 @@ var MonSQLizeRuntime = class {
|
|
|
16974
17350
|
});
|
|
16975
17351
|
}
|
|
16976
17352
|
// Model accessors ---------------------------------------------------------
|
|
16977
|
-
scopedModel(
|
|
17353
|
+
scopedModel(model, options = {}) {
|
|
17354
|
+
const name = typeof model === "string" ? model : model.collectionName;
|
|
16978
17355
|
const dbInstance = requireCompatDbInstance(this);
|
|
16979
17356
|
if (this._client) {
|
|
16980
17357
|
return this.createModelInstance(name, options);
|
|
@@ -16998,7 +17375,8 @@ var MonSQLizeRuntime = class {
|
|
|
16998
17375
|
schemaEngine: this._schemaDslEngine
|
|
16999
17376
|
});
|
|
17000
17377
|
}
|
|
17001
|
-
model(
|
|
17378
|
+
model(model) {
|
|
17379
|
+
const name = typeof model === "string" ? model : model.collectionName;
|
|
17002
17380
|
if (this._client) {
|
|
17003
17381
|
this.ensureConnected();
|
|
17004
17382
|
return this.createModelInstance(name, {
|
|
@@ -17823,6 +18201,27 @@ function ejsonLine(value) {
|
|
|
17823
18201
|
function ejsonManifest(value) {
|
|
17824
18202
|
return import_mongodb11.BSON.EJSON.stringify(value, void 0, 2, { relaxed: false });
|
|
17825
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
|
+
}
|
|
17826
18225
|
function sanitizeName(value) {
|
|
17827
18226
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "data-task";
|
|
17828
18227
|
}
|
|
@@ -17999,10 +18398,9 @@ async function createRestoreSafetyBackup(source, entries, beforeIndexes, targetH
|
|
|
17999
18398
|
}
|
|
18000
18399
|
async function readDataTaskBackup(ref) {
|
|
18001
18400
|
try {
|
|
18002
|
-
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 }));
|
|
18003
18402
|
if (manifest.kind !== "monsqlize-data-task-backup" || manifest.version !== 1 || manifest.runId !== ref.runId) throw new Error("invalid backup manifest");
|
|
18004
18403
|
if (manifest.compression !== "gzip" && manifest.compression !== "none") throw new Error("invalid backup compression");
|
|
18005
|
-
if (!Number.isSafeInteger(manifest.entryCount) || manifest.entryCount < 0) throw new Error("invalid backup entry count");
|
|
18006
18404
|
if (typeof manifest.checksum !== "string" || !/^[a-f0-9]{64}$/i.test(manifest.checksum)) throw new Error("invalid backup checksum");
|
|
18007
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) {
|
|
18008
18406
|
throw new Error("backup data file must stay beside its manifest");
|
|
@@ -18333,6 +18731,7 @@ async function acquireDataTaskLease(host, options) {
|
|
|
18333
18731
|
}
|
|
18334
18732
|
|
|
18335
18733
|
// src/capabilities/data-tasks/job-restore.ts
|
|
18734
|
+
var import_mongodb12 = require("mongodb");
|
|
18336
18735
|
async function findCurrent(collection, identity) {
|
|
18337
18736
|
let chain = collection.find(identity);
|
|
18338
18737
|
if (typeof chain.limit === "function") chain = chain.limit(2);
|
|
@@ -18353,13 +18752,20 @@ function restoreApproval(hashes) {
|
|
|
18353
18752
|
};
|
|
18354
18753
|
return { ...payload, token: hashDataTaskValue(payload) };
|
|
18355
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
|
+
}
|
|
18356
18762
|
function exactIndex(index, current) {
|
|
18357
|
-
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";
|
|
18358
18764
|
}
|
|
18359
18765
|
function pendingIndexMatches(index, current) {
|
|
18360
18766
|
return current.filter((candidate) => {
|
|
18361
18767
|
if (index.name && candidate.name !== index.name) return false;
|
|
18362
|
-
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";
|
|
18363
18769
|
});
|
|
18364
18770
|
}
|
|
18365
18771
|
function plannedDocumentMatches(current, entry) {
|
|
@@ -18627,7 +19033,7 @@ async function restoreDataTaskPlan(plan, target, targetHint, lease) {
|
|
|
18627
19033
|
await updateDataTaskBackup(safety, (manifest) => {
|
|
18628
19034
|
manifest.pendingIndexes = [...manifest.pendingIndexes ?? [], pending];
|
|
18629
19035
|
});
|
|
18630
|
-
await collection.createIndex(index.key, { ...index.options, name: index.name });
|
|
19036
|
+
await collection.createIndex(comparableIndexKey(index.key), { ...index.options, name: index.name });
|
|
18631
19037
|
if (!exactIndex(index, await collection.listIndexes())) {
|
|
18632
19038
|
throw new DataTaskJobError("RESTORE_FAILED", `index "${index.name}" failed immediate readback.`, "restore");
|
|
18633
19039
|
}
|
|
@@ -18799,6 +19205,7 @@ MonSQLize.PoolSelector = PoolSelector;
|
|
|
18799
19205
|
MonSQLize.PoolStats = PoolStatsManager;
|
|
18800
19206
|
MonSQLize.validatePoolConfig = validatePoolConfig;
|
|
18801
19207
|
MonSQLize.validatePoolConfigSafe = validatePoolConfigSafe;
|
|
19208
|
+
MonSQLize.defineModel = defineModel;
|
|
18802
19209
|
MonSQLize.Model = Model;
|
|
18803
19210
|
MonSQLize.expr = expr;
|
|
18804
19211
|
MonSQLize.createExpression = createExpression;
|