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/esm/index.mjs
CHANGED
|
@@ -170,12 +170,17 @@ function createRedisCacheAdapter(redisUrlOrInstance, options) {
|
|
|
170
170
|
|
|
171
171
|
// src/capabilities/cache/distributed-cache-invalidator.ts
|
|
172
172
|
import { randomBytes } from "crypto";
|
|
173
|
+
import { MemoryCache as BaseMemoryCache2 } from "cache-hub";
|
|
173
174
|
var DistributedCacheInvalidator = class {
|
|
174
175
|
constructor(options) {
|
|
175
176
|
if (!options.cache) {
|
|
176
177
|
throw createError(ErrorCodes.CACHE_UNAVAILABLE, "DistributedCacheInvalidator requires a cache instance");
|
|
177
178
|
}
|
|
178
179
|
this._cache = options.cache;
|
|
180
|
+
const safeMemoryCache = this._cache instanceof BaseMemoryCache2 || this._cache.local instanceof BaseMemoryCache2 && this._cache.remote === void 0;
|
|
181
|
+
if (!safeMemoryCache && typeof this._cache.invalidateFromRemote !== "function") {
|
|
182
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "DistributedCacheInvalidator requires receive-only cache invalidation");
|
|
183
|
+
}
|
|
179
184
|
this._logger = options.logger ?? null;
|
|
180
185
|
this.channel = options.channel ?? "monsqlize:cache:invalidate";
|
|
181
186
|
this.instanceId = options.instanceId ?? `instance-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
@@ -223,18 +228,10 @@ var DistributedCacheInvalidator = class {
|
|
|
223
228
|
this._stats.messagesReceived++;
|
|
224
229
|
try {
|
|
225
230
|
if (isPatternInvalidation) {
|
|
226
|
-
|
|
227
|
-
await targetCache.delPattern?.(String(message.pattern));
|
|
228
|
-
}
|
|
231
|
+
await this._invalidateFromRemote("pattern", String(message.pattern));
|
|
229
232
|
this._logger?.debug?.(`[DistributedCacheInvalidator] Invalidated cache targets: ${String(message.pattern)}`);
|
|
230
233
|
} else {
|
|
231
|
-
|
|
232
|
-
if (typeof targetCache.del === "function") {
|
|
233
|
-
await targetCache.del(String(message.key));
|
|
234
|
-
} else {
|
|
235
|
-
await targetCache.delete?.(String(message.key));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
234
|
+
await this._invalidateFromRemote("key", String(message.key));
|
|
238
235
|
this._logger?.debug?.(`[DistributedCacheInvalidator] Invalidated cache key targets: ${String(message.key)}`);
|
|
239
236
|
}
|
|
240
237
|
this._stats.invalidationsTriggered++;
|
|
@@ -244,32 +241,18 @@ var DistributedCacheInvalidator = class {
|
|
|
244
241
|
}
|
|
245
242
|
});
|
|
246
243
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (this._cache?.local && typeof this._cache.local.delPattern === "function") {
|
|
253
|
-
targets.add(this._cache.local);
|
|
254
|
-
}
|
|
255
|
-
if (this._cache?.remote && typeof this._cache.remote.delPattern === "function") {
|
|
256
|
-
targets.add(this._cache.remote);
|
|
257
|
-
}
|
|
258
|
-
return [...targets];
|
|
259
|
-
}
|
|
260
|
-
_getKeyTargetCaches() {
|
|
261
|
-
const targets = /* @__PURE__ */ new Set();
|
|
262
|
-
const supportsKeyDelete = (cache) => typeof cache?.del === "function" || typeof cache?.delete === "function";
|
|
263
|
-
if (supportsKeyDelete(this._cache)) {
|
|
264
|
-
targets.add(this._cache);
|
|
265
|
-
}
|
|
266
|
-
if (supportsKeyDelete(this._cache?.local)) {
|
|
267
|
-
targets.add(this._cache.local);
|
|
244
|
+
async _invalidateFromRemote(kind, value) {
|
|
245
|
+
if (this._cache instanceof BaseMemoryCache2) {
|
|
246
|
+
if (kind === "pattern") await this._cache.delPattern(value);
|
|
247
|
+
else await this._cache.del(value);
|
|
248
|
+
return;
|
|
268
249
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
250
|
+
if (this._cache.local instanceof BaseMemoryCache2 && this._cache.remote === void 0) {
|
|
251
|
+
if (kind === "pattern") await this._cache.local.delPattern(value);
|
|
252
|
+
else await this._cache.local.del(value);
|
|
253
|
+
return;
|
|
271
254
|
}
|
|
272
|
-
|
|
255
|
+
await this._cache.invalidateFromRemote?.(kind, value);
|
|
273
256
|
}
|
|
274
257
|
async invalidate(pattern) {
|
|
275
258
|
if (!pattern) return;
|
|
@@ -341,6 +324,15 @@ var MultiLevelCache = class extends BaseMultiLevelCache {
|
|
|
341
324
|
this._localCompat.setLockManager?.(lockManager);
|
|
342
325
|
this._remoteCompat?.setLockManager?.(lockManager);
|
|
343
326
|
}
|
|
327
|
+
async invalidateFromRemote(kind, value) {
|
|
328
|
+
if (kind === "pattern") {
|
|
329
|
+
await this._localCompat.delPattern(value);
|
|
330
|
+
await this._remoteCompat?.delPattern(value);
|
|
331
|
+
} else {
|
|
332
|
+
await this._localCompat.del(value);
|
|
333
|
+
await this._remoteCompat?.del(value);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
344
336
|
};
|
|
345
337
|
function adaptLegacyCacheLike(v1Cache) {
|
|
346
338
|
if ("has" in v1Cache && typeof v1Cache.has === "function") {
|
|
@@ -532,6 +524,9 @@ function normalizePopulateConfig(path5) {
|
|
|
532
524
|
}
|
|
533
525
|
|
|
534
526
|
// src/capabilities/model/model-registry.ts
|
|
527
|
+
function defineModel(collectionName, definition) {
|
|
528
|
+
return { collectionName, definition };
|
|
529
|
+
}
|
|
535
530
|
var Model = class {
|
|
536
531
|
static {
|
|
537
532
|
// collection name → registered model definition
|
|
@@ -546,21 +541,19 @@ var Model = class {
|
|
|
546
541
|
/** Collection names that have been redefined (used by the runtime to trigger hot-reload). */
|
|
547
542
|
this._redefinedNames = /* @__PURE__ */ new Set();
|
|
548
543
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
* Validates the definition and resolves the timestamps option.
|
|
553
|
-
*/
|
|
554
|
-
static define(collectionName, definition) {
|
|
544
|
+
static define(collectionNameOrDescriptor, definition) {
|
|
545
|
+
const collectionName = typeof collectionNameOrDescriptor === "string" ? collectionNameOrDescriptor : collectionNameOrDescriptor.collectionName;
|
|
546
|
+
const modelDefinition = typeof collectionNameOrDescriptor === "string" ? definition : collectionNameOrDescriptor.definition;
|
|
555
547
|
const normalizedName = validateCollectionName(collectionName);
|
|
556
548
|
if (this.registry.has(normalizedName)) {
|
|
557
549
|
throw createError(ErrorCodes.MODEL_ALREADY_EXISTS, `Model '${normalizedName}' is already defined.`);
|
|
558
550
|
}
|
|
559
|
-
validateDefinition(
|
|
560
|
-
|
|
551
|
+
validateDefinition(modelDefinition);
|
|
552
|
+
const resolvedDefinition = modelDefinition;
|
|
553
|
+
processTimestamps(resolvedDefinition);
|
|
561
554
|
this.registry.set(normalizedName, {
|
|
562
555
|
collectionName: normalizedName,
|
|
563
|
-
definition
|
|
556
|
+
definition: resolvedDefinition
|
|
564
557
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
565
558
|
});
|
|
566
559
|
this.bumpRevision(normalizedName);
|
|
@@ -717,6 +710,40 @@ function serializeDocument(document) {
|
|
|
717
710
|
}
|
|
718
711
|
|
|
719
712
|
// src/capabilities/model/model-write-helpers.ts
|
|
713
|
+
async function* iterateModelCandidateBatches(context, filter, options, batchSize) {
|
|
714
|
+
const lookupOptions = buildModelVersionLookupOptions(options, {
|
|
715
|
+
_id: 1,
|
|
716
|
+
[context.versionConfig?.field ?? "__v"]: 1
|
|
717
|
+
});
|
|
718
|
+
const stream = context.collection.stream(filter, {
|
|
719
|
+
...lookupOptions,
|
|
720
|
+
sort: { _id: 1 },
|
|
721
|
+
batchSize
|
|
722
|
+
});
|
|
723
|
+
let batch = [];
|
|
724
|
+
try {
|
|
725
|
+
for await (const document of stream) {
|
|
726
|
+
batch.push(document);
|
|
727
|
+
if (batch.length === batchSize) {
|
|
728
|
+
yield batch;
|
|
729
|
+
batch = [];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (batch.length > 0) yield batch;
|
|
733
|
+
} finally {
|
|
734
|
+
stream.destroy();
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function buildStrictCandidateFilter(filter, id, versionField, expectedVersion) {
|
|
738
|
+
return { $and: [filter ?? {}, { _id: id }, { [versionField]: expectedVersion }] };
|
|
739
|
+
}
|
|
740
|
+
async function isModelVersionConflict(context, filter, id, versionField, expectedVersion, options) {
|
|
741
|
+
const current = await context.collection.findOne(
|
|
742
|
+
{ $and: [filter ?? {}, { _id: id }] },
|
|
743
|
+
buildModelVersionLookupOptions(options, { [versionField]: 1 })
|
|
744
|
+
);
|
|
745
|
+
return current !== null && current !== void 0 && current[versionField] !== expectedVersion;
|
|
746
|
+
}
|
|
720
747
|
var VERSION_LOOKUP_OPTION_KEYS = [
|
|
721
748
|
"session",
|
|
722
749
|
"readConcern",
|
|
@@ -802,11 +829,14 @@ function mapModelSchemaValidationErrors(errors = []) {
|
|
|
802
829
|
}
|
|
803
830
|
function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
804
831
|
const shouldValidate = context.validateEnabled || options?.validate === true;
|
|
805
|
-
if (!shouldValidate) {
|
|
832
|
+
if (!shouldValidate || options?.skipValidation) {
|
|
806
833
|
return document;
|
|
807
834
|
}
|
|
808
|
-
if (
|
|
809
|
-
|
|
835
|
+
if (context.schemaError) {
|
|
836
|
+
throw withModelErrorMetadata(
|
|
837
|
+
createError(ErrorCodes.VALIDATION_ERROR, `Schema initialization failed: ${context.schemaError.message}`),
|
|
838
|
+
{ errors: [{ field: "_schema", message: context.schemaError.message }], ...metadata }
|
|
839
|
+
);
|
|
810
840
|
}
|
|
811
841
|
if (!context.schemaCache || !context.schemaValidateFn) {
|
|
812
842
|
return document;
|
|
@@ -840,22 +870,31 @@ function validateModelSchemaPayload(context, document, options, metadata = {}) {
|
|
|
840
870
|
}
|
|
841
871
|
return normalized;
|
|
842
872
|
}
|
|
873
|
+
function buildModelVisibleSoftDeleteCondition(config) {
|
|
874
|
+
return { [config.field]: config.type === "boolean" ? { $ne: true } : null };
|
|
875
|
+
}
|
|
843
876
|
function applyModelSoftDeleteFilter(query, options, softDeleteConfig) {
|
|
844
877
|
if (!softDeleteConfig?.enabled) {
|
|
845
878
|
return query ?? {};
|
|
846
879
|
}
|
|
847
880
|
const resolvedOptions = options ?? {};
|
|
848
881
|
const resolvedQuery = query ?? {};
|
|
849
|
-
if (
|
|
882
|
+
if (resolvedOptions.withDeleted !== void 0 && typeof resolvedOptions.withDeleted !== "boolean") {
|
|
883
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "withDeleted must be a boolean.");
|
|
884
|
+
}
|
|
885
|
+
if (resolvedOptions.onlyDeleted !== void 0 && typeof resolvedOptions.onlyDeleted !== "boolean") {
|
|
886
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "onlyDeleted must be a boolean.");
|
|
887
|
+
}
|
|
888
|
+
if (Object.prototype.hasOwnProperty.call(resolvedQuery, softDeleteConfig.field)) {
|
|
850
889
|
return resolvedQuery;
|
|
851
890
|
}
|
|
852
891
|
if (resolvedOptions.withDeleted) {
|
|
853
892
|
return resolvedQuery;
|
|
854
893
|
}
|
|
855
894
|
if (resolvedOptions.onlyDeleted) {
|
|
856
|
-
return { ...resolvedQuery, [softDeleteConfig.field]: { $ne: null } };
|
|
895
|
+
return { ...resolvedQuery, [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : { $ne: null } };
|
|
857
896
|
}
|
|
858
|
-
return { ...resolvedQuery,
|
|
897
|
+
return { ...resolvedQuery, ...buildModelVisibleSoftDeleteCondition(softDeleteConfig) };
|
|
859
898
|
}
|
|
860
899
|
function applyModelInsertTimestamps(document, timestampsConfig, nowFactory) {
|
|
861
900
|
if (!timestampsConfig) {
|
|
@@ -1150,7 +1189,9 @@ function projectPopulatedDocument(document, select, nestedPaths) {
|
|
|
1150
1189
|
return document;
|
|
1151
1190
|
}
|
|
1152
1191
|
const nestedRelationFields = nestedPaths.map((path5) => normalizePopulateConfig(path5).path);
|
|
1153
|
-
|
|
1192
|
+
const projected = pickFields(document, [.../* @__PURE__ */ new Set([...select, ...nestedRelationFields])]);
|
|
1193
|
+
if (!select.includes("_id")) delete projected._id;
|
|
1194
|
+
return projected;
|
|
1154
1195
|
}
|
|
1155
1196
|
async function populateModelPath(context, docs, path5, traversalState) {
|
|
1156
1197
|
const config = normalizePopulateConfig(path5);
|
|
@@ -1172,7 +1213,13 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1172
1213
|
}
|
|
1173
1214
|
const localValues = docs.map((doc) => getByPath(doc, relation.localField));
|
|
1174
1215
|
const keys = unique(localValues.flatMap((value) => flattenComparableValues(value)));
|
|
1175
|
-
if (
|
|
1216
|
+
if (config.limit !== void 0 && (!Number.isSafeInteger(config.limit) || config.limit < 0)) {
|
|
1217
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "populate limit must be a non-negative integer");
|
|
1218
|
+
}
|
|
1219
|
+
if (config.skip !== void 0 && (!Number.isSafeInteger(config.skip) || config.skip < 0)) {
|
|
1220
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "populate skip must be a non-negative integer");
|
|
1221
|
+
}
|
|
1222
|
+
if (keys.length === 0 || config.limit === 0) {
|
|
1176
1223
|
for (const doc of docs) {
|
|
1177
1224
|
doc[config.path] = relation.single ? null : [];
|
|
1178
1225
|
}
|
|
@@ -1192,36 +1239,63 @@ async function populateModelPath(context, docs, path5, traversalState) {
|
|
|
1192
1239
|
...effectiveSelect,
|
|
1193
1240
|
relation.foreignField,
|
|
1194
1241
|
"_id",
|
|
1195
|
-
...
|
|
1242
|
+
...Object.keys(config.sort ?? {}),
|
|
1243
|
+
...collectNestedLocalFields(relatedModel, nestedPaths),
|
|
1244
|
+
...relatedModel?.softDeleteConfig?.enabled ? [relatedModel.softDeleteConfig.field] : []
|
|
1196
1245
|
])] : [];
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
...config.
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1246
|
+
const readOptions = {
|
|
1247
|
+
...effectiveSelect ? { projection: Object.fromEntries(projectionFields.map((field) => [field, 1])) } : {},
|
|
1248
|
+
...config.sort ? { sort: config.sort } : {}
|
|
1249
|
+
};
|
|
1250
|
+
const visibilityMatch = applyModelSoftDeleteFilter(config.match ?? {}, {}, relatedModel?.softDeleteConfig ?? null);
|
|
1251
|
+
const readForKeys = (values, options = readOptions) => {
|
|
1252
|
+
const keyFilter = { [relation.foreignField]: { $in: values } };
|
|
1253
|
+
return readPopulateRows(
|
|
1254
|
+
relatedCollection,
|
|
1255
|
+
Object.keys(visibilityMatch).length === 0 ? keyFilter : { $and: [keyFilter, visibilityMatch] },
|
|
1256
|
+
options
|
|
1257
|
+
);
|
|
1258
|
+
};
|
|
1259
|
+
const hydrateRelated = async (rows) => {
|
|
1260
|
+
let hydrated = relatedModel ? relatedModel.hydrateDocuments(rows) : rows.map((item) => ({ ...item }));
|
|
1261
|
+
if (nestedPaths.length > 0 && relatedModel) {
|
|
1262
|
+
if (state.depth + 1 > state.maxDepth) {
|
|
1263
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, `populate maxDepth exceeded: ${state.maxDepth}`);
|
|
1264
|
+
}
|
|
1265
|
+
hydrated = await relatedModel.populateDocuments(hydrated, nestedPaths, {
|
|
1266
|
+
depth: state.depth + 1,
|
|
1267
|
+
maxDepth: state.maxDepth,
|
|
1268
|
+
activeConfigs: state.activeConfigs
|
|
1269
|
+
});
|
|
1207
1270
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1271
|
+
return hydrated;
|
|
1272
|
+
};
|
|
1273
|
+
if (config.limit !== void 0 || config.skip !== void 0) {
|
|
1274
|
+
const scopedOptions = {
|
|
1275
|
+
...readOptions,
|
|
1276
|
+
...config.skip !== void 0 ? { skip: config.skip } : {},
|
|
1277
|
+
...config.limit !== void 0 ? { limit: config.limit } : {}
|
|
1278
|
+
};
|
|
1279
|
+
for (const [index, doc] of docs.entries()) {
|
|
1280
|
+
const parentKeys = unique(flattenComparableValues(localValues[index]));
|
|
1281
|
+
const rows = parentKeys.length > 0 ? await readForKeys(parentKeys, scopedOptions) : [];
|
|
1282
|
+
const hydrated = await hydrateRelated(rows);
|
|
1283
|
+
const matches = dedupeDocuments(hydrated);
|
|
1284
|
+
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1285
|
+
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
const rows = [];
|
|
1289
|
+
for (let offset = 0; offset < keys.length; offset += 200) {
|
|
1290
|
+
rows.push(...await readForKeys(keys.slice(offset, offset + 200)));
|
|
1219
1291
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1292
|
+
const hydrated = await hydrateRelated(rows);
|
|
1293
|
+
const grouped = groupBy(hydrated, (item) => getByPath(item, relation.foreignField));
|
|
1294
|
+
for (const [index, doc] of docs.entries()) {
|
|
1295
|
+
const matches = rebuildPopulateMatches(localValues[index], grouped, config.sort);
|
|
1296
|
+
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1297
|
+
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1222
1298
|
}
|
|
1223
|
-
const projectedMatches = matches.map((item) => projectPopulatedDocument(item, effectiveSelect, nestedPaths));
|
|
1224
|
-
doc[config.path] = relation.single ? projectedMatches[0] ?? null : projectedMatches;
|
|
1225
1299
|
}
|
|
1226
1300
|
return docs;
|
|
1227
1301
|
} finally {
|
|
@@ -1422,12 +1496,28 @@ async function saveModelDocument(collection, document, options = {}) {
|
|
|
1422
1496
|
document._id = result.insertedId;
|
|
1423
1497
|
return document;
|
|
1424
1498
|
}
|
|
1425
|
-
async function removeModelDocument(collection, document) {
|
|
1499
|
+
async function removeModelDocument(collection, document, deleteOne = (filter) => collection.deleteOne(filter)) {
|
|
1426
1500
|
if (document._id === void 0) {
|
|
1427
1501
|
return false;
|
|
1428
1502
|
}
|
|
1429
|
-
const result = await
|
|
1430
|
-
|
|
1503
|
+
const result = await deleteOne({ _id: document._id });
|
|
1504
|
+
if (result.matchedCount === void 0 && result.modifiedCount === void 0 && result.deletedCount === void 0) {
|
|
1505
|
+
return result.acknowledged === true;
|
|
1506
|
+
}
|
|
1507
|
+
return Boolean(result.matchedCount || result.modifiedCount || result.deletedCount);
|
|
1508
|
+
}
|
|
1509
|
+
async function readPopulateRows(collection, query, options) {
|
|
1510
|
+
if (typeof collection.stream !== "function") {
|
|
1511
|
+
return await collection.find(query, options);
|
|
1512
|
+
}
|
|
1513
|
+
const stream = collection.stream(query, options);
|
|
1514
|
+
const rows = [];
|
|
1515
|
+
try {
|
|
1516
|
+
for await (const row of stream) rows.push(row);
|
|
1517
|
+
} finally {
|
|
1518
|
+
stream.destroy();
|
|
1519
|
+
}
|
|
1520
|
+
return rows;
|
|
1431
1521
|
}
|
|
1432
1522
|
|
|
1433
1523
|
// src/capabilities/model/model-soft-delete-helpers.ts
|
|
@@ -1435,7 +1525,53 @@ function deletedFilter(filter, softDeleteConfig) {
|
|
|
1435
1525
|
if (!softDeleteConfig) {
|
|
1436
1526
|
return filter ?? {};
|
|
1437
1527
|
}
|
|
1438
|
-
return
|
|
1528
|
+
return applyModelSoftDeleteFilter(filter, { onlyDeleted: true }, softDeleteConfig);
|
|
1529
|
+
}
|
|
1530
|
+
function filterVisibleBySoftDelete(docs, options, config) {
|
|
1531
|
+
if (!config?.enabled) return docs.filter((doc) => Boolean(doc));
|
|
1532
|
+
const rawOptions = options ?? {};
|
|
1533
|
+
applyModelSoftDeleteFilter({}, rawOptions, config);
|
|
1534
|
+
if (rawOptions.withDeleted) return docs.filter((doc) => Boolean(doc));
|
|
1535
|
+
return docs.filter((doc) => {
|
|
1536
|
+
if (!doc) return false;
|
|
1537
|
+
const value = doc[config.field];
|
|
1538
|
+
const deleted = config.type === "boolean" ? value === true : value !== void 0 && value !== null;
|
|
1539
|
+
return rawOptions.onlyDeleted ? deleted : !deleted;
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
function withSoftDeleteProjection(options, config) {
|
|
1543
|
+
if (!config?.enabled) return { options, stripField: false };
|
|
1544
|
+
const rawOptions = options ?? {};
|
|
1545
|
+
const field = config.field;
|
|
1546
|
+
const projection = rawOptions.projection ?? rawOptions.project;
|
|
1547
|
+
if (Array.isArray(projection)) {
|
|
1548
|
+
if (projection.includes(field)) return { options, stripField: false };
|
|
1549
|
+
return { options: { ...rawOptions, projection: [...projection, field] }, stripField: true };
|
|
1550
|
+
}
|
|
1551
|
+
if (!projection || typeof projection !== "object") return { options, stripField: false };
|
|
1552
|
+
const projectionRecord = projection;
|
|
1553
|
+
if (projectionRecord[field] === 1 || projectionRecord[field] === true) return { options, stripField: false };
|
|
1554
|
+
const inclusion = Object.entries(projectionRecord).some(([key, value]) => key !== "_id" && (value === 1 || value === true));
|
|
1555
|
+
const nextProjection = { ...projectionRecord };
|
|
1556
|
+
if (inclusion) nextProjection[field] = 1;
|
|
1557
|
+
else delete nextProjection[field];
|
|
1558
|
+
return { options: { ...rawOptions, projection: nextProjection }, stripField: true };
|
|
1559
|
+
}
|
|
1560
|
+
function applySoftDeleteFindPageOptions(options, config) {
|
|
1561
|
+
const rawOptions = options ?? {};
|
|
1562
|
+
return { ...rawOptions, query: applyModelSoftDeleteFilter(rawOptions.query, rawOptions, config) };
|
|
1563
|
+
}
|
|
1564
|
+
function applySoftDeleteAggregatePipeline(pipeline, options, config) {
|
|
1565
|
+
if (!config?.enabled) return pipeline ?? [];
|
|
1566
|
+
const rawOptions = options ?? {};
|
|
1567
|
+
if (rawOptions.withDeleted) return pipeline ?? [];
|
|
1568
|
+
const softDeleteMatch = applyModelSoftDeleteFilter({}, rawOptions, config);
|
|
1569
|
+
const matchStage = { $match: softDeleteMatch };
|
|
1570
|
+
const stages = [...pipeline ?? []];
|
|
1571
|
+
if (stages.length > 0 && stages[0] && typeof stages[0] === "object" && "$geoNear" in stages[0]) {
|
|
1572
|
+
return [stages[0], matchStage, ...stages.slice(1)];
|
|
1573
|
+
}
|
|
1574
|
+
return [matchStage, ...stages];
|
|
1439
1575
|
}
|
|
1440
1576
|
function findWithDeletedDocuments(context, query, options) {
|
|
1441
1577
|
return new PopulatePromise(async (paths) => {
|
|
@@ -1478,7 +1614,7 @@ function restoreSoftDeletedDocuments(context, filter, options) {
|
|
|
1478
1614
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1479
1615
|
}
|
|
1480
1616
|
return context.collection.updateOne(
|
|
1481
|
-
|
|
1617
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1482
1618
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1483
1619
|
options
|
|
1484
1620
|
);
|
|
@@ -1489,7 +1625,7 @@ function restoreManySoftDeletedDocuments(context, filter, options) {
|
|
|
1489
1625
|
return Promise.resolve({ modifiedCount: 0 });
|
|
1490
1626
|
}
|
|
1491
1627
|
return context.collection.updateMany(
|
|
1492
|
-
|
|
1628
|
+
deletedFilter(filter, softDeleteConfig),
|
|
1493
1629
|
{ $unset: { [softDeleteConfig.field]: 1 } },
|
|
1494
1630
|
options
|
|
1495
1631
|
);
|
|
@@ -3105,6 +3241,9 @@ var modelWriteSourceStorage = new AsyncLocalStorage();
|
|
|
3105
3241
|
function isPlainObject2(value) {
|
|
3106
3242
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3107
3243
|
}
|
|
3244
|
+
function ownValue(record, key) {
|
|
3245
|
+
return Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
3246
|
+
}
|
|
3108
3247
|
function invalidConfig(path5, reason) {
|
|
3109
3248
|
throw createError(
|
|
3110
3249
|
ErrorCodes.INVALID_OPERATION,
|
|
@@ -3125,19 +3264,19 @@ function normalizeRule(input, path5) {
|
|
|
3125
3264
|
if (!isPlainObject2(input)) {
|
|
3126
3265
|
invalidConfig(path5, "rule must be a string mode or an object");
|
|
3127
3266
|
}
|
|
3128
|
-
const mode = input
|
|
3267
|
+
const mode = ownValue(input, "mode") ?? DEFAULT_RULE.mode;
|
|
3129
3268
|
if (!ALLOWED_MODES.has(mode)) {
|
|
3130
3269
|
invalidConfig(`${path5}.mode`, 'must be "allow-both" or "model-only"');
|
|
3131
3270
|
}
|
|
3132
|
-
const raw = input
|
|
3271
|
+
const raw = ownValue(input, "raw") ?? DEFAULT_RULE.raw;
|
|
3133
3272
|
if (!ALLOWED_RAW.has(raw)) {
|
|
3134
3273
|
invalidConfig(`${path5}.raw`, 'must be "inherit", "allow", or "block"');
|
|
3135
3274
|
}
|
|
3136
|
-
const management = input
|
|
3275
|
+
const management = ownValue(input, "management") ?? DEFAULT_RULE.management;
|
|
3137
3276
|
if (!ALLOWED_MANAGEMENT.has(management)) {
|
|
3138
3277
|
invalidConfig(`${path5}.management`, 'must be "inherit", "allow", or "block"');
|
|
3139
3278
|
}
|
|
3140
|
-
const onViolation = input
|
|
3279
|
+
const onViolation = ownValue(input, "onViolation") ?? DEFAULT_RULE.onViolation;
|
|
3141
3280
|
if (!ALLOWED_VIOLATION_ACTIONS.has(onViolation)) {
|
|
3142
3281
|
invalidConfig(`${path5}.onViolation`, 'must be "throw" or "warn"');
|
|
3143
3282
|
}
|
|
@@ -3153,15 +3292,15 @@ function validateWritePathPolicyConfig(options) {
|
|
|
3153
3292
|
}
|
|
3154
3293
|
function normalizeWritePathPolicy(options) {
|
|
3155
3294
|
if (options === void 0) {
|
|
3156
|
-
return { default: { ...DEFAULT_RULE }, namespaces:
|
|
3295
|
+
return { default: { ...DEFAULT_RULE }, namespaces: /* @__PURE__ */ Object.create(null), enabled: false };
|
|
3157
3296
|
}
|
|
3158
3297
|
if (!isPlainObject2(options)) {
|
|
3159
3298
|
invalidConfig("writePathPolicy", "must be an object");
|
|
3160
3299
|
}
|
|
3161
3300
|
const typedOptions = options;
|
|
3162
|
-
const defaultRule = normalizeRule(typedOptions
|
|
3163
|
-
const namespacesInput = options
|
|
3164
|
-
const namespaces =
|
|
3301
|
+
const defaultRule = normalizeRule(ownValue(typedOptions, "default"), "writePathPolicy.default");
|
|
3302
|
+
const namespacesInput = ownValue(options, "namespaces");
|
|
3303
|
+
const namespaces = /* @__PURE__ */ Object.create(null);
|
|
3165
3304
|
if (namespacesInput !== void 0) {
|
|
3166
3305
|
if (!isPlainObject2(namespacesInput)) {
|
|
3167
3306
|
invalidConfig("writePathPolicy.namespaces", "must be an object");
|
|
@@ -3209,8 +3348,8 @@ function resolveWritePathRule(policy, namespace) {
|
|
|
3209
3348
|
if (candidate === "default") {
|
|
3210
3349
|
return { key: "default", rule: effectivePolicy.default };
|
|
3211
3350
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3351
|
+
if (Object.prototype.hasOwnProperty.call(effectivePolicy.namespaces, candidate)) {
|
|
3352
|
+
const rule = effectivePolicy.namespaces[candidate];
|
|
3214
3353
|
return { key: candidate, rule };
|
|
3215
3354
|
}
|
|
3216
3355
|
}
|
|
@@ -3353,16 +3492,17 @@ var fallbackModelIndexTasks = /* @__PURE__ */ new Map();
|
|
|
3353
3492
|
function toCompatDefinition(definition) {
|
|
3354
3493
|
return definition;
|
|
3355
3494
|
}
|
|
3356
|
-
function stableIndexStringify(value) {
|
|
3495
|
+
function stableIndexStringify(value, mode = "options") {
|
|
3357
3496
|
if (value instanceof Date) {
|
|
3358
3497
|
return JSON.stringify(value.toISOString());
|
|
3359
3498
|
}
|
|
3360
3499
|
if (Array.isArray(value)) {
|
|
3361
|
-
return `[${value.map((item) => stableIndexStringify(item)).join(",")}]`;
|
|
3500
|
+
return `[${value.map((item) => stableIndexStringify(item, mode)).join(",")}]`;
|
|
3362
3501
|
}
|
|
3363
3502
|
if (value && typeof value === "object") {
|
|
3364
|
-
const entries = Object.entries(value).filter(([, current]) => current !== void 0)
|
|
3365
|
-
|
|
3503
|
+
const entries = Object.entries(value).filter(([, current]) => current !== void 0);
|
|
3504
|
+
if (mode === "options") entries.sort(([left], [right]) => left.localeCompare(right));
|
|
3505
|
+
return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableIndexStringify(current, key === "key" ? "key" : mode)}`).join(",")}}`;
|
|
3366
3506
|
}
|
|
3367
3507
|
return JSON.stringify(value) ?? "undefined";
|
|
3368
3508
|
}
|
|
@@ -3396,7 +3536,7 @@ function declaredOptionEntries(options) {
|
|
|
3396
3536
|
});
|
|
3397
3537
|
}
|
|
3398
3538
|
function indexOptionsMatch(existing, declared) {
|
|
3399
|
-
if (stableIndexStringify(getExistingIndexKey(existing)) !== stableIndexStringify(declared.key)) {
|
|
3539
|
+
if (stableIndexStringify(getExistingIndexKey(existing), "key") !== stableIndexStringify(declared.key, "key")) {
|
|
3400
3540
|
return false;
|
|
3401
3541
|
}
|
|
3402
3542
|
for (const [name, value] of declaredOptionEntries(declared.options)) {
|
|
@@ -3415,8 +3555,8 @@ function findExistingIndexByName(existingIndexes, name) {
|
|
|
3415
3555
|
return existingIndexes.find((index) => index.name === name);
|
|
3416
3556
|
}
|
|
3417
3557
|
function findExistingIndexByKey(existingIndexes, key) {
|
|
3418
|
-
const fingerprint = stableIndexStringify(key);
|
|
3419
|
-
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index)) === fingerprint);
|
|
3558
|
+
const fingerprint = stableIndexStringify(key, "key");
|
|
3559
|
+
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index), "key") === fingerprint);
|
|
3420
3560
|
}
|
|
3421
3561
|
function createIndexEnsureError(message, result, cause) {
|
|
3422
3562
|
return createError(ErrorCodes.MONGODB_ERROR, message, [result], cause);
|
|
@@ -4156,7 +4296,7 @@ async function readNearestBookmark(cache, namespace, keyDims, targetPage) {
|
|
|
4156
4296
|
|
|
4157
4297
|
// src/adapters/mongodb/queries/query-helpers.ts
|
|
4158
4298
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
4159
|
-
import { ObjectId as ObjectId2 } from "mongodb";
|
|
4299
|
+
import { BSON, ObjectId as ObjectId2 } from "mongodb";
|
|
4160
4300
|
|
|
4161
4301
|
// src/utils/normalize.ts
|
|
4162
4302
|
function normalizeProjection(p) {
|
|
@@ -4254,12 +4394,17 @@ function shouldConvertQueryField(autoConvert, path5, depth) {
|
|
|
4254
4394
|
if (!config.enabled || depth > config.maxDepth) return false;
|
|
4255
4395
|
return shouldConvertObjectIdPath(path5, config);
|
|
4256
4396
|
}
|
|
4397
|
+
function isPlainQueryRecord(value) {
|
|
4398
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
4399
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4400
|
+
return prototype === Object.prototype || prototype === null;
|
|
4401
|
+
}
|
|
4257
4402
|
function normalizeQueryArray(value, autoConvert, path5, depth) {
|
|
4258
4403
|
return value.map((item, index) => {
|
|
4259
4404
|
if (typeof item === "string" && item.length === 24 && ObjectId2.isValid(item) && shouldConvertQueryField(autoConvert, path5, depth)) {
|
|
4260
4405
|
return new ObjectId2(item);
|
|
4261
4406
|
}
|
|
4262
|
-
if (
|
|
4407
|
+
if (isPlainQueryRecord(item)) {
|
|
4263
4408
|
return normalizeQueryFilter(item, autoConvert, `${path5}[${index}]`, depth + 1);
|
|
4264
4409
|
}
|
|
4265
4410
|
return item;
|
|
@@ -4275,7 +4420,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4275
4420
|
}
|
|
4276
4421
|
for (const [key, value] of Object.entries(filter)) {
|
|
4277
4422
|
if (key === "$and" || key === "$or" || key === "$nor") {
|
|
4278
|
-
result[key] = Array.isArray(value) ? value.map((item) => item
|
|
4423
|
+
result[key] = Array.isArray(value) ? value.map((item) => isPlainQueryRecord(item) ? normalizeQueryFilter(item, autoConvert, fieldPath, depth + 1) : item) : value;
|
|
4279
4424
|
continue;
|
|
4280
4425
|
}
|
|
4281
4426
|
const currentPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
@@ -4284,7 +4429,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4284
4429
|
result[key] = shouldConvert ? new ObjectId2(value) : value;
|
|
4285
4430
|
} else if (Array.isArray(value)) {
|
|
4286
4431
|
result[key] = shouldConvert ? normalizeQueryArray(value, autoConvert, currentPath, depth + 1) : value;
|
|
4287
|
-
} else if (
|
|
4432
|
+
} else if (isPlainQueryRecord(value)) {
|
|
4288
4433
|
const nested = value;
|
|
4289
4434
|
const hasOperators = Object.keys(nested).some((k) => k.startsWith("$"));
|
|
4290
4435
|
if (hasOperators) {
|
|
@@ -4296,7 +4441,7 @@ function normalizeQueryFilter(filter, autoConvert, fieldPath = "", depth = 0) {
|
|
|
4296
4441
|
);
|
|
4297
4442
|
} else if (shouldConvert && QUERY_OBJECTID_SCALAR_OPERATORS.has(op) && typeof opVal === "string" && opVal.length === 24 && ObjectId2.isValid(opVal)) {
|
|
4298
4443
|
nestedResult[op] = new ObjectId2(opVal);
|
|
4299
|
-
} else if (op === "$elemMatch" &&
|
|
4444
|
+
} else if (op === "$elemMatch" && isPlainQueryRecord(opVal)) {
|
|
4300
4445
|
nestedResult[op] = normalizeQueryFilter(opVal, autoConvert, currentPath, depth + 1);
|
|
4301
4446
|
} else {
|
|
4302
4447
|
nestedResult[op] = opVal;
|
|
@@ -4470,36 +4615,29 @@ async function isCollectionCacheBarrierActive(queryCache, collection, defaults =
|
|
|
4470
4615
|
buildCollectionCacheBarrierNamespaces(collection, defaults)
|
|
4471
4616
|
);
|
|
4472
4617
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
if (value
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
if (value instanceof
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
}
|
|
4497
|
-
return sorted;
|
|
4498
|
-
}
|
|
4499
|
-
return value;
|
|
4500
|
-
}
|
|
4501
|
-
function stableCacheKeyString(value) {
|
|
4502
|
-
return JSON.stringify(normalizeCacheKeyValue(value));
|
|
4618
|
+
var ORDERED_OPTION_FIELDS = /* @__PURE__ */ new Set(["query", "filter", "pipeline", "sort", "hint", "projection", "min", "max"]);
|
|
4619
|
+
function normalizeCacheKeyValue(value, mode) {
|
|
4620
|
+
if (value === null) return ["null"];
|
|
4621
|
+
if (value === void 0) return ["undefined"];
|
|
4622
|
+
if (typeof value === "number") return ["number", String(value)];
|
|
4623
|
+
if (typeof value === "bigint") return ["bigint", value.toString()];
|
|
4624
|
+
if (typeof value !== "object") return [typeof value, value];
|
|
4625
|
+
if (value instanceof Date) return ["date", String(value.getTime())];
|
|
4626
|
+
if (value instanceof RegExp) return ["regexp", value.source, value.flags];
|
|
4627
|
+
if (Array.isArray(value)) return ["array", value.map((item) => normalizeCacheKeyValue(item, mode))];
|
|
4628
|
+
const bsonType = value._bsontype;
|
|
4629
|
+
if (typeof bsonType === "string") {
|
|
4630
|
+
return ["bson", bsonType, BSON.EJSON.serialize(value, { relaxed: false })];
|
|
4631
|
+
}
|
|
4632
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0 && typeof item !== "function" && typeof item !== "symbol");
|
|
4633
|
+
if (mode === "options") entries.sort(([left], [right]) => left.localeCompare(right));
|
|
4634
|
+
return ["object", entries.map(([key, item]) => [
|
|
4635
|
+
key,
|
|
4636
|
+
normalizeCacheKeyValue(item, mode === "options" && ORDERED_OPTION_FIELDS.has(key) ? "document" : mode)
|
|
4637
|
+
])];
|
|
4638
|
+
}
|
|
4639
|
+
function stableCacheKeyString(value, mode = "options") {
|
|
4640
|
+
return `v2:${JSON.stringify(normalizeCacheKeyValue(value, mode))}`;
|
|
4503
4641
|
}
|
|
4504
4642
|
function buildResultCacheKeyOptions(options = {}) {
|
|
4505
4643
|
const keyOptions = {};
|
|
@@ -4620,7 +4758,7 @@ function buildFindCacheKey(collection, defaults = {}, query, options) {
|
|
|
4620
4758
|
...options ?? {}
|
|
4621
4759
|
});
|
|
4622
4760
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4623
|
-
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4761
|
+
return `find:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4624
4762
|
}
|
|
4625
4763
|
function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
4626
4764
|
const executeOptions = normalizeFindProjectionOptions({
|
|
@@ -4628,7 +4766,7 @@ function buildFindOneCacheKey(collection, defaults = {}, query, options) {
|
|
|
4628
4766
|
...options ?? {}
|
|
4629
4767
|
});
|
|
4630
4768
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4631
|
-
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4769
|
+
return `findOne:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4632
4770
|
}
|
|
4633
4771
|
function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
4634
4772
|
const merged = {
|
|
@@ -4638,7 +4776,7 @@ function buildCountCacheKey(collection, defaults = {}, query, options) {
|
|
|
4638
4776
|
const { cache: _cache, ...keyOptions } = merged;
|
|
4639
4777
|
void _cache;
|
|
4640
4778
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4641
|
-
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4779
|
+
return `count:${namespace}:${stableCacheKeyString(normalizeQueryForCache(query, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(keyOptions))}`;
|
|
4642
4780
|
}
|
|
4643
4781
|
function buildDistinctCacheKey(collection, defaults = {}, field, query, options) {
|
|
4644
4782
|
const rawOptions = options ?? {};
|
|
@@ -4668,7 +4806,7 @@ function buildAggregateCacheKey(collection, defaults = {}, pipeline, options) {
|
|
|
4668
4806
|
...options ?? {}
|
|
4669
4807
|
};
|
|
4670
4808
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4671
|
-
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults))}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4809
|
+
return `aggregate:${namespace}:${stableCacheKeyString(normalizePipelineForCache(pipeline, defaults), "document")}:${stableCacheKeyString(buildResultCacheKeyOptions(executeOptions))}`;
|
|
4672
4810
|
}
|
|
4673
4811
|
function buildFindOneByIdCacheKey(collection, defaults = {}, id, options) {
|
|
4674
4812
|
const objectId = parseRequiredObjectId(id);
|
|
@@ -4683,6 +4821,8 @@ function buildFindByIdsCacheKey(collection, defaults = {}, ids, options) {
|
|
|
4683
4821
|
function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
4684
4822
|
const ext = options;
|
|
4685
4823
|
const cursorTypes = ext.cursorTypes && typeof ext.cursorTypes === "object" && !Array.isArray(ext.cursorTypes) ? ext.cursorTypes : void 0;
|
|
4824
|
+
const { let: nestedLet, ...nestedOptions } = options.options ?? {};
|
|
4825
|
+
const effectiveLet = ext.let !== void 0 ? ext.let : nestedLet;
|
|
4686
4826
|
const payload = {
|
|
4687
4827
|
query: normalizeQueryForCache(options.query, defaults),
|
|
4688
4828
|
sort: normalizeSortShape(options.sort),
|
|
@@ -4704,9 +4844,10 @@ function buildFindPageCacheKey(collection, defaults = {}, options = {}) {
|
|
|
4704
4844
|
hint: ext.hint,
|
|
4705
4845
|
collation: ext.collation,
|
|
4706
4846
|
batchSize: ext.batchSize,
|
|
4707
|
-
|
|
4847
|
+
let: effectiveLet,
|
|
4848
|
+
options: nestedOptions
|
|
4708
4849
|
};
|
|
4709
|
-
const keyHash = hashPayload(payload);
|
|
4850
|
+
const keyHash = hashPayload(stableCacheKeyString(payload));
|
|
4710
4851
|
const namespace = buildCollectionCacheNamespace(collection, defaults);
|
|
4711
4852
|
return { key: `findPage:${namespace}:${keyHash}`, keyHash };
|
|
4712
4853
|
}
|
|
@@ -5053,29 +5194,30 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5053
5194
|
if (rawOptions.upsert === true) {
|
|
5054
5195
|
throw createError(ErrorCodes.INVALID_ARGUMENT, 'versionMode "strict" does not support upsert.');
|
|
5055
5196
|
}
|
|
5056
|
-
const docs = await context.collection.find(filter, {
|
|
5057
|
-
...buildModelVersionLookupOptions(rawOptions, {
|
|
5058
|
-
_id: 1,
|
|
5059
|
-
[context.versionConfig.field]: 1
|
|
5060
|
-
})
|
|
5061
|
-
});
|
|
5062
5197
|
let matchedCount = 0;
|
|
5063
5198
|
let modifiedCount = 0;
|
|
5064
5199
|
const conflictedIds = [];
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
const
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5200
|
+
const skippedIds = [];
|
|
5201
|
+
for await (const batch of iterateModelCandidateBatches(context, filter, rawOptions, 200)) {
|
|
5202
|
+
for (const doc of batch) {
|
|
5203
|
+
const id = doc._id;
|
|
5204
|
+
const expectedVersion = assertNumericExpectedVersion(doc[context.versionConfig.field], "updateMany");
|
|
5205
|
+
const result = await context.collection.updateOne(
|
|
5206
|
+
buildStrictCandidateFilter(filter, id, context.versionConfig.field, expectedVersion),
|
|
5207
|
+
update,
|
|
5208
|
+
options
|
|
5209
|
+
);
|
|
5210
|
+
if ((result?.matchedCount ?? 0) === 0) {
|
|
5211
|
+
if (await isModelVersionConflict(context, filter, id, context.versionConfig.field, expectedVersion, options)) {
|
|
5212
|
+
conflictedIds.push(id);
|
|
5213
|
+
} else {
|
|
5214
|
+
skippedIds.push(id);
|
|
5215
|
+
}
|
|
5216
|
+
continue;
|
|
5217
|
+
}
|
|
5218
|
+
matchedCount += result.matchedCount ?? 0;
|
|
5219
|
+
modifiedCount += result.modifiedCount ?? 0;
|
|
5076
5220
|
}
|
|
5077
|
-
matchedCount += result.matchedCount ?? 0;
|
|
5078
|
-
modifiedCount += result.modifiedCount ?? 0;
|
|
5079
5221
|
}
|
|
5080
5222
|
return {
|
|
5081
5223
|
acknowledged: true,
|
|
@@ -5084,7 +5226,9 @@ async function runStrictUpdateMany(context, filter, update, options) {
|
|
|
5084
5226
|
upsertedCount: 0,
|
|
5085
5227
|
upsertedId: null,
|
|
5086
5228
|
conflictCount: conflictedIds.length,
|
|
5087
|
-
conflictedIds
|
|
5229
|
+
conflictedIds,
|
|
5230
|
+
skippedCount: skippedIds.length,
|
|
5231
|
+
skippedIds
|
|
5088
5232
|
};
|
|
5089
5233
|
}
|
|
5090
5234
|
async function runStrictUpdateBatch(context, filter, update, options) {
|
|
@@ -5107,7 +5251,7 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5107
5251
|
retryAttempts: _retryAttempts,
|
|
5108
5252
|
retryDelay: _retryDelay,
|
|
5109
5253
|
onRetry: _onRetry,
|
|
5110
|
-
sort
|
|
5254
|
+
sort: _sort,
|
|
5111
5255
|
...driverOptions
|
|
5112
5256
|
} = rawOptions;
|
|
5113
5257
|
void _batchSize;
|
|
@@ -5116,38 +5260,42 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5116
5260
|
void _retryAttempts;
|
|
5117
5261
|
void _retryDelay;
|
|
5118
5262
|
void _onRetry;
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
}),
|
|
5124
|
-
sort
|
|
5125
|
-
});
|
|
5263
|
+
void _sort;
|
|
5264
|
+
const countOptions = buildModelVersionLookupOptions(driverOptions, {});
|
|
5265
|
+
delete countOptions.projection;
|
|
5266
|
+
const totalCount = await context.collection.count(filter, countOptions);
|
|
5126
5267
|
const result = {
|
|
5127
5268
|
acknowledged: true,
|
|
5128
|
-
totalCount
|
|
5269
|
+
totalCount,
|
|
5129
5270
|
matchedCount: 0,
|
|
5130
5271
|
modifiedCount: 0,
|
|
5131
5272
|
upsertedCount: 0,
|
|
5132
|
-
batchCount:
|
|
5273
|
+
batchCount: 0,
|
|
5133
5274
|
errors: [],
|
|
5134
5275
|
retries: [],
|
|
5135
5276
|
conflictCount: 0,
|
|
5136
|
-
conflictedIds: []
|
|
5277
|
+
conflictedIds: [],
|
|
5278
|
+
skippedCount: 0,
|
|
5279
|
+
skippedIds: []
|
|
5137
5280
|
};
|
|
5138
|
-
for (
|
|
5139
|
-
|
|
5281
|
+
for await (const batch of iterateModelCandidateBatches(context, filter, driverOptions, batchSize)) {
|
|
5282
|
+
result.batchCount++;
|
|
5140
5283
|
for (const doc of batch) {
|
|
5141
5284
|
const id = doc._id;
|
|
5142
5285
|
const expectedVersion = assertNumericExpectedVersion(doc[context.versionConfig.field], "updateBatch");
|
|
5143
5286
|
const updateResult = await context.collection.updateOne(
|
|
5144
|
-
|
|
5287
|
+
buildStrictCandidateFilter(filter, id, context.versionConfig.field, expectedVersion),
|
|
5145
5288
|
update,
|
|
5146
5289
|
driverOptions
|
|
5147
5290
|
);
|
|
5148
5291
|
if ((updateResult?.matchedCount ?? 0) === 0) {
|
|
5149
|
-
|
|
5150
|
-
|
|
5292
|
+
if (await isModelVersionConflict(context, filter, id, context.versionConfig.field, expectedVersion, driverOptions)) {
|
|
5293
|
+
result.conflictCount = (result.conflictCount ?? 0) + 1;
|
|
5294
|
+
result.conflictedIds?.push(id);
|
|
5295
|
+
} else {
|
|
5296
|
+
result.skippedCount = (result.skippedCount ?? 0) + 1;
|
|
5297
|
+
result.skippedIds?.push(id);
|
|
5298
|
+
}
|
|
5151
5299
|
continue;
|
|
5152
5300
|
}
|
|
5153
5301
|
result.matchedCount += updateResult.matchedCount ?? 0;
|
|
@@ -5155,15 +5303,16 @@ async function runStrictUpdateBatch(context, filter, update, options) {
|
|
|
5155
5303
|
}
|
|
5156
5304
|
if (typeof onProgress === "function") {
|
|
5157
5305
|
onProgress({
|
|
5158
|
-
currentBatch:
|
|
5159
|
-
totalBatches:
|
|
5306
|
+
currentBatch: result.batchCount,
|
|
5307
|
+
totalBatches: Math.ceil(totalCount / batchSize),
|
|
5160
5308
|
modified: result.modifiedCount,
|
|
5161
5309
|
matched: result.matchedCount,
|
|
5162
|
-
total:
|
|
5163
|
-
percentage:
|
|
5310
|
+
total: totalCount,
|
|
5311
|
+
percentage: totalCount > 0 ? Math.round(result.modifiedCount / totalCount * 100) : null,
|
|
5164
5312
|
errors: result.errors.length,
|
|
5165
5313
|
retries: result.retries.length,
|
|
5166
|
-
conflicts: result.conflictCount ?? 0
|
|
5314
|
+
conflicts: result.conflictCount ?? 0,
|
|
5315
|
+
skipped: result.skippedCount ?? 0
|
|
5167
5316
|
});
|
|
5168
5317
|
}
|
|
5169
5318
|
}
|
|
@@ -5176,10 +5325,7 @@ function buildSoftDeletePatch(config, now) {
|
|
|
5176
5325
|
return { [config.field]: config.type === "boolean" ? true : now };
|
|
5177
5326
|
}
|
|
5178
5327
|
function buildSoftDeleteFilter(filter, config) {
|
|
5179
|
-
return {
|
|
5180
|
-
...filter ?? {},
|
|
5181
|
-
...config?.enabled ? { [config.field]: null } : {}
|
|
5182
|
-
};
|
|
5328
|
+
return applyModelSoftDeleteFilter(filter, {}, config);
|
|
5183
5329
|
}
|
|
5184
5330
|
function applyModelIncrementVersion(field, increment, versionConfig) {
|
|
5185
5331
|
if (!versionConfig?.enabled || field === void 0) {
|
|
@@ -5219,7 +5365,8 @@ async function orchestrateModelInsertOne(context, document, options) {
|
|
|
5219
5365
|
payload = validateModelSchemaPayload({
|
|
5220
5366
|
validateEnabled: context.validateEnabled,
|
|
5221
5367
|
schemaCache: context.schemaCache,
|
|
5222
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5368
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5369
|
+
schemaError: context.schemaError
|
|
5223
5370
|
}, payload, options);
|
|
5224
5371
|
payload = applyModelInsertTimestamps(payload, context.timestampsConfig, () => context.nowDate());
|
|
5225
5372
|
payload = applyModelInsertVersion(payload, context.versionConfig);
|
|
@@ -5253,7 +5400,8 @@ async function orchestrateModelInsertMany(context, documents, options) {
|
|
|
5253
5400
|
doc = validateModelSchemaPayload({
|
|
5254
5401
|
validateEnabled: context.validateEnabled,
|
|
5255
5402
|
schemaCache: context.schemaCache,
|
|
5256
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5403
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5404
|
+
schemaError: context.schemaError
|
|
5257
5405
|
}, doc, resolvedOptions, { index });
|
|
5258
5406
|
doc = applyModelInsertTimestamps(doc, context.timestampsConfig, () => context.nowDate());
|
|
5259
5407
|
doc = applyModelInsertVersion(doc, context.versionConfig);
|
|
@@ -5338,7 +5486,8 @@ async function orchestrateModelReplaceOne(context, filter, replacement, options)
|
|
|
5338
5486
|
let nextReplacement = validateModelSchemaPayload({
|
|
5339
5487
|
validateEnabled: context.validateEnabled,
|
|
5340
5488
|
schemaCache: context.schemaCache,
|
|
5341
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5489
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5490
|
+
schemaError: context.schemaError
|
|
5342
5491
|
}, replacement, options);
|
|
5343
5492
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5344
5493
|
replacement,
|
|
@@ -5397,7 +5546,8 @@ async function orchestrateModelFindOneAndReplace(context, filter, replacement, o
|
|
|
5397
5546
|
let nextReplacement = validateModelSchemaPayload({
|
|
5398
5547
|
validateEnabled: context.validateEnabled,
|
|
5399
5548
|
schemaCache: context.schemaCache,
|
|
5400
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5549
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5550
|
+
schemaError: context.schemaError
|
|
5401
5551
|
}, replacement, options);
|
|
5402
5552
|
nextReplacement = preserveModelReplaceCreatedAt(
|
|
5403
5553
|
replacement,
|
|
@@ -5509,7 +5659,8 @@ async function orchestrateModelInsertBatch(context, docs, options) {
|
|
|
5509
5659
|
record = validateModelSchemaPayload({
|
|
5510
5660
|
validateEnabled: context.validateEnabled,
|
|
5511
5661
|
schemaCache: context.schemaCache,
|
|
5512
|
-
schemaValidateFn: context.schemaValidateFn
|
|
5662
|
+
schemaValidateFn: context.schemaValidateFn,
|
|
5663
|
+
schemaError: context.schemaError
|
|
5513
5664
|
}, record, resolvedOptions, { index });
|
|
5514
5665
|
record = applyModelInsertTimestamps(record, context.timestampsConfig, () => context.nowDate());
|
|
5515
5666
|
record = applyModelInsertVersion(record, context.versionConfig);
|
|
@@ -5587,7 +5738,7 @@ async function orchestrateModelDeleteOne(context, filter, options) {
|
|
|
5587
5738
|
let result;
|
|
5588
5739
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5589
5740
|
result = await context.collection.updateOne(
|
|
5590
|
-
|
|
5741
|
+
buildSoftDeleteFilter(filter, softDeleteConfig),
|
|
5591
5742
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5592
5743
|
options
|
|
5593
5744
|
);
|
|
@@ -5621,7 +5772,7 @@ async function orchestrateModelDeleteMany(context, filter, options) {
|
|
|
5621
5772
|
let result;
|
|
5622
5773
|
if (softDeleteConfig?.enabled && !resolvedOptions._forceDelete) {
|
|
5623
5774
|
result = await context.collection.updateMany(
|
|
5624
|
-
{
|
|
5775
|
+
{ $and: [filter ?? {}, buildModelVisibleSoftDeleteCondition(softDeleteConfig)] },
|
|
5625
5776
|
{ $set: { [softDeleteConfig.field]: softDeleteConfig.type === "boolean" ? true : context.nowDate() } },
|
|
5626
5777
|
options
|
|
5627
5778
|
);
|
|
@@ -5785,7 +5936,7 @@ async function inspectDeclaredRelationUsage(context, targets, inputOptions, targ
|
|
|
5785
5936
|
[inbound.localField]: { $in: targetValues }
|
|
5786
5937
|
};
|
|
5787
5938
|
const sourceSoftDelete = resolveModelSoftDeleteConfig(inbound.definition);
|
|
5788
|
-
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery,
|
|
5939
|
+
const query = !options.includeSoftDeletedReferences && sourceSoftDelete?.enabled ? { $and: [baseQuery, buildModelVisibleSoftDeleteCondition(sourceSoftDelete)] } : baseQuery;
|
|
5789
5940
|
try {
|
|
5790
5941
|
const collection = context.runtime.scopedCollection(inbound.sourceCollection, scope);
|
|
5791
5942
|
if (!collection) {
|
|
@@ -5967,7 +6118,7 @@ async function executeProtectedRelationDelete(host, filter, options, force) {
|
|
|
5967
6118
|
const deleteOptions = stripRelationUsageOptions(options);
|
|
5968
6119
|
let target;
|
|
5969
6120
|
try {
|
|
5970
|
-
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter :
|
|
6121
|
+
const targetFilter = force || !host.softDeleteConfig?.enabled ? filter : applyModelSoftDeleteFilter(filter, void 0, host.softDeleteConfig);
|
|
5971
6122
|
const rawTarget = await host.collection.findOne(
|
|
5972
6123
|
targetFilter,
|
|
5973
6124
|
relationTargetReadOptions(options, buildRelationUsageTargetProjection(host.modelName))
|
|
@@ -6051,7 +6202,7 @@ var ModelInstance = class {
|
|
|
6051
6202
|
this.softDeleteConfig = this._softDeleteConfig;
|
|
6052
6203
|
this._versionConfig = resolveModelVersionConfig(options.definition);
|
|
6053
6204
|
this._v1HooksFactory = resolveModelHooksFactory(options.definition);
|
|
6054
|
-
scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6205
|
+
if (!this._schemaError) scheduleModelIndexes(this.collection, options.definition, this._softDeleteConfig, {
|
|
6055
6206
|
runtime: this.runtime,
|
|
6056
6207
|
dbName: options.dbName,
|
|
6057
6208
|
poolName: options.poolName,
|
|
@@ -6111,46 +6262,6 @@ var ModelInstance = class {
|
|
|
6111
6262
|
}
|
|
6112
6263
|
return result;
|
|
6113
6264
|
}
|
|
6114
|
-
isVisibleBySoftDelete(doc, options) {
|
|
6115
|
-
if (!doc || !this._softDeleteConfig?.enabled) {
|
|
6116
|
-
return Boolean(doc);
|
|
6117
|
-
}
|
|
6118
|
-
const rawOptions = options ?? {};
|
|
6119
|
-
if (rawOptions.withDeleted) {
|
|
6120
|
-
return true;
|
|
6121
|
-
}
|
|
6122
|
-
const value = doc[this._softDeleteConfig.field];
|
|
6123
|
-
if (rawOptions.onlyDeleted) {
|
|
6124
|
-
return value !== void 0 && value !== null;
|
|
6125
|
-
}
|
|
6126
|
-
return value === void 0 || value === null;
|
|
6127
|
-
}
|
|
6128
|
-
filterVisibleBySoftDelete(docs, options) {
|
|
6129
|
-
return docs.filter((doc) => this.isVisibleBySoftDelete(doc, options));
|
|
6130
|
-
}
|
|
6131
|
-
applySoftDeleteFindPageOptions(options) {
|
|
6132
|
-
const rawOptions = options ?? {};
|
|
6133
|
-
return {
|
|
6134
|
-
...rawOptions,
|
|
6135
|
-
query: applyModelSoftDeleteFilter(rawOptions.query, rawOptions, this._softDeleteConfig)
|
|
6136
|
-
};
|
|
6137
|
-
}
|
|
6138
|
-
applySoftDeleteAggregatePipeline(pipeline, options) {
|
|
6139
|
-
if (!this._softDeleteConfig?.enabled) {
|
|
6140
|
-
return pipeline ?? [];
|
|
6141
|
-
}
|
|
6142
|
-
const rawOptions = options ?? {};
|
|
6143
|
-
if (rawOptions.withDeleted) {
|
|
6144
|
-
return pipeline ?? [];
|
|
6145
|
-
}
|
|
6146
|
-
const softDeleteMatch = applyModelSoftDeleteFilter({}, rawOptions, this._softDeleteConfig);
|
|
6147
|
-
const matchStage = { $match: softDeleteMatch };
|
|
6148
|
-
const stages = [...pipeline ?? []];
|
|
6149
|
-
if (stages.length > 0 && stages[0] && typeof stages[0] === "object" && "$geoNear" in stages[0]) {
|
|
6150
|
-
return [stages[0], matchStage, ...stages.slice(1)];
|
|
6151
|
-
}
|
|
6152
|
-
return [matchStage, ...stages];
|
|
6153
|
-
}
|
|
6154
6265
|
// ── public API ────────────────────────────────────────────────────────────────
|
|
6155
6266
|
find(query, options) {
|
|
6156
6267
|
return new PopulatePromise(async (paths) => {
|
|
@@ -6186,8 +6297,12 @@ var ModelInstance = class {
|
|
|
6186
6297
|
}
|
|
6187
6298
|
findOneById(id, options) {
|
|
6188
6299
|
return new PopulatePromise(async (paths) => {
|
|
6189
|
-
const
|
|
6190
|
-
|
|
6300
|
+
const lookupById = (nextId, nextOptions) => this._softDeleteConfig?.enabled ? this.collection.findOne(
|
|
6301
|
+
applyModelSoftDeleteFilter({ _id: parseRequiredObjectId(nextId) }, nextOptions, this._softDeleteConfig),
|
|
6302
|
+
nextOptions
|
|
6303
|
+
) : this.collection.findOneById(nextId, nextOptions);
|
|
6304
|
+
const doc = this._v1HooksFactory ? await this.runV1HookedOperation("find", [id, options], lookupById) : await lookupById(id, options);
|
|
6305
|
+
return this.populateSingle(this.hydrateDocument(doc), paths);
|
|
6191
6306
|
});
|
|
6192
6307
|
}
|
|
6193
6308
|
findById(id, options) {
|
|
@@ -6195,13 +6310,25 @@ var ModelInstance = class {
|
|
|
6195
6310
|
}
|
|
6196
6311
|
findByIds(ids, options) {
|
|
6197
6312
|
return new PopulatePromise(async (paths) => {
|
|
6198
|
-
|
|
6199
|
-
|
|
6313
|
+
let stripField = false;
|
|
6314
|
+
const lookupByIds = (nextIds, nextOptions) => {
|
|
6315
|
+
const projection = withSoftDeleteProjection(nextOptions, this._softDeleteConfig);
|
|
6316
|
+
stripField = projection.stripField;
|
|
6317
|
+
return this.collection.findByIds(nextIds, projection.options);
|
|
6318
|
+
};
|
|
6319
|
+
const docs = this._v1HooksFactory ? await this.runV1HookedOperation("find", [ids, options], lookupByIds) : await lookupByIds(ids, options);
|
|
6320
|
+
const visible = filterVisibleBySoftDelete(docs, options, this._softDeleteConfig);
|
|
6321
|
+
const projected = stripField && this._softDeleteConfig ? visible.map((doc) => {
|
|
6322
|
+
const copy = { ...doc };
|
|
6323
|
+
delete copy[this._softDeleteConfig.field];
|
|
6324
|
+
return copy;
|
|
6325
|
+
}) : visible;
|
|
6326
|
+
return this.populateDocuments(this.hydrateDocuments(projected), paths);
|
|
6200
6327
|
});
|
|
6201
6328
|
}
|
|
6202
6329
|
findPage(options) {
|
|
6203
6330
|
return new PopulatePromise(async (paths) => {
|
|
6204
|
-
const filteredOptions =
|
|
6331
|
+
const filteredOptions = applySoftDeleteFindPageOptions(options, this._softDeleteConfig);
|
|
6205
6332
|
const result = this._v1HooksFactory ? await this.runV1HookedOperation("find", [filteredOptions], (nextOptions) => this.collection.findPage(nextOptions)) : await this.collection.findPage(filteredOptions);
|
|
6206
6333
|
return {
|
|
6207
6334
|
...result,
|
|
@@ -6353,7 +6480,7 @@ var ModelInstance = class {
|
|
|
6353
6480
|
return this.runV1HookedOperation("find", [key, filteredQuery, options], (nextKey, nextQuery, nextOptions) => this.collection.distinct(nextKey, nextQuery, nextOptions));
|
|
6354
6481
|
}
|
|
6355
6482
|
aggregate(pipeline, options) {
|
|
6356
|
-
const filteredPipeline =
|
|
6483
|
+
const filteredPipeline = applySoftDeleteAggregatePipeline(pipeline, options, this._softDeleteConfig);
|
|
6357
6484
|
return this.runV1HookedOperation("find", [filteredPipeline, options], (nextPipeline, nextOptions) => {
|
|
6358
6485
|
const execute = () => this.collection.aggregate(nextPipeline, nextOptions);
|
|
6359
6486
|
if (resolveAggregateWriteTarget(Array.isArray(nextPipeline) ? nextPipeline : [])) {
|
|
@@ -6482,13 +6609,14 @@ var ModelInstance = class {
|
|
|
6482
6609
|
schemaValidationContext: {
|
|
6483
6610
|
validateEnabled: this._validateEnabled,
|
|
6484
6611
|
schemaCache: this._schemaCache,
|
|
6485
|
-
schemaValidateFn: this._schemaValidateFn
|
|
6612
|
+
schemaValidateFn: this._schemaValidateFn,
|
|
6613
|
+
schemaError: this._schemaError
|
|
6486
6614
|
},
|
|
6487
6615
|
nowFactory: () => this.nowDate()
|
|
6488
6616
|
}));
|
|
6489
6617
|
}
|
|
6490
6618
|
async removeDocument(document) {
|
|
6491
|
-
return this.runModelWrite(() => removeModelDocument(this.collection, document));
|
|
6619
|
+
return this.runModelWrite(() => removeModelDocument(this.collection, document, (filter) => this.deleteOne(filter)));
|
|
6492
6620
|
}
|
|
6493
6621
|
applyDefaults(document) {
|
|
6494
6622
|
return applyModelDefaults(this.definition, document);
|
|
@@ -6506,6 +6634,7 @@ var ModelInstance = class {
|
|
|
6506
6634
|
validateEnabled: this._validateEnabled,
|
|
6507
6635
|
schemaCache: this._schemaCache,
|
|
6508
6636
|
schemaValidateFn: this._schemaValidateFn,
|
|
6637
|
+
schemaError: this._schemaError,
|
|
6509
6638
|
hooksFactory: this._v1HooksFactory,
|
|
6510
6639
|
runHook: (hookName, context) => this.runHook(hookName, context)
|
|
6511
6640
|
};
|
|
@@ -6521,6 +6650,11 @@ var ModelInstance = class {
|
|
|
6521
6650
|
|
|
6522
6651
|
// src/capabilities/transaction/index.ts
|
|
6523
6652
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6653
|
+
function assertManagedTransactionWriteAllowed(options) {
|
|
6654
|
+
const session = options?.session;
|
|
6655
|
+
const managed = session?.__monSQLizeTransaction;
|
|
6656
|
+
if (typeof managed?.assertAcceptingWrites === "function") managed.assertAcceptingWrites();
|
|
6657
|
+
}
|
|
6524
6658
|
function toPublicTransactionStatus(state) {
|
|
6525
6659
|
return state === "active" ? "started" : state;
|
|
6526
6660
|
}
|
|
@@ -6621,6 +6755,7 @@ var Transaction = class {
|
|
|
6621
6755
|
this.options = options;
|
|
6622
6756
|
this.id = `tx_${randomBytes2(8).toString("hex")}`;
|
|
6623
6757
|
this.state = "pending";
|
|
6758
|
+
this.acceptingWrites = false;
|
|
6624
6759
|
this.startedAt = null;
|
|
6625
6760
|
this.timeoutTimer = null;
|
|
6626
6761
|
this.pendingInvalidations = /* @__PURE__ */ new Set();
|
|
@@ -6639,6 +6774,7 @@ var Transaction = class {
|
|
|
6639
6774
|
}
|
|
6640
6775
|
this.session.startTransaction(this.options.transactionOptions);
|
|
6641
6776
|
this.state = "active";
|
|
6777
|
+
this.acceptingWrites = true;
|
|
6642
6778
|
this.startedAt = Date.now();
|
|
6643
6779
|
const timeout = this.options.timeout ?? 3e4;
|
|
6644
6780
|
if (timeout > 0) {
|
|
@@ -6661,6 +6797,7 @@ var Transaction = class {
|
|
|
6661
6797
|
if (this.state !== "active") {
|
|
6662
6798
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
6663
6799
|
}
|
|
6800
|
+
this.acceptingWrites = false;
|
|
6664
6801
|
if (typeof this.session.commitTransaction === "function") {
|
|
6665
6802
|
await commitTransactionWithRetry(this.session, this.options.logger);
|
|
6666
6803
|
}
|
|
@@ -6686,6 +6823,7 @@ var Transaction = class {
|
|
|
6686
6823
|
* @since v1.4.0
|
|
6687
6824
|
*/
|
|
6688
6825
|
async abort() {
|
|
6826
|
+
this.acceptingWrites = false;
|
|
6689
6827
|
if (this.state !== "pending" && this.state !== "active") {
|
|
6690
6828
|
return;
|
|
6691
6829
|
}
|
|
@@ -6710,10 +6848,16 @@ var Transaction = class {
|
|
|
6710
6848
|
* @since v1.4.0
|
|
6711
6849
|
*/
|
|
6712
6850
|
async end() {
|
|
6851
|
+
this.acceptingWrites = false;
|
|
6713
6852
|
this.clearTimeout();
|
|
6714
6853
|
this.options.lockManager?.releaseLocks(this.id);
|
|
6715
6854
|
await this.session.endSession();
|
|
6716
6855
|
}
|
|
6856
|
+
assertAcceptingWrites() {
|
|
6857
|
+
if (this.state !== "active" || !this.acceptingWrites) {
|
|
6858
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Managed transaction ${this.id} is no longer accepting writes.`);
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6717
6861
|
/**
|
|
6718
6862
|
* Record a cache invalidation intent.
|
|
6719
6863
|
* @since v1.4.0
|
|
@@ -6887,12 +7031,12 @@ var TransactionManager = class {
|
|
|
6887
7031
|
return result;
|
|
6888
7032
|
} catch (error) {
|
|
6889
7033
|
lastError = error;
|
|
6890
|
-
await transaction.abort();
|
|
6891
|
-
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
6892
7034
|
if (transaction.state === "committed") {
|
|
6893
7035
|
throw error;
|
|
6894
7036
|
}
|
|
6895
|
-
|
|
7037
|
+
await transaction.abort();
|
|
7038
|
+
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
7039
|
+
if (!enableRetry || attempt === maxRetries || isUnknownTransactionCommitResult(error) || !isTransientTransactionError(error)) {
|
|
6896
7040
|
throw error;
|
|
6897
7041
|
}
|
|
6898
7042
|
await sleep(retryDelay * Math.pow(retryBackoff, attempt));
|
|
@@ -6993,22 +7137,34 @@ function stringifySessionId(id) {
|
|
|
6993
7137
|
}
|
|
6994
7138
|
return String(id);
|
|
6995
7139
|
}
|
|
6996
|
-
function
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7140
|
+
function transactionErrorChain(error) {
|
|
7141
|
+
const chain = [];
|
|
7142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7143
|
+
let current = error;
|
|
7144
|
+
while (current && typeof current === "object" && chain.length < 8 && !seen.has(current)) {
|
|
7145
|
+
seen.add(current);
|
|
7146
|
+
const candidate = current;
|
|
7147
|
+
chain.push(candidate);
|
|
7148
|
+
current = candidate.cause;
|
|
7149
|
+
}
|
|
7150
|
+
return chain;
|
|
7151
|
+
}
|
|
7152
|
+
function hasTransactionErrorLabel(candidate, label) {
|
|
7153
|
+
if (Array.isArray(candidate.errorLabels) && candidate.errorLabels.includes(label)) return true;
|
|
7154
|
+
if (typeof candidate.hasErrorLabel === "function") {
|
|
7155
|
+
try {
|
|
7156
|
+
return Boolean(candidate.hasErrorLabel.call(candidate, label));
|
|
7157
|
+
} catch {
|
|
7158
|
+
}
|
|
7003
7159
|
}
|
|
7004
|
-
return
|
|
7160
|
+
return false;
|
|
7005
7161
|
}
|
|
7006
7162
|
function isUnknownTransactionCommitResult(error) {
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
return
|
|
7163
|
+
return transactionErrorChain(error).some((candidate) => hasTransactionErrorLabel(candidate, "UnknownTransactionCommitResult"));
|
|
7164
|
+
}
|
|
7165
|
+
function isTransientTransactionError(error) {
|
|
7166
|
+
if (isUnknownTransactionCommitResult(error)) return false;
|
|
7167
|
+
return transactionErrorChain(error).some((candidate) => hasTransactionErrorLabel(candidate, "TransientTransactionError") || candidate.code === 112 || candidate.code === 117 || candidate.codeName === "WriteConflict" || candidate.codeName === "LockTimeout");
|
|
7012
7168
|
}
|
|
7013
7169
|
async function commitTransactionWithRetry(session, logger) {
|
|
7014
7170
|
const maxAttempts = 3;
|
|
@@ -7524,26 +7680,27 @@ var CountQueue = class {
|
|
|
7524
7680
|
* @returns The result of fn
|
|
7525
7681
|
* @throws A controlled error when the queue is full or the wait times out
|
|
7526
7682
|
*/
|
|
7527
|
-
async execute(fn) {
|
|
7683
|
+
async execute(fn, options = {}) {
|
|
7528
7684
|
const startTime = Date.now();
|
|
7685
|
+
const signal = options.signal;
|
|
7686
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7529
7687
|
if (this.running >= this.concurrency) {
|
|
7530
7688
|
if (this.queue.length >= this.maxQueueSize) {
|
|
7531
7689
|
this.stats.rejected += 1;
|
|
7532
7690
|
throw createError(ErrorCodes.INVALID_OPERATION, `Count queue is full (${this.maxQueueSize})`);
|
|
7533
7691
|
}
|
|
7534
7692
|
this.stats.queued += 1;
|
|
7535
|
-
await this._waitInQueue(startTime);
|
|
7693
|
+
await this._waitInQueue(startTime, signal);
|
|
7536
7694
|
}
|
|
7695
|
+
if (signal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
7537
7696
|
this.running += 1;
|
|
7538
7697
|
this.stats.executed += 1;
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
return await this._executeWithTimeout(fn, remainingMs);
|
|
7543
|
-
} finally {
|
|
7698
|
+
const elapsed = Date.now() - startTime;
|
|
7699
|
+
const remainingMs = Math.max(1, this.timeout - elapsed);
|
|
7700
|
+
return this._executeWithTimeout(fn, remainingMs, signal, () => {
|
|
7544
7701
|
this.running -= 1;
|
|
7545
7702
|
this._wakeNext();
|
|
7546
|
-
}
|
|
7703
|
+
});
|
|
7547
7704
|
}
|
|
7548
7705
|
/**
|
|
7549
7706
|
* Get queue statistics (including live state).
|
|
@@ -7578,17 +7735,28 @@ var CountQueue = class {
|
|
|
7578
7735
|
const entry = this.queue.shift();
|
|
7579
7736
|
if (entry) {
|
|
7580
7737
|
clearTimeout(entry.timer);
|
|
7738
|
+
entry.cleanup();
|
|
7581
7739
|
entry.reject(createError(ErrorCodes.INVALID_OPERATION, "Count queue was cleared before execution"));
|
|
7582
7740
|
}
|
|
7583
7741
|
}
|
|
7584
7742
|
}
|
|
7585
|
-
_waitInQueue(startTime) {
|
|
7743
|
+
_waitInQueue(startTime, signal) {
|
|
7586
7744
|
return new Promise((resolve, reject) => {
|
|
7587
7745
|
let entry;
|
|
7746
|
+
const onAbort = () => {
|
|
7747
|
+
const index = this.queue.indexOf(entry);
|
|
7748
|
+
if (index !== -1) {
|
|
7749
|
+
this.queue.splice(index, 1);
|
|
7750
|
+
clearTimeout(entry.timer);
|
|
7751
|
+
entry.cleanup();
|
|
7752
|
+
reject(createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted"));
|
|
7753
|
+
}
|
|
7754
|
+
};
|
|
7588
7755
|
const timer = setTimeout(() => {
|
|
7589
7756
|
const index = this.queue.indexOf(entry);
|
|
7590
7757
|
if (index !== -1) {
|
|
7591
7758
|
this.queue.splice(index, 1);
|
|
7759
|
+
entry.cleanup();
|
|
7592
7760
|
this.stats.timeout += 1;
|
|
7593
7761
|
reject(createError(ErrorCodes.OPERATION_TIMEOUT, `Count queue wait timeout (${this.timeout}ms)`));
|
|
7594
7762
|
}
|
|
@@ -7601,9 +7769,12 @@ var CountQueue = class {
|
|
|
7601
7769
|
},
|
|
7602
7770
|
reject,
|
|
7603
7771
|
timer,
|
|
7604
|
-
startTime
|
|
7772
|
+
startTime,
|
|
7773
|
+
cleanup: () => signal?.removeEventListener("abort", onAbort)
|
|
7605
7774
|
};
|
|
7606
7775
|
this.queue.push(entry);
|
|
7776
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7777
|
+
if (signal?.aborted) onAbort();
|
|
7607
7778
|
});
|
|
7608
7779
|
}
|
|
7609
7780
|
_wakeNext() {
|
|
@@ -7611,6 +7782,7 @@ var CountQueue = class {
|
|
|
7611
7782
|
const entry = this.queue.shift();
|
|
7612
7783
|
if (entry) {
|
|
7613
7784
|
clearTimeout(entry.timer);
|
|
7785
|
+
entry.cleanup();
|
|
7614
7786
|
entry.resolve();
|
|
7615
7787
|
}
|
|
7616
7788
|
}
|
|
@@ -7627,10 +7799,12 @@ var CountQueue = class {
|
|
|
7627
7799
|
}
|
|
7628
7800
|
return value;
|
|
7629
7801
|
}
|
|
7630
|
-
_executeWithTimeout(fn, timeoutMs
|
|
7802
|
+
_executeWithTimeout(fn, timeoutMs, signal, release) {
|
|
7631
7803
|
let timer = null;
|
|
7632
7804
|
const controller = new AbortController();
|
|
7633
|
-
|
|
7805
|
+
let rejectCancellation;
|
|
7806
|
+
const cancellation = new Promise((_, reject) => {
|
|
7807
|
+
rejectCancellation = reject;
|
|
7634
7808
|
timer = setTimeout(
|
|
7635
7809
|
() => {
|
|
7636
7810
|
controller.abort();
|
|
@@ -7640,14 +7814,22 @@ var CountQueue = class {
|
|
|
7640
7814
|
timeoutMs
|
|
7641
7815
|
);
|
|
7642
7816
|
});
|
|
7817
|
+
const onAbort = () => {
|
|
7818
|
+
controller.abort();
|
|
7819
|
+
rejectCancellation(createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted"));
|
|
7820
|
+
};
|
|
7821
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7643
7822
|
let taskPromise;
|
|
7644
7823
|
try {
|
|
7645
7824
|
taskPromise = Promise.resolve(fn(controller.signal));
|
|
7646
7825
|
} catch (error) {
|
|
7647
7826
|
taskPromise = Promise.reject(error);
|
|
7648
7827
|
}
|
|
7649
|
-
|
|
7828
|
+
void taskPromise.then(release, release);
|
|
7829
|
+
if (signal?.aborted) onAbort();
|
|
7830
|
+
return Promise.race([taskPromise, cancellation]).finally(() => {
|
|
7650
7831
|
if (timer !== null) clearTimeout(timer);
|
|
7832
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7651
7833
|
});
|
|
7652
7834
|
}
|
|
7653
7835
|
_updateWaitTimeStats(waitTime) {
|
|
@@ -7660,6 +7842,7 @@ var CountQueue = class {
|
|
|
7660
7842
|
};
|
|
7661
7843
|
|
|
7662
7844
|
// src/capabilities/pool/pool-health-checker.ts
|
|
7845
|
+
var SILENT_HEALTH_CHECKER_LOGGER = {};
|
|
7663
7846
|
var HealthChecker = class {
|
|
7664
7847
|
constructor(options = {}) {
|
|
7665
7848
|
this._healthStatus = /* @__PURE__ */ new Map();
|
|
@@ -7669,7 +7852,7 @@ var HealthChecker = class {
|
|
|
7669
7852
|
this._inProgress = /* @__PURE__ */ new Set();
|
|
7670
7853
|
this._started = false;
|
|
7671
7854
|
this._poolManager = options.poolManager ?? null;
|
|
7672
|
-
this._logger = options.logger ??
|
|
7855
|
+
this._logger = options.logger ?? SILENT_HEALTH_CHECKER_LOGGER;
|
|
7673
7856
|
}
|
|
7674
7857
|
register(poolNameOrConfig, configOrClient) {
|
|
7675
7858
|
let poolName;
|
|
@@ -7793,6 +7976,7 @@ var HealthChecker = class {
|
|
|
7793
7976
|
};
|
|
7794
7977
|
|
|
7795
7978
|
// src/capabilities/pool/pool-selector.ts
|
|
7979
|
+
var SILENT_POOL_SELECTOR_LOGGER = {};
|
|
7796
7980
|
var POOL_STRATEGY_ALIASES = {
|
|
7797
7981
|
"round-robin": "roundRobin",
|
|
7798
7982
|
"least-connections": "leastConnections",
|
|
@@ -7806,7 +7990,7 @@ var PoolSelector = class {
|
|
|
7806
7990
|
constructor(options = {}) {
|
|
7807
7991
|
this._roundRobinIndex = /* @__PURE__ */ new Map();
|
|
7808
7992
|
this._strategy = normalizePoolStrategy(options.strategy);
|
|
7809
|
-
this._logger = options.logger ??
|
|
7993
|
+
this._logger = options.logger ?? SILENT_POOL_SELECTOR_LOGGER;
|
|
7810
7994
|
}
|
|
7811
7995
|
select(pools, context) {
|
|
7812
7996
|
if (!pools || pools.length === 0) {
|
|
@@ -7937,11 +8121,12 @@ var PoolSelector = class {
|
|
|
7937
8121
|
};
|
|
7938
8122
|
|
|
7939
8123
|
// src/capabilities/pool/pool-stats-manager.ts
|
|
8124
|
+
var SILENT_POOL_STATS_LOGGER = {};
|
|
7940
8125
|
var PoolStatsManager = class {
|
|
7941
8126
|
constructor(options = {}) {
|
|
7942
8127
|
this._stats = /* @__PURE__ */ new Map();
|
|
7943
8128
|
this._buffer = [];
|
|
7944
|
-
this._logger = options.logger ??
|
|
8129
|
+
this._logger = options.logger ?? SILENT_POOL_STATS_LOGGER;
|
|
7945
8130
|
this._batchInterval = setInterval(() => {
|
|
7946
8131
|
this._flush();
|
|
7947
8132
|
}, 100);
|
|
@@ -8173,6 +8358,9 @@ var ConnectionPoolManager = class {
|
|
|
8173
8358
|
this._closed = false;
|
|
8174
8359
|
this._configs = /* @__PURE__ */ new Map();
|
|
8175
8360
|
this._pendingAdds = /* @__PURE__ */ new Set();
|
|
8361
|
+
this._pendingAddTasks = /* @__PURE__ */ new Set();
|
|
8362
|
+
this._generation = 0;
|
|
8363
|
+
this._closePromise = null;
|
|
8176
8364
|
this.logger = options.logger ?? null;
|
|
8177
8365
|
this.maxPoolsCount = options.maxPoolsCount ?? 10;
|
|
8178
8366
|
this.strategy = options.poolStrategy ?? "auto";
|
|
@@ -8189,9 +8377,9 @@ var ConnectionPoolManager = class {
|
|
|
8189
8377
|
this._fallbackConfig = this.fallback;
|
|
8190
8378
|
this._fallback = this.fallback;
|
|
8191
8379
|
this._pools = this.pools;
|
|
8192
|
-
this._selector = new PoolSelector({ strategy: this.strategy });
|
|
8193
|
-
this._healthChecker = new HealthChecker({ poolManager: this });
|
|
8194
|
-
this._stats = new PoolStatsManager({ logger:
|
|
8380
|
+
this._selector = new PoolSelector({ strategy: this.strategy, logger: this.logger ?? void 0 });
|
|
8381
|
+
this._healthChecker = new HealthChecker({ poolManager: this, logger: this.logger ?? void 0 });
|
|
8382
|
+
this._stats = new PoolStatsManager({ logger: this.logger ?? void 0 });
|
|
8195
8383
|
}
|
|
8196
8384
|
/**
|
|
8197
8385
|
* Add a connection pool.
|
|
@@ -8199,35 +8387,21 @@ var ConnectionPoolManager = class {
|
|
|
8199
8387
|
*/
|
|
8200
8388
|
async addPool(config) {
|
|
8201
8389
|
validatePoolConfig(config);
|
|
8390
|
+
if (this._closed) {
|
|
8391
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Connection pool manager is closed");
|
|
8392
|
+
}
|
|
8202
8393
|
if (this.pools.has(config.name) || this._pendingAdds.has(config.name)) {
|
|
8203
8394
|
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8204
8395
|
}
|
|
8205
|
-
if (this.maxPoolsCount > 0 && this.pools.size >= this.maxPoolsCount) {
|
|
8396
|
+
if (this.maxPoolsCount > 0 && this.pools.size + this._pendingAdds.size >= this.maxPoolsCount) {
|
|
8206
8397
|
throw createError(ErrorCodes.INVALID_CONFIG, `Maximum pool count (${this.maxPoolsCount}) reached`);
|
|
8207
8398
|
}
|
|
8208
8399
|
this._pendingAdds.add(config.name);
|
|
8400
|
+
const generation = this._generation;
|
|
8401
|
+
const task = this._finishAddPool(config, generation);
|
|
8402
|
+
this._pendingAddTasks.add(task);
|
|
8209
8403
|
try {
|
|
8210
|
-
|
|
8211
|
-
if (this.pools.has(config.name)) {
|
|
8212
|
-
await client.close().catch(() => {
|
|
8213
|
-
});
|
|
8214
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
8215
|
-
}
|
|
8216
|
-
this.pools.set(config.name, {
|
|
8217
|
-
client,
|
|
8218
|
-
config,
|
|
8219
|
-
createdAt: Date.now()
|
|
8220
|
-
});
|
|
8221
|
-
this.healthStatus.set(config.name, {
|
|
8222
|
-
status: "up",
|
|
8223
|
-
consecutiveFailures: 0,
|
|
8224
|
-
lastCheckTime: null,
|
|
8225
|
-
lastError: null,
|
|
8226
|
-
uptime: 0
|
|
8227
|
-
});
|
|
8228
|
-
this.stats.set(config.name, createEmptyPoolStats(config.name));
|
|
8229
|
-
this._configs.set(config.name, config);
|
|
8230
|
-
this._healthChecker.register(config.name, config.healthCheck ?? {});
|
|
8404
|
+
await task;
|
|
8231
8405
|
} catch (err) {
|
|
8232
8406
|
const error = err;
|
|
8233
8407
|
const msg = error.message ?? "";
|
|
@@ -8241,8 +8415,31 @@ var ConnectionPoolManager = class {
|
|
|
8241
8415
|
throw err;
|
|
8242
8416
|
} finally {
|
|
8243
8417
|
this._pendingAdds.delete(config.name);
|
|
8418
|
+
this._pendingAddTasks.delete(task);
|
|
8244
8419
|
}
|
|
8245
8420
|
}
|
|
8421
|
+
async _finishAddPool(config, generation) {
|
|
8422
|
+
const client = await this.clientFactory(config);
|
|
8423
|
+
if (this._closed || generation !== this._generation || this.pools.has(config.name)) {
|
|
8424
|
+
await client.close().catch(() => {
|
|
8425
|
+
});
|
|
8426
|
+
throw createError(
|
|
8427
|
+
this._closed || generation !== this._generation ? ErrorCodes.INVALID_OPERATION : ErrorCodes.INVALID_CONFIG,
|
|
8428
|
+
this._closed || generation !== this._generation ? "Connection pool manager closed while adding a pool" : `Pool '${config.name}' already exists`
|
|
8429
|
+
);
|
|
8430
|
+
}
|
|
8431
|
+
this.pools.set(config.name, { client, config, createdAt: Date.now() });
|
|
8432
|
+
this.healthStatus.set(config.name, {
|
|
8433
|
+
status: "up",
|
|
8434
|
+
consecutiveFailures: 0,
|
|
8435
|
+
lastCheckTime: null,
|
|
8436
|
+
lastError: null,
|
|
8437
|
+
uptime: 0
|
|
8438
|
+
});
|
|
8439
|
+
this.stats.set(config.name, createEmptyPoolStats(config.name));
|
|
8440
|
+
this._configs.set(config.name, config);
|
|
8441
|
+
this._healthChecker.register(config.name, config.healthCheck ?? {});
|
|
8442
|
+
}
|
|
8246
8443
|
/**
|
|
8247
8444
|
* Remove a connection pool.
|
|
8248
8445
|
* @since v1.0.8
|
|
@@ -8398,16 +8595,24 @@ var ConnectionPoolManager = class {
|
|
|
8398
8595
|
* @since v1.0.8
|
|
8399
8596
|
*/
|
|
8400
8597
|
async close() {
|
|
8598
|
+
if (this._closePromise) return this._closePromise;
|
|
8599
|
+
this._closed = true;
|
|
8600
|
+
this._generation += 1;
|
|
8601
|
+
this._closePromise = this._closeAllPools();
|
|
8602
|
+
return this._closePromise;
|
|
8603
|
+
}
|
|
8604
|
+
async _closeAllPools() {
|
|
8401
8605
|
this.stopHealthCheck();
|
|
8402
8606
|
this._healthChecker.stop();
|
|
8403
8607
|
this._stats.close();
|
|
8608
|
+
await Promise.allSettled([...this._pendingAddTasks]);
|
|
8404
8609
|
for (const pool of this.pools.values()) {
|
|
8405
8610
|
await pool.client.close();
|
|
8406
8611
|
}
|
|
8407
8612
|
this.pools.clear();
|
|
8408
8613
|
this.healthStatus.clear();
|
|
8409
8614
|
this.stats.clear();
|
|
8410
|
-
this.
|
|
8615
|
+
this._configs.clear();
|
|
8411
8616
|
}
|
|
8412
8617
|
// ─── v1 compat methods ───────────────────────────────────────────────────────────
|
|
8413
8618
|
/** v1 compat: get pool health Map */
|
|
@@ -8595,6 +8800,7 @@ async function initializeDistributedCacheInvalidator(options, cache, logger) {
|
|
|
8595
8800
|
logger
|
|
8596
8801
|
});
|
|
8597
8802
|
} catch (err) {
|
|
8803
|
+
if (err?.code === ErrorCodes.INVALID_CONFIG) throw err;
|
|
8598
8804
|
logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 check Redis config or package installation completeness.", err);
|
|
8599
8805
|
return null;
|
|
8600
8806
|
}
|
|
@@ -8673,6 +8879,33 @@ async function loadModelFiles(options, logger, opts = {}) {
|
|
|
8673
8879
|
}
|
|
8674
8880
|
}
|
|
8675
8881
|
|
|
8882
|
+
// src/entry/runtime-health.ts
|
|
8883
|
+
function summarizeRuntimeHealth(cacheOption, cache, poolManager, invalidator) {
|
|
8884
|
+
const cacheStats = cache.getStats?.();
|
|
8885
|
+
const poolHealth = poolManager?.getHealthStatus();
|
|
8886
|
+
const distributedStats = invalidator?.getStats() ?? null;
|
|
8887
|
+
const cacheInput = cacheOption;
|
|
8888
|
+
const cacheEnabled = cacheInput?.enabled !== false;
|
|
8889
|
+
const distributedEnabled = cacheInput?.distributed !== void 0 && cacheInput.distributed.enabled !== false;
|
|
8890
|
+
const cacheProbe = cacheEnabled ? cache.getHealthStatus?.() : void 0;
|
|
8891
|
+
const cacheProbeStatus = cacheProbe === true || cacheProbe === "up" ? "up" : cacheProbe === false || cacheProbe === "down" ? "down" : "unknown";
|
|
8892
|
+
const redisState = invalidator;
|
|
8893
|
+
const redisStatuses = [redisState?.pub?.status, redisState?.sub?.status];
|
|
8894
|
+
const distributedStatus = !distributedEnabled || !invalidator ? "unknown" : redisStatuses.every((status) => status === "ready") ? "up" : redisStatuses.some((status) => status === "end" || status === "close") ? "down" : "unknown";
|
|
8895
|
+
const poolStates = Object.values(poolHealth ?? {}).map((health) => health.status);
|
|
8896
|
+
const poolsStatus = poolStates.length === 0 ? "unknown" : poolStates.some((status) => status === "down") ? "down" : poolStates.every((status) => status === "up") ? "up" : "unknown";
|
|
8897
|
+
return {
|
|
8898
|
+
cacheStats,
|
|
8899
|
+
poolHealth,
|
|
8900
|
+
distributedStats,
|
|
8901
|
+
cacheEnabled,
|
|
8902
|
+
distributedEnabled,
|
|
8903
|
+
cacheProbeStatus,
|
|
8904
|
+
distributedStatus,
|
|
8905
|
+
poolsStatus
|
|
8906
|
+
};
|
|
8907
|
+
}
|
|
8908
|
+
|
|
8676
8909
|
// src/entry/runtime-defaults.ts
|
|
8677
8910
|
function isPlainObject3(value) {
|
|
8678
8911
|
if (value === null || typeof value !== "object") return false;
|
|
@@ -9323,36 +9556,36 @@ var FUNC_REGEX = /^(CONCAT|UPPER|LOWER|TRIM|SUBSTR|LENGTH|ABS|CEIL|FLOOR|ROUND|S
|
|
|
9323
9556
|
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;
|
|
9324
9557
|
function compileInnerExpression(expression) {
|
|
9325
9558
|
const expr2 = expression.trim();
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
9559
|
+
if (!expr2) throw createError(ErrorCodes.INVALID_EXPRESSION, "Expression cannot be empty");
|
|
9560
|
+
const positions = topLevelPositions(expr2);
|
|
9561
|
+
const unwrapped = stripOuterParentheses(expr2);
|
|
9562
|
+
if (unwrapped !== expr2) return compileInnerExpression(unwrapped);
|
|
9563
|
+
if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(expr2)) return Number(expr2);
|
|
9564
|
+
const conditional = findConditional(expr2, positions);
|
|
9565
|
+
if (conditional) {
|
|
9566
|
+
return { $cond: {
|
|
9567
|
+
if: compileInnerExpression(expr2.slice(0, conditional.question)),
|
|
9568
|
+
then: compileInnerExpression(expr2.slice(conditional.question + 1, conditional.colon)),
|
|
9569
|
+
else: compileInnerExpression(expr2.slice(conditional.colon + 1))
|
|
9570
|
+
} };
|
|
9571
|
+
}
|
|
9572
|
+
const nullCoalParts = splitTopLevel2(expr2, "??", positions);
|
|
9573
|
+
if (nullCoalParts.length > 1) {
|
|
9574
|
+
return nullCoalParts.slice(1).reduce(
|
|
9575
|
+
(left, right) => ({ $ifNull: [left, compileInnerExpression(right)] }),
|
|
9576
|
+
compileInnerExpression(nullCoalParts[0])
|
|
9577
|
+
);
|
|
9333
9578
|
}
|
|
9334
|
-
const orParts = splitTopLevel2(expr2, "||");
|
|
9579
|
+
const orParts = splitTopLevel2(expr2, "||", positions);
|
|
9335
9580
|
if (orParts.length > 1) {
|
|
9336
|
-
return { $or: orParts.map((part) => compileInnerExpression(part
|
|
9337
|
-
}
|
|
9338
|
-
const ternary = /^([^?]+)\s*\?\s*([^:]+)\s*:\s*(.+)$/.exec(expr2);
|
|
9339
|
-
if (ternary) {
|
|
9340
|
-
const [, condition, thenPart, elsePart] = ternary;
|
|
9341
|
-
return {
|
|
9342
|
-
$cond: {
|
|
9343
|
-
if: compileInnerExpression(condition.trim()),
|
|
9344
|
-
then: parseThenElse(thenPart.trim()),
|
|
9345
|
-
else: parseThenElse(elsePart.trim())
|
|
9346
|
-
}
|
|
9347
|
-
};
|
|
9581
|
+
return { $or: orParts.map((part) => compileInnerExpression(part)) };
|
|
9348
9582
|
}
|
|
9349
|
-
const
|
|
9350
|
-
if (
|
|
9351
|
-
return { $
|
|
9583
|
+
const andParts = splitTopLevel2(expr2, "&&", positions);
|
|
9584
|
+
if (andParts.length > 1) {
|
|
9585
|
+
return { $and: andParts.map((part) => compileInnerExpression(part)) };
|
|
9352
9586
|
}
|
|
9353
|
-
const
|
|
9354
|
-
if (
|
|
9355
|
-
const [, left, operator, right] = cmpMatch;
|
|
9587
|
+
const comparison = findBinaryOperator(expr2, positions, ["===", "!==", ">=", "<=", ">", "<"]);
|
|
9588
|
+
if (comparison) {
|
|
9356
9589
|
const operatorMap = {
|
|
9357
9590
|
"===": "$eq",
|
|
9358
9591
|
"!==": "$ne",
|
|
@@ -9361,24 +9594,39 @@ function compileInnerExpression(expression) {
|
|
|
9361
9594
|
">": "$gt",
|
|
9362
9595
|
"<": "$lt"
|
|
9363
9596
|
};
|
|
9364
|
-
return {
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
9597
|
+
return { [operatorMap[comparison.operator]]: [
|
|
9598
|
+
compileInnerExpression(expr2.slice(0, comparison.index)),
|
|
9599
|
+
compileInnerExpression(expr2.slice(comparison.index + comparison.operator.length))
|
|
9600
|
+
] };
|
|
9601
|
+
}
|
|
9602
|
+
const additive = findBinaryOperator(expr2, positions, ["+", "-"], true);
|
|
9603
|
+
if (additive) {
|
|
9604
|
+
return { [additive.operator === "+" ? "$add" : "$subtract"]: [
|
|
9605
|
+
compileInnerExpression(expr2.slice(0, additive.index)),
|
|
9606
|
+
compileInnerExpression(expr2.slice(additive.index + 1))
|
|
9607
|
+
] };
|
|
9608
|
+
}
|
|
9609
|
+
const multiplicative = findBinaryOperator(expr2, positions, ["*", "/", "%"], true);
|
|
9610
|
+
if (multiplicative) {
|
|
9611
|
+
const operatorMap = { "*": "$multiply", "/": "$divide", "%": "$mod" };
|
|
9612
|
+
return { [operatorMap[multiplicative.operator]]: [
|
|
9613
|
+
compileInnerExpression(expr2.slice(0, multiplicative.index)),
|
|
9614
|
+
compileInnerExpression(expr2.slice(multiplicative.index + 1))
|
|
9615
|
+
] };
|
|
9370
9616
|
}
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
const [, left, operator, right] = addSubMatch;
|
|
9374
|
-
const operatorMap = { "+": "$add", "-": "$subtract" };
|
|
9375
|
-
return { [operatorMap[operator]]: [parseOperand(left.trim()), parseOperand(right.trim())] };
|
|
9617
|
+
if (expr2.startsWith("-")) {
|
|
9618
|
+
return { $multiply: [-1, compileInnerExpression(expr2.slice(1))] };
|
|
9376
9619
|
}
|
|
9377
|
-
const
|
|
9378
|
-
if (
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9620
|
+
const funcMatch = expr2.match(FUNC_REGEX);
|
|
9621
|
+
if (funcMatch) {
|
|
9622
|
+
try {
|
|
9623
|
+
return dispatchFunction(funcMatch[1].toUpperCase(), funcMatch[2] ?? "");
|
|
9624
|
+
} catch (error) {
|
|
9625
|
+
if (error && typeof error === "object" && "code" in error && error.code === ErrorCodes.INVALID_EXPRESSION) {
|
|
9626
|
+
throw error;
|
|
9627
|
+
}
|
|
9628
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, `Invalid arguments for ${funcMatch[1].toUpperCase()}`);
|
|
9629
|
+
}
|
|
9382
9630
|
}
|
|
9383
9631
|
const genericFuncCallRe = /^[A-Za-z_][A-Za-z0-9_]*\s*\(.+\)$/;
|
|
9384
9632
|
if (genericFuncCallRe.test(expr2)) {
|
|
@@ -9387,9 +9635,6 @@ function compileInnerExpression(expression) {
|
|
|
9387
9635
|
}
|
|
9388
9636
|
return parseValue(expr2);
|
|
9389
9637
|
}
|
|
9390
|
-
function parseThenElse(source) {
|
|
9391
|
-
return source.includes("?") && source.includes(":") ? compileInnerExpression(source) : parseValue(source);
|
|
9392
|
-
}
|
|
9393
9638
|
function parseValue(value) {
|
|
9394
9639
|
const normalized = stripOuterParentheses(value.trim());
|
|
9395
9640
|
if (normalized.startsWith("'") && normalized.endsWith("'") || normalized.startsWith('"') && normalized.endsWith('"')) {
|
|
@@ -9399,18 +9644,22 @@ function parseValue(value) {
|
|
|
9399
9644
|
if (normalized === "true") return true;
|
|
9400
9645
|
if (normalized === "false") return false;
|
|
9401
9646
|
if (!isNaN(Number(normalized)) && normalized !== "") return Number(normalized);
|
|
9402
|
-
if (IS_FUNC_CALL_RE.test(normalized)) {
|
|
9647
|
+
if (normalized !== value.trim() || IS_FUNC_CALL_RE.test(normalized) || FUNC_REGEX.test(normalized)) {
|
|
9403
9648
|
return compileInnerExpression(normalized);
|
|
9404
9649
|
}
|
|
9405
|
-
if (normalized.startsWith("
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9650
|
+
if (normalized.startsWith("[") || normalized.startsWith("{")) {
|
|
9651
|
+
try {
|
|
9652
|
+
return JSON.parse(normalized);
|
|
9653
|
+
} catch {
|
|
9654
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Invalid array or object literal");
|
|
9655
|
+
}
|
|
9656
|
+
}
|
|
9657
|
+
const positions = topLevelPositions(normalized);
|
|
9658
|
+
if (positions.some((index) => /[?+\-*/%><&|]/.test(normalized[index]))) {
|
|
9411
9659
|
return compileInnerExpression(normalized);
|
|
9412
9660
|
}
|
|
9413
|
-
|
|
9661
|
+
if (normalized.startsWith("$")) return normalized;
|
|
9662
|
+
return `$${normalized}`;
|
|
9414
9663
|
}
|
|
9415
9664
|
function dispatchFunction(name, argsStr) {
|
|
9416
9665
|
const args = splitArgsStr(argsStr);
|
|
@@ -9712,72 +9961,82 @@ function compileMapExpression(exprStr, varName) {
|
|
|
9712
9961
|
return compileInnerExpression(replaced);
|
|
9713
9962
|
}
|
|
9714
9963
|
function splitArgsStr(argsStr) {
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
let inString = false;
|
|
9718
|
-
let stringChar = "";
|
|
9719
|
-
let parenDepth = 0;
|
|
9720
|
-
for (let index = 0; index < argsStr.length; index++) {
|
|
9721
|
-
const ch = argsStr[index];
|
|
9722
|
-
if ((ch === '"' || ch === "'") && (index === 0 || argsStr[index - 1] !== "\\")) {
|
|
9723
|
-
if (!inString) {
|
|
9724
|
-
inString = true;
|
|
9725
|
-
stringChar = ch;
|
|
9726
|
-
} else if (ch === stringChar) {
|
|
9727
|
-
inString = false;
|
|
9728
|
-
stringChar = "";
|
|
9729
|
-
}
|
|
9730
|
-
current += ch;
|
|
9731
|
-
} else if (ch === "(" && !inString) {
|
|
9732
|
-
parenDepth++;
|
|
9733
|
-
current += ch;
|
|
9734
|
-
} else if (ch === ")" && !inString) {
|
|
9735
|
-
parenDepth--;
|
|
9736
|
-
current += ch;
|
|
9737
|
-
} else if (ch === "," && !inString && parenDepth === 0) {
|
|
9738
|
-
args.push(current.trim());
|
|
9739
|
-
current = "";
|
|
9740
|
-
} else {
|
|
9741
|
-
current += ch;
|
|
9742
|
-
}
|
|
9743
|
-
}
|
|
9744
|
-
if (current.trim()) args.push(current.trim());
|
|
9745
|
-
return args;
|
|
9964
|
+
if (!argsStr.trim()) return [];
|
|
9965
|
+
return splitTopLevel2(argsStr, ",");
|
|
9746
9966
|
}
|
|
9747
|
-
function
|
|
9748
|
-
const
|
|
9749
|
-
|
|
9750
|
-
let
|
|
9751
|
-
let inString = false;
|
|
9752
|
-
let stringChar = "";
|
|
9967
|
+
function topLevelPositions(source) {
|
|
9968
|
+
const positions = [];
|
|
9969
|
+
const stack = [];
|
|
9970
|
+
let quote = null;
|
|
9753
9971
|
for (let index = 0; index < source.length; index++) {
|
|
9754
9972
|
const ch = source[index];
|
|
9755
|
-
if (
|
|
9756
|
-
if (
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9973
|
+
if (quote) {
|
|
9974
|
+
if (ch === quote && source[index - 1] !== "\\") quote = null;
|
|
9975
|
+
continue;
|
|
9976
|
+
}
|
|
9977
|
+
if (ch === '"' || ch === "'") {
|
|
9978
|
+
quote = ch;
|
|
9979
|
+
continue;
|
|
9980
|
+
}
|
|
9981
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
9982
|
+
stack.push(ch);
|
|
9983
|
+
continue;
|
|
9984
|
+
}
|
|
9985
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
9986
|
+
const opening = stack.pop();
|
|
9987
|
+
if (ch === ")" && opening !== "(" || ch === "]" && opening !== "[" || ch === "}" && opening !== "{") {
|
|
9988
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Unbalanced expression delimiters");
|
|
9762
9989
|
}
|
|
9763
|
-
|
|
9764
|
-
} else if (!inString && ch === "(") {
|
|
9765
|
-
parenDepth++;
|
|
9766
|
-
current += ch;
|
|
9767
|
-
} else if (!inString && ch === ")") {
|
|
9768
|
-
parenDepth--;
|
|
9769
|
-
current += ch;
|
|
9770
|
-
} else if (!inString && parenDepth === 0 && source.startsWith(separator, index)) {
|
|
9771
|
-
parts.push(current);
|
|
9772
|
-
current = "";
|
|
9773
|
-
index += separator.length - 1;
|
|
9774
|
-
} else {
|
|
9775
|
-
current += ch;
|
|
9990
|
+
continue;
|
|
9776
9991
|
}
|
|
9992
|
+
if (stack.length === 0) positions.push(index);
|
|
9993
|
+
}
|
|
9994
|
+
if (quote || stack.length > 0) {
|
|
9995
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Unbalanced expression delimiters");
|
|
9996
|
+
}
|
|
9997
|
+
return positions;
|
|
9998
|
+
}
|
|
9999
|
+
function splitTopLevel2(source, separator, positions = topLevelPositions(source)) {
|
|
10000
|
+
const parts = [];
|
|
10001
|
+
let start = 0;
|
|
10002
|
+
for (const index of positions) {
|
|
10003
|
+
if (index < start || !source.startsWith(separator, index)) continue;
|
|
10004
|
+
parts.push(source.slice(start, index).trim());
|
|
10005
|
+
start = index + separator.length;
|
|
10006
|
+
}
|
|
10007
|
+
if (parts.length === 0) return [source];
|
|
10008
|
+
parts.push(source.slice(start).trim());
|
|
10009
|
+
if (parts.some((part) => !part)) {
|
|
10010
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, `Missing operand around ${separator}`);
|
|
9777
10011
|
}
|
|
9778
|
-
parts.push(current);
|
|
9779
10012
|
return parts;
|
|
9780
10013
|
}
|
|
10014
|
+
function findConditional(source, positions) {
|
|
10015
|
+
const question = positions.find((index) => source[index] === "?" && source[index - 1] !== "?" && source[index + 1] !== "?");
|
|
10016
|
+
if (question === void 0) return null;
|
|
10017
|
+
let nested = 0;
|
|
10018
|
+
for (const index of positions) {
|
|
10019
|
+
if (index <= question) continue;
|
|
10020
|
+
if (source[index] === "?" && source[index - 1] !== "?" && source[index + 1] !== "?") nested++;
|
|
10021
|
+
if (source[index] === ":") {
|
|
10022
|
+
if (nested === 0) return { question, colon: index };
|
|
10023
|
+
nested--;
|
|
10024
|
+
}
|
|
10025
|
+
}
|
|
10026
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, "Conditional expression is missing a branch");
|
|
10027
|
+
}
|
|
10028
|
+
function findBinaryOperator(source, positions, operators, rightmost = false) {
|
|
10029
|
+
let match = null;
|
|
10030
|
+
for (const index of positions) {
|
|
10031
|
+
const operator = operators.find((candidate) => source.startsWith(candidate, index));
|
|
10032
|
+
if (!operator) continue;
|
|
10033
|
+
if ((operator === "+" || operator === "-") && !source.slice(0, index).trim()) continue;
|
|
10034
|
+
if ((operator === "+" || operator === "-") && /[+\-*/%<>=!?&|:,]$/.test(source.slice(0, index).trimEnd())) continue;
|
|
10035
|
+
match = { index, operator };
|
|
10036
|
+
if (!rightmost) break;
|
|
10037
|
+
}
|
|
10038
|
+
return match;
|
|
10039
|
+
}
|
|
9781
10040
|
function stripOuterParentheses(source) {
|
|
9782
10041
|
let normalized = source.trim();
|
|
9783
10042
|
while (normalized.startsWith("(") && normalized.endsWith(")") && isWrappedByOuterParentheses(normalized)) {
|
|
@@ -9823,10 +10082,15 @@ function isExpressionObject(value) {
|
|
|
9823
10082
|
value && typeof value === "object" && "__expr__" in value && typeof value.__expr__ === "string" && "__compiled__" in value && typeof value.__compiled__ === "boolean"
|
|
9824
10083
|
);
|
|
9825
10084
|
}
|
|
10085
|
+
function isPlainExpressionRecord(value) {
|
|
10086
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
10087
|
+
const prototype = Object.getPrototypeOf(value);
|
|
10088
|
+
return prototype === Object.prototype || prototype === null;
|
|
10089
|
+
}
|
|
9826
10090
|
function hasExpressionInObject(value) {
|
|
9827
10091
|
if (isExpressionObject(value)) return true;
|
|
9828
|
-
if (!value || typeof value !== "object") return false;
|
|
9829
10092
|
if (Array.isArray(value)) return value.some((item) => hasExpressionInObject(item));
|
|
10093
|
+
if (!isPlainExpressionRecord(value)) return false;
|
|
9830
10094
|
return Object.values(value).some((item) => hasExpressionInObject(item));
|
|
9831
10095
|
}
|
|
9832
10096
|
function hasExpressionInPipeline(pipeline) {
|
|
@@ -9843,7 +10107,7 @@ function transformExpressions(value, context) {
|
|
|
9843
10107
|
if (Array.isArray(value)) {
|
|
9844
10108
|
return value.map((item) => transformStageEntry(item));
|
|
9845
10109
|
}
|
|
9846
|
-
if (!value
|
|
10110
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9847
10111
|
const entries = Object.entries(value);
|
|
9848
10112
|
const result = {};
|
|
9849
10113
|
for (const [key, current] of entries) {
|
|
@@ -9852,7 +10116,7 @@ function transformExpressions(value, context) {
|
|
|
9852
10116
|
return result;
|
|
9853
10117
|
}
|
|
9854
10118
|
function transformStageEntry(stage) {
|
|
9855
|
-
if (!
|
|
10119
|
+
if (!isPlainExpressionRecord(stage)) {
|
|
9856
10120
|
return transformExpressions(stage, "project");
|
|
9857
10121
|
}
|
|
9858
10122
|
const entries = Object.entries(stage);
|
|
@@ -9876,7 +10140,7 @@ function transformStageValue(value, context) {
|
|
|
9876
10140
|
if (Array.isArray(value)) {
|
|
9877
10141
|
return value.map((item) => transformStageValue(item, context));
|
|
9878
10142
|
}
|
|
9879
|
-
if (!value
|
|
10143
|
+
if (!isPlainExpressionRecord(value)) return value;
|
|
9880
10144
|
const entries = Object.entries(value);
|
|
9881
10145
|
const result = {};
|
|
9882
10146
|
for (const [key, current] of entries) {
|
|
@@ -10807,6 +11071,7 @@ var AggregateChain = class {
|
|
|
10807
11071
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).explain(verbosity === true ? "queryPlanner" : verbosity);
|
|
10808
11072
|
}
|
|
10809
11073
|
stream() {
|
|
11074
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10810
11075
|
const stream = this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).stream();
|
|
10811
11076
|
const onWriteComplete = this.writePipelineHooks?.onWriteComplete;
|
|
10812
11077
|
if (onWriteComplete) {
|
|
@@ -10824,6 +11089,7 @@ var AggregateChain = class {
|
|
|
10824
11089
|
throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
|
|
10825
11090
|
}
|
|
10826
11091
|
this.executed = true;
|
|
11092
|
+
if (this.writePipelineHooks) assertManagedTransactionWriteAllowed(this.buildExecuteOptions());
|
|
10827
11093
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).toArray().then(async (result) => {
|
|
10828
11094
|
await this.writePipelineHooks?.onWriteComplete?.();
|
|
10829
11095
|
return result;
|
|
@@ -11140,48 +11406,59 @@ function stripWriteCacheControlOptions(options) {
|
|
|
11140
11406
|
|
|
11141
11407
|
// src/adapters/mongodb/writes/write-basic.ts
|
|
11142
11408
|
async function insertOneDocument(collection, ...args) {
|
|
11409
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11143
11410
|
return collection.insertOne(...args);
|
|
11144
11411
|
}
|
|
11145
11412
|
async function insertManyDocuments(collection, ...args) {
|
|
11413
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11146
11414
|
return collection.insertMany(...args);
|
|
11147
11415
|
}
|
|
11148
11416
|
async function updateOneDocument(collection, ...args) {
|
|
11417
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11149
11418
|
return collection.updateOne(...args);
|
|
11150
11419
|
}
|
|
11151
11420
|
async function updateManyDocuments(collection, ...args) {
|
|
11421
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11152
11422
|
return collection.updateMany(...args);
|
|
11153
11423
|
}
|
|
11154
11424
|
async function replaceOneDocument(collection, ...args) {
|
|
11425
|
+
assertManagedTransactionWriteAllowed(args[2]);
|
|
11155
11426
|
return collection.replaceOne(...args);
|
|
11156
11427
|
}
|
|
11157
11428
|
async function findOneAndUpdateDocument(collection, filter, update, options) {
|
|
11429
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11158
11430
|
if (options !== void 0) {
|
|
11159
11431
|
return collection.findOneAndUpdate(filter, update, options);
|
|
11160
11432
|
}
|
|
11161
11433
|
return collection.findOneAndUpdate(filter, update);
|
|
11162
11434
|
}
|
|
11163
11435
|
async function findOneAndReplaceDocument(collection, filter, replacement, options) {
|
|
11436
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11164
11437
|
if (options !== void 0) {
|
|
11165
11438
|
return collection.findOneAndReplace(filter, replacement, options);
|
|
11166
11439
|
}
|
|
11167
11440
|
return collection.findOneAndReplace(filter, replacement);
|
|
11168
11441
|
}
|
|
11169
11442
|
async function findOneAndDeleteDocument(collection, filter, options) {
|
|
11443
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11170
11444
|
if (options !== void 0) {
|
|
11171
11445
|
return collection.findOneAndDelete(filter, options);
|
|
11172
11446
|
}
|
|
11173
11447
|
return collection.findOneAndDelete(filter);
|
|
11174
11448
|
}
|
|
11175
11449
|
async function upsertOneDocument(collection, filter, update, options = {}) {
|
|
11450
|
+
assertManagedTransactionWriteAllowed(options);
|
|
11176
11451
|
return collection.updateOne(filter, update, {
|
|
11177
11452
|
...options,
|
|
11178
11453
|
upsert: true
|
|
11179
11454
|
});
|
|
11180
11455
|
}
|
|
11181
11456
|
async function deleteOneDocument(collection, ...args) {
|
|
11457
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11182
11458
|
return collection.deleteOne(...args);
|
|
11183
11459
|
}
|
|
11184
11460
|
async function deleteManyDocuments(collection, ...args) {
|
|
11461
|
+
assertManagedTransactionWriteAllowed(args[1]);
|
|
11185
11462
|
return collection.deleteMany(...args);
|
|
11186
11463
|
}
|
|
11187
11464
|
async function incrementOneDocument(collection, filter, field, incrementOrOptions, maybeOptions) {
|
|
@@ -11208,6 +11485,7 @@ async function incrementOneDocument(collection, filter, field, incrementOrOption
|
|
|
11208
11485
|
includeResultMetadata: true
|
|
11209
11486
|
};
|
|
11210
11487
|
if (normalizedProjection) findOptions.projection = normalizedProjection;
|
|
11488
|
+
assertManagedTransactionWriteAllowed(findOptions);
|
|
11211
11489
|
const rawResult = await collection.findOneAndUpdate(
|
|
11212
11490
|
filter,
|
|
11213
11491
|
updateDocument,
|
|
@@ -11340,14 +11618,25 @@ async function runWithConcurrency(items, concurrency, worker) {
|
|
|
11340
11618
|
return;
|
|
11341
11619
|
}
|
|
11342
11620
|
let nextIndex = 0;
|
|
11621
|
+
let stopAcquiring = false;
|
|
11622
|
+
let firstFatal;
|
|
11343
11623
|
const workerCount = Math.min(concurrency, items.length);
|
|
11344
11624
|
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
11345
|
-
while (nextIndex < items.length) {
|
|
11625
|
+
while (!stopAcquiring && nextIndex < items.length) {
|
|
11346
11626
|
const item = items[nextIndex];
|
|
11347
11627
|
nextIndex += 1;
|
|
11348
|
-
|
|
11628
|
+
try {
|
|
11629
|
+
await worker(item);
|
|
11630
|
+
} catch (error) {
|
|
11631
|
+
if (!stopAcquiring) {
|
|
11632
|
+
firstFatal = error;
|
|
11633
|
+
stopAcquiring = true;
|
|
11634
|
+
}
|
|
11635
|
+
break;
|
|
11636
|
+
}
|
|
11349
11637
|
}
|
|
11350
11638
|
}));
|
|
11639
|
+
if (stopAcquiring) throw firstFatal;
|
|
11351
11640
|
}
|
|
11352
11641
|
async function insertBatchDocuments(collection, documents, options = {}) {
|
|
11353
11642
|
if (!Array.isArray(documents)) {
|
|
@@ -11394,12 +11683,14 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11394
11683
|
insertedIds: {},
|
|
11395
11684
|
retries: []
|
|
11396
11685
|
};
|
|
11686
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11397
11687
|
await runWithConcurrency(batchPlans, effectiveConcurrency, async ({ batchIndex, batch, batchOffset: batchOffset2 }) => {
|
|
11398
11688
|
let pendingBatch = batch;
|
|
11399
11689
|
let pendingIndexes = batch.map((_, index) => index);
|
|
11400
11690
|
let attempts = 0;
|
|
11401
11691
|
while (true) {
|
|
11402
11692
|
try {
|
|
11693
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11403
11694
|
const batchResult = await collection.insertMany(pendingBatch, {
|
|
11404
11695
|
...driverOptions,
|
|
11405
11696
|
ordered
|
|
@@ -11419,6 +11710,7 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11419
11710
|
});
|
|
11420
11711
|
break;
|
|
11421
11712
|
} catch (cause) {
|
|
11713
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11422
11714
|
const errorRecord = {
|
|
11423
11715
|
batchIndex,
|
|
11424
11716
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11490,6 +11782,10 @@ async function insertBatchDocuments(collection, documents, options = {}) {
|
|
|
11490
11782
|
});
|
|
11491
11783
|
return result;
|
|
11492
11784
|
}
|
|
11785
|
+
function batchWriteFilter(filter, batch) {
|
|
11786
|
+
const ids = { _id: { $in: batch } };
|
|
11787
|
+
return Object.prototype.hasOwnProperty.call(filter, "_id") ? { $and: [filter, ids] } : { ...filter, ...ids };
|
|
11788
|
+
}
|
|
11493
11789
|
async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
11494
11790
|
if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
|
|
11495
11791
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "filter must be a non-array object");
|
|
@@ -11533,6 +11829,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11533
11829
|
"updateBatch retry requires an idempotent update; non-idempotent operators such as $inc/$push cannot be safely replayed"
|
|
11534
11830
|
);
|
|
11535
11831
|
}
|
|
11832
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11536
11833
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11537
11834
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11538
11835
|
const result = {
|
|
@@ -11549,8 +11846,9 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11549
11846
|
let attempts = 0;
|
|
11550
11847
|
while (true) {
|
|
11551
11848
|
try {
|
|
11849
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11552
11850
|
const batchResult = await collection.updateMany(
|
|
11553
|
-
|
|
11851
|
+
batchWriteFilter(filter, batch),
|
|
11554
11852
|
update,
|
|
11555
11853
|
driverOptions
|
|
11556
11854
|
);
|
|
@@ -11568,6 +11866,7 @@ async function updateBatchDocuments(collection, filter, update, options = {}) {
|
|
|
11568
11866
|
});
|
|
11569
11867
|
break;
|
|
11570
11868
|
} catch (cause) {
|
|
11869
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11571
11870
|
const errorRecord = {
|
|
11572
11871
|
batchIndex,
|
|
11573
11872
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -11630,6 +11929,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11630
11929
|
if (!Number.isInteger(retryAttempts) || retryAttempts < 0) {
|
|
11631
11930
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "retryAttempts must be a non-negative integer");
|
|
11632
11931
|
}
|
|
11932
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11633
11933
|
const estimatedTotal = await estimateBatchTotal(collection, filter, driverOptions, estimateProgress);
|
|
11634
11934
|
const totalBatches = estimatedTotal === null ? null : Math.ceil(estimatedTotal / batchSize);
|
|
11635
11935
|
const result = {
|
|
@@ -11644,8 +11944,9 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11644
11944
|
let attempts = 0;
|
|
11645
11945
|
while (true) {
|
|
11646
11946
|
try {
|
|
11947
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11647
11948
|
const batchResult = await collection.deleteMany(
|
|
11648
|
-
|
|
11949
|
+
batchWriteFilter(filter, batch),
|
|
11649
11950
|
driverOptions
|
|
11650
11951
|
);
|
|
11651
11952
|
result.deletedCount += batchResult.deletedCount;
|
|
@@ -11660,6 +11961,7 @@ async function deleteBatchDocuments(collection, filter, options = {}) {
|
|
|
11660
11961
|
});
|
|
11661
11962
|
break;
|
|
11662
11963
|
} catch (cause) {
|
|
11964
|
+
assertManagedTransactionWriteAllowed(driverOptions);
|
|
11663
11965
|
const errorRecord = {
|
|
11664
11966
|
batchIndex,
|
|
11665
11967
|
message: cause instanceof Error ? cause.message : String(cause),
|
|
@@ -12188,9 +12490,11 @@ var MongoCollectionAccessor = class {
|
|
|
12188
12490
|
const normalizedQuery = this._cvFilter(query);
|
|
12189
12491
|
const maxTimeMS = this.management.defaults?.maxTimeMS;
|
|
12190
12492
|
const merged = { ...maxTimeMS !== void 0 ? { maxTimeMS } : {}, ...options ?? {} };
|
|
12191
|
-
const cacheTTL = typeof merged.cache === "number" ? merged.cache : 0;
|
|
12493
|
+
const cacheTTL = merged.explain === void 0 || merged.explain === false ? typeof merged.cache === "number" ? merged.cache : 0 : 0;
|
|
12192
12494
|
const { cache: _cache, ...keyOptions } = merged;
|
|
12193
12495
|
void _cache;
|
|
12496
|
+
const callerSignal = keyOptions.signal;
|
|
12497
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12194
12498
|
const executeCount = (signal) => countDocuments(
|
|
12195
12499
|
this.collectionRef,
|
|
12196
12500
|
normalizedQuery ?? {},
|
|
@@ -12201,15 +12505,16 @@ var MongoCollectionAccessor = class {
|
|
|
12201
12505
|
const cacheKey = buildCountCacheKey(this.collectionRef, this.management.defaults, normalizedQuery, keyOptions);
|
|
12202
12506
|
const cached = await Promise.resolve(this.management.queryCache.get(cacheKey));
|
|
12203
12507
|
if (cached !== void 0) {
|
|
12508
|
+
if (callerSignal?.aborted) throw createError(ErrorCodes.INVALID_OPERATION, "Count operation aborted");
|
|
12204
12509
|
return Promise.resolve(wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, cached));
|
|
12205
12510
|
}
|
|
12206
|
-
const runner2 = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12511
|
+
const runner2 = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12207
12512
|
return runner2.then(async (result) => {
|
|
12208
12513
|
await Promise.resolve(this.management.queryCache?.set(cacheKey, result, cacheTTL));
|
|
12209
12514
|
return wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result);
|
|
12210
12515
|
});
|
|
12211
12516
|
}
|
|
12212
|
-
const runner = countQueue ? countQueue.execute(executeCount) : executeCount();
|
|
12517
|
+
const runner = countQueue ? countQueue.execute(executeCount, { signal: callerSignal }) : executeCount();
|
|
12213
12518
|
return runner.then((result) => wrapQueryResultWithMeta(this.collectionRef, this.management.defaults, "count", merged, startTs, result));
|
|
12214
12519
|
}
|
|
12215
12520
|
/** Runs an aggregation pipeline and returns a chainable aggregate cursor. */
|
|
@@ -12766,10 +13071,10 @@ function assertCompatPoolExists(poolManager, poolName) {
|
|
|
12766
13071
|
function createPoolScope(runtime, poolName) {
|
|
12767
13072
|
return {
|
|
12768
13073
|
collection: (name) => runtime.scopedCollection(name, { pool: poolName }),
|
|
12769
|
-
model: (
|
|
13074
|
+
model: (model) => runtime.scopedModel(model, { pool: poolName }),
|
|
12770
13075
|
use: (dbName) => ({
|
|
12771
13076
|
collection: (name) => runtime.scopedCollection(name, { pool: poolName, database: dbName }),
|
|
12772
|
-
model: (
|
|
13077
|
+
model: (model) => runtime.scopedModel(model, { pool: poolName, database: dbName })
|
|
12773
13078
|
})
|
|
12774
13079
|
};
|
|
12775
13080
|
}
|
|
@@ -13498,20 +13803,21 @@ var DistributedCacheLockManager = class {
|
|
|
13498
13803
|
}
|
|
13499
13804
|
}
|
|
13500
13805
|
async withLock(key, callback, options = {}) {
|
|
13806
|
+
let lock;
|
|
13501
13807
|
try {
|
|
13502
|
-
|
|
13503
|
-
try {
|
|
13504
|
-
return await callback();
|
|
13505
|
-
} finally {
|
|
13506
|
-
await lock.release().catch(() => {
|
|
13507
|
-
});
|
|
13508
|
-
}
|
|
13808
|
+
lock = await this.acquireLock(key, options);
|
|
13509
13809
|
} catch (error) {
|
|
13510
13810
|
if (this._isRedisConnectionError(error) && options.fallbackToNoLock) {
|
|
13511
13811
|
return callback();
|
|
13512
13812
|
}
|
|
13513
13813
|
throw error;
|
|
13514
13814
|
}
|
|
13815
|
+
try {
|
|
13816
|
+
return await callback();
|
|
13817
|
+
} finally {
|
|
13818
|
+
await lock.release().catch(() => {
|
|
13819
|
+
});
|
|
13820
|
+
}
|
|
13515
13821
|
}
|
|
13516
13822
|
async acquireLock(key, options = {}) {
|
|
13517
13823
|
const ttl = options.ttl ?? 1e4;
|
|
@@ -14614,8 +14920,6 @@ var SlowQueryLogManager = class {
|
|
|
14614
14920
|
};
|
|
14615
14921
|
|
|
14616
14922
|
// src/capabilities/sync/index.ts
|
|
14617
|
-
import { copyFile, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
14618
|
-
import path2 from "node:path";
|
|
14619
14923
|
import { MongoClient as MongoDriverClient3 } from "mongodb";
|
|
14620
14924
|
|
|
14621
14925
|
// src/capabilities/sync/idempotency.ts
|
|
@@ -14718,25 +15022,19 @@ function validateSyncIdempotencyConfig(config) {
|
|
|
14718
15022
|
}
|
|
14719
15023
|
}
|
|
14720
15024
|
|
|
14721
|
-
// src/capabilities/sync/
|
|
15025
|
+
// src/capabilities/sync/resume-token-store.ts
|
|
15026
|
+
import { copyFile, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
15027
|
+
import path2 from "node:path";
|
|
14722
15028
|
function normalizeError(error) {
|
|
14723
15029
|
return error instanceof Error ? error : new Error(String(error));
|
|
14724
15030
|
}
|
|
14725
15031
|
function delay2(ms) {
|
|
14726
|
-
if (ms <= 0)
|
|
14727
|
-
return Promise.resolve();
|
|
14728
|
-
}
|
|
15032
|
+
if (ms <= 0) return Promise.resolve();
|
|
14729
15033
|
return new Promise((resolve) => {
|
|
14730
15034
|
const timer = setTimeout(resolve, ms);
|
|
14731
15035
|
timer.unref?.();
|
|
14732
15036
|
});
|
|
14733
15037
|
}
|
|
14734
|
-
function normalizeCollectionFilter(collections) {
|
|
14735
|
-
if (!collections?.length || collections.includes("*")) {
|
|
14736
|
-
return null;
|
|
14737
|
-
}
|
|
14738
|
-
return new Set(collections);
|
|
14739
|
-
}
|
|
14740
15038
|
async function syncDirectory(directory) {
|
|
14741
15039
|
try {
|
|
14742
15040
|
const handle = await open(directory, "r");
|
|
@@ -14748,35 +15046,6 @@ async function syncDirectory(directory) {
|
|
|
14748
15046
|
} catch {
|
|
14749
15047
|
}
|
|
14750
15048
|
}
|
|
14751
|
-
function validateTargetConfig(target, index) {
|
|
14752
|
-
if (!target || typeof target !== "object") {
|
|
14753
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] must be an object.`);
|
|
14754
|
-
}
|
|
14755
|
-
if (typeof target.name !== "string" || target.name.trim() === "") {
|
|
14756
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].name must be a non-empty string.`);
|
|
14757
|
-
}
|
|
14758
|
-
if (!target.apply && !target.uri && !target.pool) {
|
|
14759
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] requires one of apply / uri / pool.`);
|
|
14760
|
-
}
|
|
14761
|
-
if (target.uri !== void 0 && (typeof target.uri !== "string" || target.uri.trim() === "")) {
|
|
14762
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].uri must be a non-empty string when provided.`);
|
|
14763
|
-
}
|
|
14764
|
-
if (target.pool !== void 0 && (typeof target.pool !== "string" || target.pool.trim() === "")) {
|
|
14765
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].pool must be a non-empty string when provided.`);
|
|
14766
|
-
}
|
|
14767
|
-
if (target.databaseName !== void 0 && (typeof target.databaseName !== "string" || target.databaseName.trim() === "")) {
|
|
14768
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].databaseName must be a non-empty string when provided.`);
|
|
14769
|
-
}
|
|
14770
|
-
if (target.collections !== void 0 && (!Array.isArray(target.collections) || target.collections.length === 0)) {
|
|
14771
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must be a non-empty array when provided.`);
|
|
14772
|
-
}
|
|
14773
|
-
if (target.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14774
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must contain non-empty strings.`);
|
|
14775
|
-
}
|
|
14776
|
-
if (target.apply !== void 0 && typeof target.apply !== "function") {
|
|
14777
|
-
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].apply must be a function when provided.`);
|
|
14778
|
-
}
|
|
14779
|
-
}
|
|
14780
15049
|
function validateResumeTokenConfig(config) {
|
|
14781
15050
|
if (!config || typeof config !== "object") {
|
|
14782
15051
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken must be an object.");
|
|
@@ -14807,39 +15076,6 @@ function validateResumeTokenConfig(config) {
|
|
|
14807
15076
|
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] resumeToken.saveRetryDelayMs must be a non-negative integer.");
|
|
14808
15077
|
}
|
|
14809
15078
|
}
|
|
14810
|
-
function validateSyncConfig(config) {
|
|
14811
|
-
if (!config || typeof config !== "object") {
|
|
14812
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] config must be an object.");
|
|
14813
|
-
}
|
|
14814
|
-
if (typeof config.enabled !== "boolean") {
|
|
14815
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] enabled must be a boolean.");
|
|
14816
|
-
}
|
|
14817
|
-
if (!config.enabled) {
|
|
14818
|
-
return;
|
|
14819
|
-
}
|
|
14820
|
-
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
14821
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] targets must be a non-empty array.");
|
|
14822
|
-
}
|
|
14823
|
-
if (config.collections !== void 0 && (!Array.isArray(config.collections) || config.collections.length === 0)) {
|
|
14824
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must be a non-empty array when provided.");
|
|
14825
|
-
}
|
|
14826
|
-
if (config.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
14827
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must contain non-empty strings.");
|
|
14828
|
-
}
|
|
14829
|
-
if (config.filter !== void 0 && typeof config.filter !== "function") {
|
|
14830
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] filter must be a function.");
|
|
14831
|
-
}
|
|
14832
|
-
if (config.transform !== void 0 && typeof config.transform !== "function") {
|
|
14833
|
-
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] transform must be a function.");
|
|
14834
|
-
}
|
|
14835
|
-
validateSyncIdempotencyConfig(config.idempotency);
|
|
14836
|
-
config.targets.forEach((target, index) => {
|
|
14837
|
-
validateTargetConfig(target, index);
|
|
14838
|
-
});
|
|
14839
|
-
if (config.resumeToken) {
|
|
14840
|
-
validateResumeTokenConfig(config.resumeToken);
|
|
14841
|
-
}
|
|
14842
|
-
}
|
|
14843
15079
|
var ResumeTokenStore = class {
|
|
14844
15080
|
constructor(options = {}) {
|
|
14845
15081
|
this.storage = options.storage ?? "file";
|
|
@@ -14863,16 +15099,9 @@ var ResumeTokenStore = class {
|
|
|
14863
15099
|
return JSON.parse(payload);
|
|
14864
15100
|
} catch (error) {
|
|
14865
15101
|
const code = error?.code;
|
|
14866
|
-
if (code !== "ENOENT")
|
|
14867
|
-
this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14868
|
-
}
|
|
15102
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to load resume token", error);
|
|
14869
15103
|
if (code !== "ENOENT" && this.strictLoad) {
|
|
14870
|
-
throw createError(
|
|
14871
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14872
|
-
"[Sync] failed to load resume token",
|
|
14873
|
-
void 0,
|
|
14874
|
-
normalizeError(error)
|
|
14875
|
-
);
|
|
15104
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to load resume token", void 0, normalizeError(error));
|
|
14876
15105
|
}
|
|
14877
15106
|
return null;
|
|
14878
15107
|
}
|
|
@@ -14898,12 +15127,7 @@ var ResumeTokenStore = class {
|
|
|
14898
15127
|
}
|
|
14899
15128
|
this.logger?.error?.("[Sync] failed to save resume token", lastError);
|
|
14900
15129
|
if (this.strictSave) {
|
|
14901
|
-
throw createError(
|
|
14902
|
-
ErrorCodes.DATABASE_ERROR,
|
|
14903
|
-
"[Sync] failed to save resume token",
|
|
14904
|
-
void 0,
|
|
14905
|
-
lastError ?? void 0
|
|
14906
|
-
);
|
|
15130
|
+
throw createError(ErrorCodes.DATABASE_ERROR, "[Sync] failed to save resume token", void 0, lastError ?? void 0);
|
|
14907
15131
|
}
|
|
14908
15132
|
}
|
|
14909
15133
|
async writePayload(payload) {
|
|
@@ -14936,9 +15160,7 @@ var ResumeTokenStore = class {
|
|
|
14936
15160
|
await copyFile(this.path, `${this.path}.bak`);
|
|
14937
15161
|
} catch (error) {
|
|
14938
15162
|
const code = error?.code;
|
|
14939
|
-
if (code !== "ENOENT")
|
|
14940
|
-
throw error;
|
|
14941
|
-
}
|
|
15163
|
+
if (code !== "ENOENT") throw error;
|
|
14942
15164
|
}
|
|
14943
15165
|
}
|
|
14944
15166
|
async clear() {
|
|
@@ -14950,12 +15172,80 @@ var ResumeTokenStore = class {
|
|
|
14950
15172
|
await unlink(this.path);
|
|
14951
15173
|
} catch (error) {
|
|
14952
15174
|
const code = error?.code;
|
|
14953
|
-
if (code !== "ENOENT")
|
|
14954
|
-
this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14955
|
-
}
|
|
15175
|
+
if (code !== "ENOENT") this.logger?.warn?.("[Sync] failed to clear resume token", error);
|
|
14956
15176
|
}
|
|
14957
15177
|
}
|
|
14958
15178
|
};
|
|
15179
|
+
|
|
15180
|
+
// src/capabilities/sync/index.ts
|
|
15181
|
+
function normalizeCollectionFilter(collections) {
|
|
15182
|
+
if (!collections?.length || collections.includes("*")) {
|
|
15183
|
+
return null;
|
|
15184
|
+
}
|
|
15185
|
+
return new Set(collections);
|
|
15186
|
+
}
|
|
15187
|
+
function validateTargetConfig(target, index) {
|
|
15188
|
+
if (!target || typeof target !== "object") {
|
|
15189
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] must be an object.`);
|
|
15190
|
+
}
|
|
15191
|
+
if (typeof target.name !== "string" || target.name.trim() === "") {
|
|
15192
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].name must be a non-empty string.`);
|
|
15193
|
+
}
|
|
15194
|
+
if (!target.apply && !target.uri && !target.pool) {
|
|
15195
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}] requires one of apply / uri / pool.`);
|
|
15196
|
+
}
|
|
15197
|
+
if (target.uri !== void 0 && (typeof target.uri !== "string" || target.uri.trim() === "")) {
|
|
15198
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].uri must be a non-empty string when provided.`);
|
|
15199
|
+
}
|
|
15200
|
+
if (target.pool !== void 0 && (typeof target.pool !== "string" || target.pool.trim() === "")) {
|
|
15201
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].pool must be a non-empty string when provided.`);
|
|
15202
|
+
}
|
|
15203
|
+
if (target.databaseName !== void 0 && (typeof target.databaseName !== "string" || target.databaseName.trim() === "")) {
|
|
15204
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].databaseName must be a non-empty string when provided.`);
|
|
15205
|
+
}
|
|
15206
|
+
if (target.collections !== void 0 && (!Array.isArray(target.collections) || target.collections.length === 0)) {
|
|
15207
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must be a non-empty array when provided.`);
|
|
15208
|
+
}
|
|
15209
|
+
if (target.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
15210
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].collections must contain non-empty strings.`);
|
|
15211
|
+
}
|
|
15212
|
+
if (target.apply !== void 0 && typeof target.apply !== "function") {
|
|
15213
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `[Sync] targets[${index}].apply must be a function when provided.`);
|
|
15214
|
+
}
|
|
15215
|
+
}
|
|
15216
|
+
function validateSyncConfig(config) {
|
|
15217
|
+
if (!config || typeof config !== "object") {
|
|
15218
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] config must be an object.");
|
|
15219
|
+
}
|
|
15220
|
+
if (typeof config.enabled !== "boolean") {
|
|
15221
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] enabled must be a boolean.");
|
|
15222
|
+
}
|
|
15223
|
+
if (!config.enabled) {
|
|
15224
|
+
return;
|
|
15225
|
+
}
|
|
15226
|
+
if (!Array.isArray(config.targets) || config.targets.length === 0) {
|
|
15227
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] targets must be a non-empty array.");
|
|
15228
|
+
}
|
|
15229
|
+
if (config.collections !== void 0 && (!Array.isArray(config.collections) || config.collections.length === 0)) {
|
|
15230
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must be a non-empty array when provided.");
|
|
15231
|
+
}
|
|
15232
|
+
if (config.collections?.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
15233
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] collections must contain non-empty strings.");
|
|
15234
|
+
}
|
|
15235
|
+
if (config.filter !== void 0 && typeof config.filter !== "function") {
|
|
15236
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] filter must be a function.");
|
|
15237
|
+
}
|
|
15238
|
+
if (config.transform !== void 0 && typeof config.transform !== "function") {
|
|
15239
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "[Sync] transform must be a function.");
|
|
15240
|
+
}
|
|
15241
|
+
validateSyncIdempotencyConfig(config.idempotency);
|
|
15242
|
+
config.targets.forEach((target, index) => {
|
|
15243
|
+
validateTargetConfig(target, index);
|
|
15244
|
+
});
|
|
15245
|
+
if (config.resumeToken) {
|
|
15246
|
+
validateResumeTokenConfig(config.resumeToken);
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
14959
15249
|
var ChangeStreamSyncManager = class {
|
|
14960
15250
|
constructor(options) {
|
|
14961
15251
|
this.targets = [];
|
|
@@ -14964,7 +15254,10 @@ var ChangeStreamSyncManager = class {
|
|
|
14964
15254
|
this.closingStreamPromise = null;
|
|
14965
15255
|
this.changeQueue = Promise.resolve();
|
|
14966
15256
|
this.running = false;
|
|
15257
|
+
this.stopping = false;
|
|
15258
|
+
this.stopPromise = null;
|
|
14967
15259
|
this.fatalSyncError = null;
|
|
15260
|
+
this.generation = 0;
|
|
14968
15261
|
this.stats = {
|
|
14969
15262
|
eventCount: 0,
|
|
14970
15263
|
syncedCount: 0,
|
|
@@ -14997,40 +15290,62 @@ var ChangeStreamSyncManager = class {
|
|
|
14997
15290
|
* @since v1.0.9
|
|
14998
15291
|
*/
|
|
14999
15292
|
async start() {
|
|
15293
|
+
if (this.stopPromise) await this.stopPromise;
|
|
15000
15294
|
if (this.running || !this.config.enabled) return;
|
|
15001
15295
|
if (this.startPromise) return this.startPromise;
|
|
15296
|
+
const generation = ++this.generation;
|
|
15002
15297
|
const startup = (async () => {
|
|
15003
15298
|
await this.closingStreamPromise;
|
|
15299
|
+
await this.changeQueue.catch(() => void 0);
|
|
15300
|
+
this.assertCurrentGeneration(generation, true);
|
|
15004
15301
|
if (this.running) return;
|
|
15005
15302
|
this.fatalSyncError = null;
|
|
15006
|
-
await this.
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
const
|
|
15015
|
-
this.
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
return;
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15303
|
+
await this.closeTargets();
|
|
15304
|
+
let stagedTargets = [];
|
|
15305
|
+
let stream = null;
|
|
15306
|
+
try {
|
|
15307
|
+
await this.validateEnvironment();
|
|
15308
|
+
this.assertCurrentGeneration(generation);
|
|
15309
|
+
stagedTargets = await this.initializeTargets();
|
|
15310
|
+
this.assertCurrentGeneration(generation);
|
|
15311
|
+
const resumeAfter = await this.tokenStore.load();
|
|
15312
|
+
this.assertCurrentGeneration(generation);
|
|
15313
|
+
const options = { fullDocument: "updateLookup" };
|
|
15314
|
+
if (resumeAfter) options.resumeAfter = resumeAfter;
|
|
15315
|
+
stream = this.db.watch(this.buildPipeline(), options);
|
|
15316
|
+
const activeStream = stream;
|
|
15317
|
+
activeStream.on("change", (event) => this.enqueueChange(event, generation));
|
|
15318
|
+
activeStream.on("error", (error) => {
|
|
15319
|
+
if (generation !== this.generation) return;
|
|
15320
|
+
const normalized = normalizeError(error);
|
|
15321
|
+
this.stats.errorCount += 1;
|
|
15322
|
+
this.stats.lastError = normalized;
|
|
15323
|
+
this.logger?.error?.("[Sync] change stream error", normalized);
|
|
15324
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15325
|
+
});
|
|
15326
|
+
activeStream.on("close", () => {
|
|
15327
|
+
if (generation !== this.generation || !this.running || this.changeStream !== activeStream) {
|
|
15328
|
+
this.logger?.debug?.("[Sync] change stream closed");
|
|
15329
|
+
return;
|
|
15330
|
+
}
|
|
15331
|
+
const error = createError(ErrorCodes.CONNECTION_CLOSED, "[Sync] change stream closed unexpectedly");
|
|
15332
|
+
this.stats.errorCount += 1;
|
|
15333
|
+
this.stats.lastError = error;
|
|
15334
|
+
this.logger?.warn?.("[Sync] change stream closed unexpectedly", error);
|
|
15335
|
+
this.stopAfterFatalSyncError(error, generation);
|
|
15336
|
+
});
|
|
15337
|
+
this.assertCurrentGeneration(generation);
|
|
15338
|
+
this.targets.push(...stagedTargets);
|
|
15339
|
+
stagedTargets = [];
|
|
15340
|
+
this.changeStream = activeStream;
|
|
15341
|
+
stream = null;
|
|
15342
|
+
this.running = true;
|
|
15343
|
+
this.stats.startTime = /* @__PURE__ */ new Date();
|
|
15344
|
+
} catch (error) {
|
|
15345
|
+
if (stream) await this.closeChangeStream(stream).catch(() => void 0);
|
|
15346
|
+
for (const target of stagedTargets.reverse()) await target.close().catch(() => void 0);
|
|
15347
|
+
throw error;
|
|
15348
|
+
}
|
|
15034
15349
|
})();
|
|
15035
15350
|
this.startPromise = startup;
|
|
15036
15351
|
try {
|
|
@@ -15044,16 +15359,37 @@ var ChangeStreamSyncManager = class {
|
|
|
15044
15359
|
* @since v1.0.9
|
|
15045
15360
|
*/
|
|
15046
15361
|
async stop() {
|
|
15047
|
-
this.
|
|
15048
|
-
|
|
15049
|
-
const
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15362
|
+
if (this.stopPromise) return this.stopPromise;
|
|
15363
|
+
this.stopping = true;
|
|
15364
|
+
const cleanup = (async () => {
|
|
15365
|
+
if (this.startPromise && !this.running) this.generation += 1;
|
|
15366
|
+
await this.startPromise?.catch(() => void 0);
|
|
15367
|
+
await this.changeQueue.catch(() => void 0);
|
|
15368
|
+
this.generation += 1;
|
|
15369
|
+
this.running = false;
|
|
15370
|
+
const stream = this.changeStream;
|
|
15371
|
+
this.changeStream = null;
|
|
15372
|
+
if (stream) await this.closeChangeStream(stream);
|
|
15373
|
+
else await this.closingStreamPromise;
|
|
15374
|
+
await this.closeTargets();
|
|
15375
|
+
})();
|
|
15376
|
+
this.stopPromise = cleanup;
|
|
15377
|
+
try {
|
|
15378
|
+
await cleanup;
|
|
15379
|
+
} finally {
|
|
15380
|
+
if (this.stopPromise === cleanup) this.stopPromise = null;
|
|
15381
|
+
this.stopping = false;
|
|
15382
|
+
}
|
|
15383
|
+
}
|
|
15384
|
+
assertCurrentGeneration(generation, allowPreviousFatal = false) {
|
|
15385
|
+
if (generation !== this.generation) {
|
|
15386
|
+
throw createError(ErrorCodes.CONNECTION_CLOSED, "[Sync] startup was superseded by stop or restart");
|
|
15387
|
+
}
|
|
15388
|
+
if (!allowPreviousFatal && this.fatalSyncError) throw this.fatalSyncError;
|
|
15389
|
+
}
|
|
15390
|
+
async closeTargets() {
|
|
15054
15391
|
while (this.targets.length > 0) {
|
|
15055
|
-
|
|
15056
|
-
await target?.close();
|
|
15392
|
+
await this.targets.pop()?.close();
|
|
15057
15393
|
}
|
|
15058
15394
|
}
|
|
15059
15395
|
/**
|
|
@@ -15118,11 +15454,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15118
15454
|
return pipeline;
|
|
15119
15455
|
}
|
|
15120
15456
|
async initializeTargets() {
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15457
|
+
const staged = [];
|
|
15458
|
+
try {
|
|
15459
|
+
for (const target of this.config.targets) staged.push(await this.resolveTarget(target));
|
|
15460
|
+
return staged;
|
|
15461
|
+
} catch (error) {
|
|
15462
|
+
for (const target of staged.reverse()) await target.close().catch(() => void 0);
|
|
15463
|
+
throw error;
|
|
15126
15464
|
}
|
|
15127
15465
|
}
|
|
15128
15466
|
async resolveTarget(target) {
|
|
@@ -15159,14 +15497,15 @@ var ChangeStreamSyncManager = class {
|
|
|
15159
15497
|
const client = await this.clientFactory(target.uri, target.options);
|
|
15160
15498
|
return createMongoTarget(target.name, collections, client, target.databaseName ?? this.db.databaseName, true);
|
|
15161
15499
|
}
|
|
15162
|
-
async handleChange(event) {
|
|
15163
|
-
if (this.fatalSyncError) {
|
|
15500
|
+
async handleChange(event, generation = this.generation) {
|
|
15501
|
+
if (generation !== this.generation || !this.running || this.fatalSyncError) {
|
|
15164
15502
|
this.logger?.warn?.("[Sync] skipping queued change after fatal sync error", this.fatalSyncError);
|
|
15165
15503
|
return;
|
|
15166
15504
|
}
|
|
15167
15505
|
this.stats.eventCount += 1;
|
|
15168
15506
|
this.stats.lastEventTime = /* @__PURE__ */ new Date();
|
|
15169
15507
|
if (this.config.filter && !this.config.filter(event)) {
|
|
15508
|
+
await this.saveProcessedToken(event, generation);
|
|
15170
15509
|
return;
|
|
15171
15510
|
}
|
|
15172
15511
|
let document = event.fullDocument;
|
|
@@ -15178,6 +15517,7 @@ var ChangeStreamSyncManager = class {
|
|
|
15178
15517
|
let duplicateTargets = 0;
|
|
15179
15518
|
const targetErrors = [];
|
|
15180
15519
|
for (const target of this.targets) {
|
|
15520
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15181
15521
|
if (target.collections && !target.collections.has(event.ns.coll)) {
|
|
15182
15522
|
continue;
|
|
15183
15523
|
}
|
|
@@ -15194,11 +15534,13 @@ var ChangeStreamSyncManager = class {
|
|
|
15194
15534
|
if (idempotencyKey && this.idempotency.markMode === "start") {
|
|
15195
15535
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "start");
|
|
15196
15536
|
}
|
|
15537
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15197
15538
|
await target.apply(event, document, {
|
|
15198
15539
|
targetName: target.name,
|
|
15199
15540
|
...idempotencyKey ? { idempotencyKey } : {}
|
|
15200
15541
|
});
|
|
15201
15542
|
if (idempotencyKey && this.idempotency.markMode === "success") {
|
|
15543
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15202
15544
|
await this.markTargetEventApplied(idempotencyKey, event, target.name, "success");
|
|
15203
15545
|
}
|
|
15204
15546
|
target.stats.syncCount += 1;
|
|
@@ -15219,26 +15561,30 @@ var ChangeStreamSyncManager = class {
|
|
|
15219
15561
|
}
|
|
15220
15562
|
}
|
|
15221
15563
|
if (targetErrors.length > 0) {
|
|
15222
|
-
this.stopAfterFatalSyncError(targetErrors[0]);
|
|
15564
|
+
this.stopAfterFatalSyncError(targetErrors[0], generation);
|
|
15223
15565
|
return;
|
|
15224
15566
|
}
|
|
15225
|
-
if (
|
|
15226
|
-
|
|
15227
|
-
await this.tokenStore.save(event._id);
|
|
15228
|
-
} catch (error) {
|
|
15229
|
-
const normalized = normalizeError(error);
|
|
15230
|
-
this.stats.tokenSaveErrorCount += 1;
|
|
15231
|
-
this.stats.lastTokenSaveError = normalized;
|
|
15232
|
-
this.stats.lastError = normalized;
|
|
15233
|
-
this.stopAfterFatalSyncError(normalized);
|
|
15234
|
-
throw normalized;
|
|
15235
|
-
}
|
|
15236
|
-
this.stats.syncedCount += 1;
|
|
15567
|
+
if (succeeded === eligibleTargets) {
|
|
15568
|
+
await this.saveProcessedToken(event, generation);
|
|
15237
15569
|
if (duplicateTargets > 0 && duplicateTargets === eligibleTargets) {
|
|
15238
15570
|
this.stats.duplicateEventCount += 1;
|
|
15239
15571
|
}
|
|
15240
15572
|
}
|
|
15241
15573
|
}
|
|
15574
|
+
async saveProcessedToken(event, generation) {
|
|
15575
|
+
if (generation !== this.generation || this.fatalSyncError || !this.running) return;
|
|
15576
|
+
try {
|
|
15577
|
+
await this.tokenStore.save(event._id);
|
|
15578
|
+
} catch (error) {
|
|
15579
|
+
const normalized = normalizeError(error);
|
|
15580
|
+
this.stats.tokenSaveErrorCount += 1;
|
|
15581
|
+
this.stats.lastTokenSaveError = normalized;
|
|
15582
|
+
this.stats.lastError = normalized;
|
|
15583
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15584
|
+
throw normalized;
|
|
15585
|
+
}
|
|
15586
|
+
if (generation === this.generation && !this.fatalSyncError && this.running) this.stats.syncedCount += 1;
|
|
15587
|
+
}
|
|
15242
15588
|
buildTargetIdempotencyKey(targetName, event) {
|
|
15243
15589
|
if (!this.idempotency.enabled) {
|
|
15244
15590
|
return null;
|
|
@@ -15261,7 +15607,8 @@ var ChangeStreamSyncManager = class {
|
|
|
15261
15607
|
ts: Date.now()
|
|
15262
15608
|
}, this.idempotency.ttl));
|
|
15263
15609
|
}
|
|
15264
|
-
stopAfterFatalSyncError(error) {
|
|
15610
|
+
stopAfterFatalSyncError(error, generation) {
|
|
15611
|
+
if (generation !== this.generation) return;
|
|
15265
15612
|
this.fatalSyncError = error;
|
|
15266
15613
|
this.running = false;
|
|
15267
15614
|
const stream = this.changeStream;
|
|
@@ -15279,12 +15626,14 @@ var ChangeStreamSyncManager = class {
|
|
|
15279
15626
|
if (this.closingStreamPromise === closing) this.closingStreamPromise = null;
|
|
15280
15627
|
});
|
|
15281
15628
|
}
|
|
15282
|
-
enqueueChange(event) {
|
|
15283
|
-
this.
|
|
15629
|
+
enqueueChange(event, generation) {
|
|
15630
|
+
if (generation !== this.generation || !this.running || this.stopping || this.fatalSyncError) return;
|
|
15631
|
+
this.changeQueue = this.changeQueue.then(() => this.handleChange(event, generation)).catch((error) => {
|
|
15284
15632
|
const normalized = normalizeError(error);
|
|
15285
15633
|
this.stats.errorCount += 1;
|
|
15286
15634
|
this.stats.lastError = normalized;
|
|
15287
15635
|
this.logger?.error?.("[Sync] change handling failed", normalized);
|
|
15636
|
+
this.stopAfterFatalSyncError(normalized, generation);
|
|
15288
15637
|
});
|
|
15289
15638
|
}
|
|
15290
15639
|
};
|
|
@@ -15713,7 +16062,9 @@ var SSHTunnelSSH2 = class {
|
|
|
15713
16062
|
passphrase,
|
|
15714
16063
|
port = 22,
|
|
15715
16064
|
readyTimeout = 2e4,
|
|
15716
|
-
keepaliveInterval = 3e4
|
|
16065
|
+
keepaliveInterval = 3e4,
|
|
16066
|
+
hostHash,
|
|
16067
|
+
hostVerifier
|
|
15717
16068
|
} = this._sshConfig;
|
|
15718
16069
|
if (!host || !username) {
|
|
15719
16070
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH config requires: host, username");
|
|
@@ -15722,6 +16073,8 @@ var SSHTunnelSSH2 = class {
|
|
|
15722
16073
|
throw createError(ErrorCodes.INVALID_CONFIG, "SSH authentication required: provide password, privateKey, or privateKeyPath");
|
|
15723
16074
|
}
|
|
15724
16075
|
const config = { host, port, username, readyTimeout, keepaliveInterval };
|
|
16076
|
+
if (hostHash !== void 0) config.hostHash = hostHash;
|
|
16077
|
+
if (hostVerifier !== void 0) config.hostVerifier = hostVerifier;
|
|
15725
16078
|
if (password) {
|
|
15726
16079
|
config.password = password;
|
|
15727
16080
|
} else if (privateKey) {
|
|
@@ -15740,6 +16093,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15740
16093
|
return new Promise((resolve, reject) => {
|
|
15741
16094
|
const ssh = new SshClient();
|
|
15742
16095
|
let settled = false;
|
|
16096
|
+
const fail = (err) => {
|
|
16097
|
+
if (settled) {
|
|
16098
|
+
this.markDisconnected();
|
|
16099
|
+
return;
|
|
16100
|
+
}
|
|
16101
|
+
settled = true;
|
|
16102
|
+
this.isConnected = false;
|
|
16103
|
+
this.localPort = null;
|
|
16104
|
+
this.server = null;
|
|
16105
|
+
this.sshClient = null;
|
|
16106
|
+
for (const socket of this._activeSockets) socket.destroy();
|
|
16107
|
+
this._activeSockets.clear();
|
|
16108
|
+
void closeServer(server);
|
|
16109
|
+
try {
|
|
16110
|
+
ssh.end();
|
|
16111
|
+
} catch {
|
|
16112
|
+
}
|
|
16113
|
+
reject(err);
|
|
16114
|
+
};
|
|
15743
16115
|
const server = net.createServer((socket) => {
|
|
15744
16116
|
this._activeSockets.add(socket);
|
|
15745
16117
|
socket.on("close", () => this._activeSockets.delete(socket));
|
|
@@ -15765,12 +16137,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15765
16137
|
}
|
|
15766
16138
|
);
|
|
15767
16139
|
});
|
|
15768
|
-
server.on("error",
|
|
15769
|
-
if (!settled) {
|
|
15770
|
-
settled = true;
|
|
15771
|
-
reject(err);
|
|
15772
|
-
}
|
|
15773
|
-
});
|
|
16140
|
+
server.on("error", fail);
|
|
15774
16141
|
server.listen(this._sshConfig.localPort ?? 0, "127.0.0.1", () => {
|
|
15775
16142
|
this.server = server;
|
|
15776
16143
|
const addr = server.address();
|
|
@@ -15785,25 +16152,25 @@ var SSHTunnelSSH2 = class {
|
|
|
15785
16152
|
});
|
|
15786
16153
|
ssh.on("error", (err) => {
|
|
15787
16154
|
if (!settled) {
|
|
15788
|
-
|
|
15789
|
-
server.close();
|
|
15790
|
-
reject(err);
|
|
16155
|
+
fail(err);
|
|
15791
16156
|
return;
|
|
15792
16157
|
}
|
|
15793
16158
|
this.markDisconnected();
|
|
15794
16159
|
});
|
|
15795
16160
|
const handleDisconnect = () => {
|
|
15796
16161
|
if (!settled) {
|
|
15797
|
-
|
|
15798
|
-
server.close();
|
|
15799
|
-
reject(createError(ErrorCodes.CONNECTION_FAILED, "SSH connection closed before the tunnel became ready"));
|
|
16162
|
+
fail(createError(ErrorCodes.CONNECTION_FAILED, "SSH connection closed before the tunnel became ready"));
|
|
15800
16163
|
return;
|
|
15801
16164
|
}
|
|
15802
16165
|
this.markDisconnected();
|
|
15803
16166
|
};
|
|
15804
16167
|
ssh.on("close", handleDisconnect);
|
|
15805
16168
|
ssh.on("end", handleDisconnect);
|
|
15806
|
-
|
|
16169
|
+
try {
|
|
16170
|
+
ssh.connect(authConfig);
|
|
16171
|
+
} catch (err) {
|
|
16172
|
+
fail(err);
|
|
16173
|
+
}
|
|
15807
16174
|
});
|
|
15808
16175
|
});
|
|
15809
16176
|
}
|
|
@@ -15834,6 +16201,7 @@ var SSHTunnelSSH2 = class {
|
|
|
15834
16201
|
ssh?.end();
|
|
15835
16202
|
}
|
|
15836
16203
|
markDisconnected() {
|
|
16204
|
+
const ssh = this.sshClient;
|
|
15837
16205
|
this.isConnected = false;
|
|
15838
16206
|
this.localPort = null;
|
|
15839
16207
|
this.sshClient = null;
|
|
@@ -15844,6 +16212,10 @@ var SSHTunnelSSH2 = class {
|
|
|
15844
16212
|
}
|
|
15845
16213
|
this._activeSockets.clear();
|
|
15846
16214
|
void closeServer(server);
|
|
16215
|
+
try {
|
|
16216
|
+
ssh?.end();
|
|
16217
|
+
} catch {
|
|
16218
|
+
}
|
|
15847
16219
|
}
|
|
15848
16220
|
};
|
|
15849
16221
|
function parseHostFromUri(uri) {
|
|
@@ -15930,12 +16302,17 @@ async function prepareSshTunnelConnectConfig(connectConfig, databaseName, logger
|
|
|
15930
16302
|
const remotePort = Number(cfg["dstPort"] ?? rawCfg["remotePort"] ?? rawCfg["mongoPort"] ?? parsePortFromUri(uri));
|
|
15931
16303
|
const tunnel = new SSHTunnelSSH2(sshCfg, remoteHost, remotePort, { name: databaseName });
|
|
15932
16304
|
logger.info?.(`[SSH] Establishing tunnel to ${remoteHost}:${remotePort} via ${String(cfg["host"])}`);
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
16305
|
+
try {
|
|
16306
|
+
await tunnel.connect();
|
|
16307
|
+
logger.info?.(`[SSH] Tunnel ready \u2014 local address: ${tunnel.getLocalAddress()}`);
|
|
16308
|
+
return {
|
|
16309
|
+
tunnel,
|
|
16310
|
+
connectConfig: connectConfig?.uri ? { ...connectConfig, uri: tunnel.getTunnelUri("mongodb", connectConfig.uri) } : connectConfig
|
|
16311
|
+
};
|
|
16312
|
+
} catch (error) {
|
|
16313
|
+
await tunnel.close();
|
|
16314
|
+
throw error;
|
|
16315
|
+
}
|
|
15939
16316
|
}
|
|
15940
16317
|
|
|
15941
16318
|
// src/entry/runtime-schema-dsl.ts
|
|
@@ -16230,7 +16607,7 @@ function makePageResult(rows, ctx) {
|
|
|
16230
16607
|
import { createHash as createHash4 } from "node:crypto";
|
|
16231
16608
|
import os2 from "node:os";
|
|
16232
16609
|
import path3 from "node:path";
|
|
16233
|
-
import { BSON } from "mongodb";
|
|
16610
|
+
import { BSON as BSON2 } from "mongodb";
|
|
16234
16611
|
|
|
16235
16612
|
// src/capabilities/data-tasks/support.ts
|
|
16236
16613
|
var INDEX_OPTION_KEYS = [
|
|
@@ -16338,7 +16715,7 @@ function canonicalize(value) {
|
|
|
16338
16715
|
return value;
|
|
16339
16716
|
}
|
|
16340
16717
|
function canonicalStringify(value) {
|
|
16341
|
-
return JSON.stringify(canonicalize(
|
|
16718
|
+
return JSON.stringify(canonicalize(BSON2.EJSON.serialize(value, { relaxed: false })));
|
|
16342
16719
|
}
|
|
16343
16720
|
function hashDataTaskValue(value) {
|
|
16344
16721
|
return createHash4("sha256").update(canonicalStringify(value)).digest("hex");
|
|
@@ -16827,9 +17204,8 @@ var MonSQLizeRuntime = class {
|
|
|
16827
17204
|
driverError = error instanceof Error ? error.message : String(error);
|
|
16828
17205
|
}
|
|
16829
17206
|
}
|
|
16830
|
-
const
|
|
16831
|
-
const poolHealth =
|
|
16832
|
-
const distributedStats = this._distributedInvalidator?.getStats() ?? null;
|
|
17207
|
+
const health = summarizeRuntimeHealth(this.options.cache, this._cache, this._poolManager, this._distributedInvalidator);
|
|
17208
|
+
const { cacheStats, poolHealth, distributedStats, cacheEnabled, distributedEnabled, cacheProbeStatus, distributedStatus, poolsStatus } = health;
|
|
16833
17209
|
const connected = this._connected && driverConnected;
|
|
16834
17210
|
return {
|
|
16835
17211
|
status: connected ? "up" : "down",
|
|
@@ -16837,7 +17213,7 @@ var MonSQLizeRuntime = class {
|
|
|
16837
17213
|
driver: { connected: driverConnected, ...driverError ? { error: driverError } : {} },
|
|
16838
17214
|
defaults: this.getDefaults(),
|
|
16839
17215
|
cache: {
|
|
16840
|
-
enabled:
|
|
17216
|
+
enabled: cacheEnabled,
|
|
16841
17217
|
stats: cacheStats,
|
|
16842
17218
|
pools: poolHealth,
|
|
16843
17219
|
distributed: distributedStats
|
|
@@ -16849,18 +17225,18 @@ var MonSQLizeRuntime = class {
|
|
|
16849
17225
|
...driverError ? { error: driverError } : {}
|
|
16850
17226
|
},
|
|
16851
17227
|
cache: {
|
|
16852
|
-
status:
|
|
16853
|
-
enabled:
|
|
17228
|
+
status: cacheProbeStatus,
|
|
17229
|
+
enabled: cacheEnabled,
|
|
16854
17230
|
hasStats: cacheStats !== void 0
|
|
16855
17231
|
},
|
|
16856
17232
|
distributedCacheInvalidator: {
|
|
16857
|
-
status:
|
|
16858
|
-
enabled:
|
|
17233
|
+
status: distributedStatus,
|
|
17234
|
+
enabled: distributedEnabled,
|
|
16859
17235
|
stats: distributedStats
|
|
16860
17236
|
},
|
|
16861
17237
|
pools: {
|
|
16862
|
-
status:
|
|
16863
|
-
enabled:
|
|
17238
|
+
status: poolsStatus,
|
|
17239
|
+
enabled: this._poolManager !== null,
|
|
16864
17240
|
health: poolHealth
|
|
16865
17241
|
}
|
|
16866
17242
|
},
|
|
@@ -16928,7 +17304,7 @@ var MonSQLizeRuntime = class {
|
|
|
16928
17304
|
requireCompatDbInstance(this);
|
|
16929
17305
|
return {
|
|
16930
17306
|
collection: (collectionName) => this.scopedCollection(collectionName, { database: name }),
|
|
16931
|
-
model: (
|
|
17307
|
+
model: (model) => this.scopedModel(model, { database: name })
|
|
16932
17308
|
};
|
|
16933
17309
|
}
|
|
16934
17310
|
pool(poolName) {
|
|
@@ -16960,7 +17336,8 @@ var MonSQLizeRuntime = class {
|
|
|
16960
17336
|
});
|
|
16961
17337
|
}
|
|
16962
17338
|
// Model accessors ---------------------------------------------------------
|
|
16963
|
-
scopedModel(
|
|
17339
|
+
scopedModel(model, options = {}) {
|
|
17340
|
+
const name = typeof model === "string" ? model : model.collectionName;
|
|
16964
17341
|
const dbInstance = requireCompatDbInstance(this);
|
|
16965
17342
|
if (this._client) {
|
|
16966
17343
|
return this.createModelInstance(name, options);
|
|
@@ -16984,7 +17361,8 @@ var MonSQLizeRuntime = class {
|
|
|
16984
17361
|
schemaEngine: this._schemaDslEngine
|
|
16985
17362
|
});
|
|
16986
17363
|
}
|
|
16987
|
-
model(
|
|
17364
|
+
model(model) {
|
|
17365
|
+
const name = typeof model === "string" ? model : model.collectionName;
|
|
16988
17366
|
if (this._client) {
|
|
16989
17367
|
this.ensureConnected();
|
|
16990
17368
|
return this.createModelInstance(name, {
|
|
@@ -17220,7 +17598,7 @@ var MonSQLizeRuntime = class {
|
|
|
17220
17598
|
};
|
|
17221
17599
|
|
|
17222
17600
|
// src/capabilities/data-tasks/job-planner.ts
|
|
17223
|
-
import { BSON as
|
|
17601
|
+
import { BSON as BSON3 } from "mongodb";
|
|
17224
17602
|
|
|
17225
17603
|
// src/capabilities/data-tasks/document-utils.ts
|
|
17226
17604
|
function isPlainRecord(value) {
|
|
@@ -17702,7 +18080,7 @@ async function planCollection(sourceHost, targetHost, config, sampleSize) {
|
|
|
17702
18080
|
before: change.before,
|
|
17703
18081
|
after: change.after
|
|
17704
18082
|
}));
|
|
17705
|
-
const backupBytes = changes.reduce((total, change) => total + Buffer.byteLength(`${
|
|
18083
|
+
const backupBytes = changes.reduce((total, change) => total + Buffer.byteLength(`${BSON3.EJSON.stringify({
|
|
17706
18084
|
...change,
|
|
17707
18085
|
beforeHash: hashDataTaskValue(change.before),
|
|
17708
18086
|
plannedAfterHash: hashDataTaskValue(change.after)
|
|
@@ -17787,7 +18165,7 @@ import { mkdir as mkdir2, open as open2, readFile as readFile2, rename as rename
|
|
|
17787
18165
|
import path4 from "node:path";
|
|
17788
18166
|
import { gzip, gunzip } from "node:zlib";
|
|
17789
18167
|
import { promisify } from "node:util";
|
|
17790
|
-
import { BSON as
|
|
18168
|
+
import { BSON as BSON4, Int32, Long } from "mongodb";
|
|
17791
18169
|
var gzipAsync = promisify(gzip);
|
|
17792
18170
|
var gunzipAsync = promisify(gunzip);
|
|
17793
18171
|
var BACKUP_TARGET = /* @__PURE__ */ Symbol("dataTaskBackupTarget");
|
|
@@ -17804,10 +18182,31 @@ function checksum(buffer) {
|
|
|
17804
18182
|
return createHash5("sha256").update(buffer).digest("hex");
|
|
17805
18183
|
}
|
|
17806
18184
|
function ejsonLine(value) {
|
|
17807
|
-
return
|
|
18185
|
+
return BSON4.EJSON.stringify(value, { relaxed: false });
|
|
17808
18186
|
}
|
|
17809
18187
|
function ejsonManifest(value) {
|
|
17810
|
-
return
|
|
18188
|
+
return BSON4.EJSON.stringify(value, void 0, 2, { relaxed: false });
|
|
18189
|
+
}
|
|
18190
|
+
function manifestInteger(value, field, minimum) {
|
|
18191
|
+
const numeric = value instanceof Int32 ? value.valueOf() : Long.isLong(value) ? value.toBigInt() : value;
|
|
18192
|
+
if (typeof numeric === "bigint") {
|
|
18193
|
+
if (numeric < BigInt(minimum) || numeric > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
18194
|
+
throw new Error(`invalid backup ${field}`);
|
|
18195
|
+
}
|
|
18196
|
+
return Number(numeric);
|
|
18197
|
+
}
|
|
18198
|
+
if (typeof numeric !== "number" || !Number.isSafeInteger(numeric) || numeric < minimum) {
|
|
18199
|
+
throw new Error(`invalid backup ${field}`);
|
|
18200
|
+
}
|
|
18201
|
+
return numeric;
|
|
18202
|
+
}
|
|
18203
|
+
function decodeManifestControls(raw) {
|
|
18204
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("invalid backup manifest");
|
|
18205
|
+
const manifest = raw;
|
|
18206
|
+
manifest.version = manifestInteger(manifest.version, "manifest", 1);
|
|
18207
|
+
manifest.entryCount = manifestInteger(manifest.entryCount, "entry count", 0);
|
|
18208
|
+
if (manifest.maxBytes !== void 0) manifest.maxBytes = manifestInteger(manifest.maxBytes, "max bytes", 1);
|
|
18209
|
+
return manifest;
|
|
17811
18210
|
}
|
|
17812
18211
|
function sanitizeName(value) {
|
|
17813
18212
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "data-task";
|
|
@@ -17985,10 +18384,9 @@ async function createRestoreSafetyBackup(source, entries, beforeIndexes, targetH
|
|
|
17985
18384
|
}
|
|
17986
18385
|
async function readDataTaskBackup(ref) {
|
|
17987
18386
|
try {
|
|
17988
|
-
const manifest =
|
|
18387
|
+
const manifest = decodeManifestControls(BSON4.EJSON.parse(await readFile2(ref.manifestPath, "utf8"), { relaxed: false }));
|
|
17989
18388
|
if (manifest.kind !== "monsqlize-data-task-backup" || manifest.version !== 1 || manifest.runId !== ref.runId) throw new Error("invalid backup manifest");
|
|
17990
18389
|
if (manifest.compression !== "gzip" && manifest.compression !== "none") throw new Error("invalid backup compression");
|
|
17991
|
-
if (!Number.isSafeInteger(manifest.entryCount) || manifest.entryCount < 0) throw new Error("invalid backup entry count");
|
|
17992
18390
|
if (typeof manifest.checksum !== "string" || !/^[a-f0-9]{64}$/i.test(manifest.checksum)) throw new Error("invalid backup checksum");
|
|
17993
18391
|
if (typeof manifest.dataFile !== "string" || manifest.dataFile.length === 0 || path4.isAbsolute(manifest.dataFile) || path4.basename(manifest.dataFile) !== manifest.dataFile) {
|
|
17994
18392
|
throw new Error("backup data file must stay beside its manifest");
|
|
@@ -18001,7 +18399,7 @@ async function readDataTaskBackup(ref) {
|
|
|
18001
18399
|
if (digest !== manifest.checksum || digest !== ref.checksum) throw new Error("backup checksum mismatch");
|
|
18002
18400
|
const plain = manifest.compression === "gzip" ? await gunzipAsync(contents) : contents;
|
|
18003
18401
|
const lines = plain.toString("utf8").split(/\r?\n/).filter(Boolean);
|
|
18004
|
-
const entries = lines.map((line) =>
|
|
18402
|
+
const entries = lines.map((line) => BSON4.EJSON.parse(line, { relaxed: false }));
|
|
18005
18403
|
if (entries.length !== manifest.entryCount) throw new Error("backup entry count mismatch");
|
|
18006
18404
|
return { ref: attachTarget({ ...ref }, backupTargetHint(ref)), manifest, entries };
|
|
18007
18405
|
} catch (error) {
|
|
@@ -18319,6 +18717,7 @@ async function acquireDataTaskLease(host, options) {
|
|
|
18319
18717
|
}
|
|
18320
18718
|
|
|
18321
18719
|
// src/capabilities/data-tasks/job-restore.ts
|
|
18720
|
+
import { Int32 as Int322, Long as Long2 } from "mongodb";
|
|
18322
18721
|
async function findCurrent(collection, identity) {
|
|
18323
18722
|
let chain = collection.find(identity);
|
|
18324
18723
|
if (typeof chain.limit === "function") chain = chain.limit(2);
|
|
@@ -18339,13 +18738,20 @@ function restoreApproval(hashes) {
|
|
|
18339
18738
|
};
|
|
18340
18739
|
return { ...payload, token: hashDataTaskValue(payload) };
|
|
18341
18740
|
}
|
|
18741
|
+
function comparableIndexKey(key) {
|
|
18742
|
+
return Object.fromEntries(Object.entries(key).map(([field, value]) => {
|
|
18743
|
+
if (value instanceof Int322) return [field, value.valueOf()];
|
|
18744
|
+
if (Long2.isLong(value) && value.toBigInt() <= BigInt(Number.MAX_SAFE_INTEGER) && value.toBigInt() >= BigInt(Number.MIN_SAFE_INTEGER)) return [field, Number(value.toBigInt())];
|
|
18745
|
+
return [field, value];
|
|
18746
|
+
}));
|
|
18747
|
+
}
|
|
18342
18748
|
function exactIndex(index, current) {
|
|
18343
|
-
return classifyDataTaskIndexes([{ key: index.key, name: index.name, options: index.options }], current)[0]?.status === "existing";
|
|
18749
|
+
return classifyDataTaskIndexes([{ key: comparableIndexKey(index.key), name: index.name, options: index.options }], current)[0]?.status === "existing";
|
|
18344
18750
|
}
|
|
18345
18751
|
function pendingIndexMatches(index, current) {
|
|
18346
18752
|
return current.filter((candidate) => {
|
|
18347
18753
|
if (index.name && candidate.name !== index.name) return false;
|
|
18348
|
-
return classifyDataTaskIndexes([{ key: index.key, options: index.options }], [candidate])[0]?.status === "existing";
|
|
18754
|
+
return classifyDataTaskIndexes([{ key: comparableIndexKey(index.key), options: index.options }], [candidate])[0]?.status === "existing";
|
|
18349
18755
|
});
|
|
18350
18756
|
}
|
|
18351
18757
|
function plannedDocumentMatches(current, entry) {
|
|
@@ -18613,7 +19019,7 @@ async function restoreDataTaskPlan(plan, target, targetHint, lease) {
|
|
|
18613
19019
|
await updateDataTaskBackup(safety, (manifest) => {
|
|
18614
19020
|
manifest.pendingIndexes = [...manifest.pendingIndexes ?? [], pending];
|
|
18615
19021
|
});
|
|
18616
|
-
await collection.createIndex(index.key, { ...index.options, name: index.name });
|
|
19022
|
+
await collection.createIndex(comparableIndexKey(index.key), { ...index.options, name: index.name });
|
|
18617
19023
|
if (!exactIndex(index, await collection.listIndexes())) {
|
|
18618
19024
|
throw new DataTaskJobError("RESTORE_FAILED", `index "${index.name}" failed immediate readback.`, "restore");
|
|
18619
19025
|
}
|
|
@@ -18785,6 +19191,7 @@ MonSQLize.PoolSelector = PoolSelector;
|
|
|
18785
19191
|
MonSQLize.PoolStats = PoolStatsManager;
|
|
18786
19192
|
MonSQLize.validatePoolConfig = validatePoolConfig;
|
|
18787
19193
|
MonSQLize.validatePoolConfigSafe = validatePoolConfigSafe;
|
|
19194
|
+
MonSQLize.defineModel = defineModel;
|
|
18788
19195
|
MonSQLize.Model = Model;
|
|
18789
19196
|
MonSQLize.ModelInstance = ModelInstance;
|
|
18790
19197
|
MonSQLize.expr = expr;
|
|
@@ -18874,6 +19281,7 @@ export {
|
|
|
18874
19281
|
dataTasks,
|
|
18875
19282
|
decodeCursor2 as decodeCursor,
|
|
18876
19283
|
index_default as default,
|
|
19284
|
+
defineModel,
|
|
18877
19285
|
encodeCursor2 as encodeCursor,
|
|
18878
19286
|
expr,
|
|
18879
19287
|
generateQueryHash,
|