monsqlize 2.0.2 → 2.0.4
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 +13 -5
- package/README.md +66 -20
- package/changelogs/README.md +8 -4
- package/changelogs/v2.0.0.md +1 -1
- package/changelogs/v2.0.3.md +58 -0
- package/changelogs/v2.0.4.md +61 -0
- package/dist/cjs/index.cjs +911 -213
- package/dist/cjs/transaction/Transaction.cjs +88 -3
- package/dist/cjs/transaction/TransactionManager.cjs +135 -11
- package/dist/esm/index.mjs +906 -208
- package/dist/types/collection.d.ts +5 -3
- package/dist/types/model.d.ts +279 -175
- package/dist/types/mongodb.d.ts +8 -1
- package/dist/types/monsqlize.d.ts +37 -4
- package/dist/types/pool.d.ts +1 -1
- package/dist/types/runtime.d.ts +33 -8
- package/dist/types/transaction.d.ts +12 -0
- package/package.json +23 -22
package/dist/cjs/index.cjs
CHANGED
|
@@ -145,8 +145,8 @@ function createQueryTimeoutError(timeoutMs) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// src/capabilities/cache/redis-cache-adapter.ts
|
|
148
|
-
var LEGACY_INVALID_REDIS_ARG_ERROR = "redisUrlOrInstance
|
|
149
|
-
var LEGACY_IOREDIS_MISSING_ERROR = "ioredis
|
|
148
|
+
var LEGACY_INVALID_REDIS_ARG_ERROR = "redisUrlOrInstance must be a Redis URL string or an ioredis instance";
|
|
149
|
+
var LEGACY_IOREDIS_MISSING_ERROR = "Unable to load ioredis. monsqlize installs ioredis by default; check package installation completeness or pass an existing ioredis instance";
|
|
150
150
|
function isMissingIoredisError(error) {
|
|
151
151
|
if (!(error instanceof Error)) {
|
|
152
152
|
return false;
|
|
@@ -156,14 +156,14 @@ function isMissingIoredisError(error) {
|
|
|
156
156
|
function createLegacyRedisError(message, code = ErrorCodes.INVALID_ARGUMENT) {
|
|
157
157
|
return createError(code, message);
|
|
158
158
|
}
|
|
159
|
-
function createRedisCacheAdapter(redisUrlOrInstance) {
|
|
159
|
+
function createRedisCacheAdapter(redisUrlOrInstance, options) {
|
|
160
160
|
if (typeof redisUrlOrInstance === "string") {
|
|
161
161
|
const redisUrl = redisUrlOrInstance.trim();
|
|
162
162
|
if (!redisUrl) {
|
|
163
163
|
throw createLegacyRedisError(LEGACY_INVALID_REDIS_ARG_ERROR);
|
|
164
164
|
}
|
|
165
165
|
try {
|
|
166
|
-
return (0, import_redis.createRedisCacheAdapter)(redisUrl);
|
|
166
|
+
return (0, import_redis.createRedisCacheAdapter)(redisUrl, options);
|
|
167
167
|
} catch (error) {
|
|
168
168
|
if (isMissingIoredisError(error)) {
|
|
169
169
|
throw createLegacyRedisError(LEGACY_IOREDIS_MISSING_ERROR, ErrorCodes.CACHE_UNAVAILABLE);
|
|
@@ -172,7 +172,7 @@ function createRedisCacheAdapter(redisUrlOrInstance) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
if (redisUrlOrInstance && typeof redisUrlOrInstance === "object") {
|
|
175
|
-
return (0, import_redis.createRedisCacheAdapter)(redisUrlOrInstance);
|
|
175
|
+
return (0, import_redis.createRedisCacheAdapter)(redisUrlOrInstance, options);
|
|
176
176
|
}
|
|
177
177
|
throw createLegacyRedisError(LEGACY_INVALID_REDIS_ARG_ERROR);
|
|
178
178
|
}
|
|
@@ -182,7 +182,7 @@ var import_crypto = require("crypto");
|
|
|
182
182
|
var DistributedCacheInvalidator = class {
|
|
183
183
|
constructor(options) {
|
|
184
184
|
if (!options.cache) {
|
|
185
|
-
throw
|
|
185
|
+
throw createError(ErrorCodes.CACHE_UNAVAILABLE, "DistributedCacheInvalidator requires a cache instance");
|
|
186
186
|
}
|
|
187
187
|
this._cache = options.cache;
|
|
188
188
|
this._logger = options.logger ?? null;
|
|
@@ -200,7 +200,7 @@ var DistributedCacheInvalidator = class {
|
|
|
200
200
|
this.pub = new Redis(options.redisUrl);
|
|
201
201
|
this.sub = new Redis(options.redisUrl);
|
|
202
202
|
} else {
|
|
203
|
-
throw
|
|
203
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "DistributedCacheInvalidator requires either redis or redisUrl");
|
|
204
204
|
}
|
|
205
205
|
this._setupSubscription();
|
|
206
206
|
}
|
|
@@ -380,17 +380,17 @@ var _PopulatePromise = class _PopulatePromise {
|
|
|
380
380
|
/**
|
|
381
381
|
* Append a populate path and return a new PopulatePromise (chainable).
|
|
382
382
|
*/
|
|
383
|
-
populate(
|
|
383
|
+
populate(path3, options) {
|
|
384
384
|
const toConfig = (item) => {
|
|
385
385
|
if (typeof item !== "string" && (typeof item !== "object" || item === null || Array.isArray(item))) {
|
|
386
386
|
throw createError(ErrorCodes.INVALID_ARGUMENT, "populate param must be a string, array, or object");
|
|
387
387
|
}
|
|
388
388
|
return typeof item === "string" ? { path: item, ...options } : { ...item, ...options };
|
|
389
389
|
};
|
|
390
|
-
if (Array.isArray(
|
|
391
|
-
return new _PopulatePromise(this.executor, [...this.paths, ...
|
|
390
|
+
if (Array.isArray(path3)) {
|
|
391
|
+
return new _PopulatePromise(this.executor, [...this.paths, ...path3.map(toConfig)]);
|
|
392
392
|
}
|
|
393
|
-
const config = toConfig(
|
|
393
|
+
const config = toConfig(path3);
|
|
394
394
|
return new _PopulatePromise(this.executor, [...this.paths, config]);
|
|
395
395
|
}
|
|
396
396
|
/**
|
|
@@ -504,8 +504,8 @@ function validateRelationConfig(name, config) {
|
|
|
504
504
|
throw createError(ErrorCodes.INVALID_ARGUMENT, `relations.single must be a boolean`);
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
|
-
function normalizePopulateConfig(
|
|
508
|
-
return typeof
|
|
507
|
+
function normalizePopulateConfig(path3) {
|
|
508
|
+
return typeof path3 === "string" ? { path: path3 } : path3;
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
// src/capabilities/model/model-registry.ts
|
|
@@ -656,8 +656,8 @@ function groupBy(values, keySelector) {
|
|
|
656
656
|
}
|
|
657
657
|
return map;
|
|
658
658
|
}
|
|
659
|
-
function getByPath(source,
|
|
660
|
-
return
|
|
659
|
+
function getByPath(source, path3) {
|
|
660
|
+
return path3.split(".").reduce((current, key) => {
|
|
661
661
|
if (!current || typeof current !== "object") {
|
|
662
662
|
return void 0;
|
|
663
663
|
}
|
|
@@ -712,8 +712,8 @@ function resolveRegisteredCollectionName(registered, fallback) {
|
|
|
712
712
|
const definition = registered.definition;
|
|
713
713
|
return definition.collection ?? definition.name ?? registered.collectionName;
|
|
714
714
|
}
|
|
715
|
-
async function populateModelPath(context, docs,
|
|
716
|
-
const config = normalizePopulateConfig(
|
|
715
|
+
async function populateModelPath(context, docs, path3) {
|
|
716
|
+
const config = normalizePopulateConfig(path3);
|
|
717
717
|
if (docs.length === 0) {
|
|
718
718
|
return docs;
|
|
719
719
|
}
|
|
@@ -830,8 +830,8 @@ function hydrateModelDocument(context, doc) {
|
|
|
830
830
|
populate: {
|
|
831
831
|
configurable: true,
|
|
832
832
|
enumerable: false,
|
|
833
|
-
value: (
|
|
834
|
-
const paths = Array.isArray(
|
|
833
|
+
value: (path3) => {
|
|
834
|
+
const paths = Array.isArray(path3) ? path3 : [path3];
|
|
835
835
|
return new PopulatePromise(
|
|
836
836
|
(resolvedPaths) => context.populateDocument(hydrated, resolvedPaths),
|
|
837
837
|
paths
|
|
@@ -997,6 +997,75 @@ function stableIndexStringify(value) {
|
|
|
997
997
|
}
|
|
998
998
|
return JSON.stringify(value) ?? "undefined";
|
|
999
999
|
}
|
|
1000
|
+
function getIndexOptionName(options) {
|
|
1001
|
+
return typeof options.name === "string" && options.name.length > 0 ? options.name : void 0;
|
|
1002
|
+
}
|
|
1003
|
+
function summarizeIndexError(error) {
|
|
1004
|
+
if (error instanceof Error) {
|
|
1005
|
+
const record = error;
|
|
1006
|
+
return {
|
|
1007
|
+
name: error.name,
|
|
1008
|
+
message: error.message,
|
|
1009
|
+
code: record.code ?? record.codeName
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
message: String(error)
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function isRecord(value) {
|
|
1017
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1018
|
+
}
|
|
1019
|
+
function getExistingIndexKey(index) {
|
|
1020
|
+
return index.key;
|
|
1021
|
+
}
|
|
1022
|
+
function declaredOptionEntries(options) {
|
|
1023
|
+
return Object.entries(options).filter(([name, value]) => {
|
|
1024
|
+
if (value === void 0) return false;
|
|
1025
|
+
if (name === "background") return false;
|
|
1026
|
+
return true;
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
function indexOptionsMatch(existing, declared) {
|
|
1030
|
+
if (stableIndexStringify(getExistingIndexKey(existing)) !== stableIndexStringify(declared.key)) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
for (const [name, value] of declaredOptionEntries(declared.options)) {
|
|
1034
|
+
const existingValue = existing[name];
|
|
1035
|
+
if (value === false && existingValue === void 0) {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (stableIndexStringify(existingValue) !== stableIndexStringify(value)) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return true;
|
|
1043
|
+
}
|
|
1044
|
+
function findExistingIndexByName(existingIndexes, name) {
|
|
1045
|
+
if (!name) return void 0;
|
|
1046
|
+
return existingIndexes.find((index) => index.name === name);
|
|
1047
|
+
}
|
|
1048
|
+
function findExistingIndexByKey(existingIndexes, key) {
|
|
1049
|
+
const fingerprint = stableIndexStringify(key);
|
|
1050
|
+
return existingIndexes.find((index) => stableIndexStringify(getExistingIndexKey(index)) === fingerprint);
|
|
1051
|
+
}
|
|
1052
|
+
function createIndexEnsureError(message, result, cause) {
|
|
1053
|
+
return createError(ErrorCodes.MONGODB_ERROR, message, [result], cause);
|
|
1054
|
+
}
|
|
1055
|
+
function resolveModelAutoIndexOptions(definition, runtimeAutoIndex) {
|
|
1056
|
+
const modelAutoIndex = toCompatDefinition(definition).options?.autoIndex;
|
|
1057
|
+
const value = modelAutoIndex ?? runtimeAutoIndex;
|
|
1058
|
+
if (value === false) {
|
|
1059
|
+
return { enabled: false, emitEvents: true };
|
|
1060
|
+
}
|
|
1061
|
+
if (value && typeof value === "object") {
|
|
1062
|
+
return {
|
|
1063
|
+
enabled: value.enabled !== false,
|
|
1064
|
+
emitEvents: value.emitEvents !== false
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
return { enabled: true, emitEvents: true };
|
|
1068
|
+
}
|
|
1000
1069
|
function getIndexTaskRegistry(runtime) {
|
|
1001
1070
|
if (!runtime) {
|
|
1002
1071
|
return fallbackModelIndexTasks;
|
|
@@ -1024,6 +1093,20 @@ function resolveIndexTaskScope(collection, options) {
|
|
|
1024
1093
|
};
|
|
1025
1094
|
}
|
|
1026
1095
|
}
|
|
1096
|
+
function toIndexNamespace(scope) {
|
|
1097
|
+
return {
|
|
1098
|
+
db: scope.dbName,
|
|
1099
|
+
collection: scope.collectionName,
|
|
1100
|
+
poolName: scope.poolName
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function emitIndexFailure(runtime, payload, emitEvents) {
|
|
1104
|
+
if (!emitEvents) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const emitter = runtime;
|
|
1108
|
+
emitter?.emit?.("model-index-error", payload);
|
|
1109
|
+
}
|
|
1027
1110
|
function warnIndexFailure(runtime, taskKey, error) {
|
|
1028
1111
|
const logger = runtime;
|
|
1029
1112
|
logger?.logger?.warn?.("[MonSQLize] model index creation failed", {
|
|
@@ -1031,9 +1114,10 @@ function warnIndexFailure(runtime, taskKey, error) {
|
|
|
1031
1114
|
error: error instanceof Error ? error.message : String(error)
|
|
1032
1115
|
});
|
|
1033
1116
|
}
|
|
1034
|
-
function scheduleIndexTask(collection,
|
|
1117
|
+
function scheduleIndexTask(collection, declaredIndex, emitEvents, options) {
|
|
1035
1118
|
const scope = resolveIndexTaskScope(collection, options);
|
|
1036
|
-
const
|
|
1119
|
+
const { key, options: indexOptions } = declaredIndex;
|
|
1120
|
+
const indexFingerprint = declaredIndex.fingerprint;
|
|
1037
1121
|
const taskKey = `${scope.poolName}:${scope.dbName}:${scope.collectionName}:${indexFingerprint}`;
|
|
1038
1122
|
const registry = getIndexTaskRegistry(options?.runtime);
|
|
1039
1123
|
const existing = registry.get(taskKey);
|
|
@@ -1051,6 +1135,14 @@ function scheduleIndexTask(collection, key, indexOptions, options) {
|
|
|
1051
1135
|
task.status = "failed";
|
|
1052
1136
|
task.error = error;
|
|
1053
1137
|
warnIndexFailure(options?.runtime, taskKey, error);
|
|
1138
|
+
emitIndexFailure(options?.runtime, {
|
|
1139
|
+
namespace: scope,
|
|
1140
|
+
taskKey,
|
|
1141
|
+
source: declaredIndex.source,
|
|
1142
|
+
key,
|
|
1143
|
+
options: indexOptions,
|
|
1144
|
+
error: summarizeIndexError(error)
|
|
1145
|
+
}, emitEvents);
|
|
1054
1146
|
resolve();
|
|
1055
1147
|
});
|
|
1056
1148
|
});
|
|
@@ -1167,6 +1259,134 @@ function resolveModelHooksFactory(definition) {
|
|
|
1167
1259
|
const hooks = toCompatDefinition(definition).hooks;
|
|
1168
1260
|
return typeof hooks === "function" ? hooks : null;
|
|
1169
1261
|
}
|
|
1262
|
+
function collectModelIndexDefinitions(definition, softDeleteConfig) {
|
|
1263
|
+
const declared = [];
|
|
1264
|
+
if (softDeleteConfig?.enabled && softDeleteConfig.type === "timestamp" && softDeleteConfig.ttl) {
|
|
1265
|
+
const key = { [softDeleteConfig.field]: 1 };
|
|
1266
|
+
const options = { expireAfterSeconds: softDeleteConfig.ttl };
|
|
1267
|
+
declared.push({
|
|
1268
|
+
source: "softDelete",
|
|
1269
|
+
key,
|
|
1270
|
+
options,
|
|
1271
|
+
name: getIndexOptionName(options),
|
|
1272
|
+
fingerprint: stableIndexStringify({ key, options })
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
const indexes = toCompatDefinition(definition).indexes;
|
|
1276
|
+
if (!Array.isArray(indexes) || indexes.length === 0) {
|
|
1277
|
+
return declared;
|
|
1278
|
+
}
|
|
1279
|
+
for (const indexSpec of indexes) {
|
|
1280
|
+
if (!isRecord(indexSpec) || !indexSpec.key) {
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
const { key, ...indexOptions } = indexSpec;
|
|
1284
|
+
declared.push({
|
|
1285
|
+
source: "definition",
|
|
1286
|
+
key,
|
|
1287
|
+
options: indexOptions,
|
|
1288
|
+
name: getIndexOptionName(indexOptions),
|
|
1289
|
+
fingerprint: stableIndexStringify({ key, options: indexOptions })
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
return declared;
|
|
1293
|
+
}
|
|
1294
|
+
async function ensureModelIndexesForCollection(collection, definition, softDeleteConfig, options = {}) {
|
|
1295
|
+
const namespace = toIndexNamespace(resolveIndexTaskScope(collection, options));
|
|
1296
|
+
const declared = collectModelIndexDefinitions(definition, softDeleteConfig);
|
|
1297
|
+
const existingIndexes = await collection.listIndexes();
|
|
1298
|
+
const existing = [];
|
|
1299
|
+
const missing = [];
|
|
1300
|
+
const conflicts = [];
|
|
1301
|
+
for (const declaredIndex of declared) {
|
|
1302
|
+
const existingByName = findExistingIndexByName(existingIndexes, declaredIndex.name);
|
|
1303
|
+
if (existingByName) {
|
|
1304
|
+
if (indexOptionsMatch(existingByName, declaredIndex)) {
|
|
1305
|
+
existing.push({ declared: declaredIndex, existing: existingByName });
|
|
1306
|
+
} else {
|
|
1307
|
+
conflicts.push({
|
|
1308
|
+
declared: declaredIndex,
|
|
1309
|
+
existing: existingByName,
|
|
1310
|
+
reason: "name-conflict"
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const existingByKey = findExistingIndexByKey(existingIndexes, declaredIndex.key);
|
|
1316
|
+
if (existingByKey) {
|
|
1317
|
+
if (indexOptionsMatch(existingByKey, declaredIndex)) {
|
|
1318
|
+
existing.push({ declared: declaredIndex, existing: existingByKey });
|
|
1319
|
+
} else {
|
|
1320
|
+
conflicts.push({
|
|
1321
|
+
declared: declaredIndex,
|
|
1322
|
+
existing: existingByKey,
|
|
1323
|
+
reason: "options-conflict"
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
missing.push(declaredIndex);
|
|
1329
|
+
}
|
|
1330
|
+
const result = {
|
|
1331
|
+
dryRun: options.dryRun === true,
|
|
1332
|
+
namespace,
|
|
1333
|
+
declared,
|
|
1334
|
+
existing,
|
|
1335
|
+
missing,
|
|
1336
|
+
created: [],
|
|
1337
|
+
conflicts,
|
|
1338
|
+
failed: [],
|
|
1339
|
+
skipped: options.dryRun === true ? missing.map((declaredIndex) => ({ declared: declaredIndex, reason: "dry-run" })) : conflicts.map((conflict) => ({ declared: conflict.declared, reason: conflict.reason }))
|
|
1340
|
+
};
|
|
1341
|
+
if (conflicts.length > 0 && options.throwOnError) {
|
|
1342
|
+
throw createIndexEnsureError("Model index conflicts detected.", result);
|
|
1343
|
+
}
|
|
1344
|
+
if (options.dryRun === true) {
|
|
1345
|
+
return result;
|
|
1346
|
+
}
|
|
1347
|
+
for (const declaredIndex of missing) {
|
|
1348
|
+
try {
|
|
1349
|
+
const createdName = await collection.createIndex(declaredIndex.key, declaredIndex.options);
|
|
1350
|
+
result.created.push({
|
|
1351
|
+
declared: declaredIndex,
|
|
1352
|
+
name: typeof createdName === "string" ? createdName : void 0,
|
|
1353
|
+
result: createdName
|
|
1354
|
+
});
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
result.failed.push({
|
|
1357
|
+
declared: declaredIndex,
|
|
1358
|
+
error: summarizeIndexError(error)
|
|
1359
|
+
});
|
|
1360
|
+
if (options.throwOnError) {
|
|
1361
|
+
throw createIndexEnsureError(
|
|
1362
|
+
"Model index creation failed.",
|
|
1363
|
+
result,
|
|
1364
|
+
error instanceof Error ? error : void 0
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1371
|
+
function summarizeModelIndexEnsureResults(results) {
|
|
1372
|
+
return results.reduce((totals, result) => ({
|
|
1373
|
+
declared: totals.declared + result.declared.length,
|
|
1374
|
+
existing: totals.existing + result.existing.length,
|
|
1375
|
+
missing: totals.missing + result.missing.length,
|
|
1376
|
+
created: totals.created + result.created.length,
|
|
1377
|
+
conflicts: totals.conflicts + result.conflicts.length,
|
|
1378
|
+
failed: totals.failed + result.failed.length,
|
|
1379
|
+
skipped: totals.skipped + result.skipped.length
|
|
1380
|
+
}), {
|
|
1381
|
+
declared: 0,
|
|
1382
|
+
existing: 0,
|
|
1383
|
+
missing: 0,
|
|
1384
|
+
created: 0,
|
|
1385
|
+
conflicts: 0,
|
|
1386
|
+
failed: 0,
|
|
1387
|
+
skipped: 0
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1170
1390
|
function initializeModelV1Methods(target, definition) {
|
|
1171
1391
|
const methods = toCompatDefinition(definition).methods;
|
|
1172
1392
|
if (typeof methods !== "function") {
|
|
@@ -1191,25 +1411,12 @@ function initializeModelV1Methods(target, definition) {
|
|
|
1191
1411
|
}
|
|
1192
1412
|
}
|
|
1193
1413
|
function scheduleModelIndexes(collection, definition, softDeleteConfig, options) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
scheduleIndexTask(
|
|
1197
|
-
collection,
|
|
1198
|
-
{ [softDeleteIndex.field]: 1 },
|
|
1199
|
-
{ expireAfterSeconds: softDeleteIndex.ttl },
|
|
1200
|
-
options
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
const indexes = toCompatDefinition(definition).indexes;
|
|
1204
|
-
if (!Array.isArray(indexes) || indexes.length === 0) {
|
|
1414
|
+
const autoIndex = resolveModelAutoIndexOptions(definition, options?.autoIndex);
|
|
1415
|
+
if (!autoIndex.enabled) {
|
|
1205
1416
|
return;
|
|
1206
1417
|
}
|
|
1207
|
-
for (const
|
|
1208
|
-
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
const { key, ...indexOptions } = indexSpec;
|
|
1212
|
-
scheduleIndexTask(collection, key, indexOptions, options);
|
|
1418
|
+
for (const declaredIndex of collectModelIndexDefinitions(definition, softDeleteConfig)) {
|
|
1419
|
+
scheduleIndexTask(collection, declaredIndex, autoIndex.emitEvents, options);
|
|
1213
1420
|
}
|
|
1214
1421
|
}
|
|
1215
1422
|
|
|
@@ -1777,7 +1984,8 @@ var ModelInstance = class {
|
|
|
1777
1984
|
runtime: this.runtime,
|
|
1778
1985
|
dbName: options.dbName,
|
|
1779
1986
|
poolName: options.poolName,
|
|
1780
|
-
collectionName: options.collectionName
|
|
1987
|
+
collectionName: options.collectionName,
|
|
1988
|
+
autoIndex: this.runtime.options?.autoIndex
|
|
1781
1989
|
});
|
|
1782
1990
|
this._v1InstanceMethods = initializeModelV1Methods(this, options.definition);
|
|
1783
1991
|
}
|
|
@@ -1981,6 +2189,15 @@ var ModelInstance = class {
|
|
|
1981
2189
|
listIndexes() {
|
|
1982
2190
|
return this.collection.listIndexes();
|
|
1983
2191
|
}
|
|
2192
|
+
ensureIndexes(options = {}) {
|
|
2193
|
+
return ensureModelIndexesForCollection(this.collection, this.definition, this._softDeleteConfig, {
|
|
2194
|
+
...options,
|
|
2195
|
+
runtime: this.runtime,
|
|
2196
|
+
dbName: this.dbName,
|
|
2197
|
+
poolName: this.poolName,
|
|
2198
|
+
collectionName: this.collectionName
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
1984
2201
|
dropIndex(name) {
|
|
1985
2202
|
return this.collection.dropIndex(name);
|
|
1986
2203
|
}
|
|
@@ -2066,18 +2283,18 @@ var ModelInstance = class {
|
|
|
2066
2283
|
}
|
|
2067
2284
|
async populateDocuments(docs, paths) {
|
|
2068
2285
|
let current = docs;
|
|
2069
|
-
for (const
|
|
2070
|
-
current = await this.populatePath(current,
|
|
2286
|
+
for (const path3 of paths) {
|
|
2287
|
+
current = await this.populatePath(current, path3);
|
|
2071
2288
|
}
|
|
2072
2289
|
return current;
|
|
2073
2290
|
}
|
|
2074
|
-
async populatePath(docs,
|
|
2291
|
+
async populatePath(docs, path3) {
|
|
2075
2292
|
return populateModelPath({
|
|
2076
2293
|
relations: this.relations,
|
|
2077
2294
|
runtime: this.runtime,
|
|
2078
2295
|
dbName: this.dbName,
|
|
2079
2296
|
poolName: this.poolName
|
|
2080
|
-
}, docs,
|
|
2297
|
+
}, docs, path3);
|
|
2081
2298
|
}
|
|
2082
2299
|
hydrateDocuments(docs) {
|
|
2083
2300
|
return docs.filter(Boolean).map((doc) => this.hydrateDocument(doc));
|
|
@@ -2250,9 +2467,9 @@ var Transaction = class {
|
|
|
2250
2467
|
*/
|
|
2251
2468
|
async start() {
|
|
2252
2469
|
if (this.state !== "pending") {
|
|
2253
|
-
throw
|
|
2470
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
|
|
2254
2471
|
}
|
|
2255
|
-
this.session.startTransaction();
|
|
2472
|
+
this.session.startTransaction(this.options.transactionOptions);
|
|
2256
2473
|
this.state = "active";
|
|
2257
2474
|
this.startedAt = Date.now();
|
|
2258
2475
|
const timeout = this.options.timeout ?? 3e4;
|
|
@@ -2272,7 +2489,7 @@ var Transaction = class {
|
|
|
2272
2489
|
*/
|
|
2273
2490
|
async commit() {
|
|
2274
2491
|
if (this.state !== "active") {
|
|
2275
|
-
throw
|
|
2492
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
2276
2493
|
}
|
|
2277
2494
|
if (typeof this.session.commitTransaction === "function") {
|
|
2278
2495
|
await this.session.commitTransaction();
|
|
@@ -2370,7 +2587,9 @@ var TransactionManager = class {
|
|
|
2370
2587
|
this.stats = {
|
|
2371
2588
|
totalTransactions: 0,
|
|
2372
2589
|
successfulTransactions: 0,
|
|
2373
|
-
failedTransactions: 0
|
|
2590
|
+
failedTransactions: 0,
|
|
2591
|
+
readOnlyTransactions: 0,
|
|
2592
|
+
writeTransactions: 0
|
|
2374
2593
|
};
|
|
2375
2594
|
const options = "client" in input ? input : {
|
|
2376
2595
|
client: input,
|
|
@@ -2381,6 +2600,10 @@ var TransactionManager = class {
|
|
|
2381
2600
|
this.cache = options.cache ?? null;
|
|
2382
2601
|
this.logger = options.logger ?? null;
|
|
2383
2602
|
this.lockManager = options.lockManager ?? null;
|
|
2603
|
+
this.defaultReadConcern = options.defaultReadConcern;
|
|
2604
|
+
this.defaultWriteConcern = options.defaultWriteConcern;
|
|
2605
|
+
this.defaultReadPreference = options.defaultReadPreference;
|
|
2606
|
+
this.maxStatsSamples = options.maxStatsSamples ?? 1e3;
|
|
2384
2607
|
this.defaultOptions = {
|
|
2385
2608
|
maxDuration: options.maxDuration ?? 3e4,
|
|
2386
2609
|
enableRetry: options.enableRetry ?? true,
|
|
@@ -2397,11 +2620,17 @@ var TransactionManager = class {
|
|
|
2397
2620
|
const session = this.client.startSession({
|
|
2398
2621
|
causalConsistency: options.causalConsistency !== false
|
|
2399
2622
|
});
|
|
2623
|
+
const transactionOptions = {
|
|
2624
|
+
readConcern: options.readConcern ?? this.defaultReadConcern,
|
|
2625
|
+
writeConcern: options.writeConcern ?? this.defaultWriteConcern,
|
|
2626
|
+
readPreference: options.readPreference ?? this.defaultReadPreference
|
|
2627
|
+
};
|
|
2400
2628
|
const transaction = new Transaction(session, {
|
|
2401
2629
|
cache: this.cache,
|
|
2402
2630
|
logger: this.logger,
|
|
2403
2631
|
lockManager: options.enableCacheLock === false ? null : this.lockManager,
|
|
2404
|
-
timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration
|
|
2632
|
+
timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration,
|
|
2633
|
+
transactionOptions: compactUndefined(transactionOptions)
|
|
2405
2634
|
});
|
|
2406
2635
|
const originalEnd = transaction.end.bind(transaction);
|
|
2407
2636
|
transaction.end = async () => {
|
|
@@ -2428,12 +2657,12 @@ var TransactionManager = class {
|
|
|
2428
2657
|
await transaction.start();
|
|
2429
2658
|
const result = await callback(transaction);
|
|
2430
2659
|
await transaction.commit();
|
|
2431
|
-
this.recordStats(Date.now() - startedAt, true);
|
|
2660
|
+
this.recordStats(transaction, Date.now() - startedAt, true);
|
|
2432
2661
|
return result;
|
|
2433
2662
|
} catch (error) {
|
|
2434
2663
|
lastError = error;
|
|
2435
2664
|
await transaction.abort();
|
|
2436
|
-
this.recordStats(Date.now() - startedAt, false);
|
|
2665
|
+
this.recordStats(transaction, Date.now() - startedAt, false);
|
|
2437
2666
|
if (!enableRetry || attempt === maxRetries || !isTransientTransactionError(error)) {
|
|
2438
2667
|
throw error;
|
|
2439
2668
|
}
|
|
@@ -2469,27 +2698,54 @@ var TransactionManager = class {
|
|
|
2469
2698
|
*/
|
|
2470
2699
|
getStats() {
|
|
2471
2700
|
const averageDuration = this.durations.length === 0 ? 0 : this.durations.reduce((sum, item) => sum + item, 0) / this.durations.length;
|
|
2701
|
+
const sortedDurations = [...this.durations].sort((a, b) => a - b);
|
|
2702
|
+
const p95Duration = percentile(sortedDurations, 0.95);
|
|
2703
|
+
const p99Duration = percentile(sortedDurations, 0.99);
|
|
2704
|
+
const totalTransactions = this.stats.totalTransactions;
|
|
2472
2705
|
return {
|
|
2473
|
-
totalTransactions
|
|
2706
|
+
totalTransactions,
|
|
2474
2707
|
successfulTransactions: this.stats.successfulTransactions,
|
|
2475
2708
|
failedTransactions: this.stats.failedTransactions,
|
|
2709
|
+
readOnlyTransactions: this.stats.readOnlyTransactions,
|
|
2710
|
+
writeTransactions: this.stats.writeTransactions,
|
|
2476
2711
|
activeTransactions: this.activeTransactions.size,
|
|
2477
|
-
averageDuration
|
|
2712
|
+
averageDuration,
|
|
2713
|
+
p95Duration,
|
|
2714
|
+
p99Duration,
|
|
2715
|
+
successRate: totalTransactions > 0 ? `${(this.stats.successfulTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
|
|
2716
|
+
readOnlyRatio: totalTransactions > 0 ? `${(this.stats.readOnlyTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
|
|
2717
|
+
sampleCount: this.durations.length
|
|
2478
2718
|
};
|
|
2479
2719
|
}
|
|
2480
|
-
recordStats(duration, success) {
|
|
2720
|
+
recordStats(transaction, duration, success) {
|
|
2481
2721
|
this.stats.totalTransactions += 1;
|
|
2482
2722
|
if (success) {
|
|
2483
2723
|
this.stats.successfulTransactions += 1;
|
|
2484
2724
|
} else {
|
|
2485
2725
|
this.stats.failedTransactions += 1;
|
|
2486
2726
|
}
|
|
2727
|
+
if (transaction.pendingInvalidations.size > 0) {
|
|
2728
|
+
this.stats.writeTransactions += 1;
|
|
2729
|
+
} else {
|
|
2730
|
+
this.stats.readOnlyTransactions += 1;
|
|
2731
|
+
}
|
|
2487
2732
|
this.durations.push(duration);
|
|
2488
|
-
if (this.durations.length >
|
|
2733
|
+
if (this.durations.length > this.maxStatsSamples) {
|
|
2489
2734
|
this.durations.shift();
|
|
2490
2735
|
}
|
|
2491
2736
|
}
|
|
2492
2737
|
};
|
|
2738
|
+
function percentile(sortedValues, ratio) {
|
|
2739
|
+
if (sortedValues.length === 0) {
|
|
2740
|
+
return 0;
|
|
2741
|
+
}
|
|
2742
|
+
const index = Math.floor(sortedValues.length * ratio);
|
|
2743
|
+
return sortedValues[Math.min(index, sortedValues.length - 1)] ?? 0;
|
|
2744
|
+
}
|
|
2745
|
+
function compactUndefined(value) {
|
|
2746
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0);
|
|
2747
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
2748
|
+
}
|
|
2493
2749
|
function stringifySessionId(id) {
|
|
2494
2750
|
if (typeof id === "string") {
|
|
2495
2751
|
return id;
|
|
@@ -2526,18 +2782,166 @@ async function sleep(ms) {
|
|
|
2526
2782
|
}
|
|
2527
2783
|
|
|
2528
2784
|
// src/adapters/mongodb/common/connect.ts
|
|
2785
|
+
var import_node_fs = require("node:fs");
|
|
2786
|
+
var import_node_path = __toESM(require("node:path"));
|
|
2529
2787
|
var import_mongodb = require("mongodb");
|
|
2788
|
+
var DEFAULT_MEMORY_SERVER_VERSION = "7.0.14";
|
|
2789
|
+
var MANAGED_DB_PATH_PREFIXES = ["single-", "replset-", "examples-single-", "examples-replset-", "probe-single-", "probe-replset-"];
|
|
2530
2790
|
var _memoryServerInstance = null;
|
|
2791
|
+
var _memoryServerCleanupOptions = { doCleanup: true, force: true };
|
|
2792
|
+
var _memoryServerDbPath = null;
|
|
2793
|
+
var _memoryServerClients = /* @__PURE__ */ new Set();
|
|
2794
|
+
function setDefaultEnv(name, value) {
|
|
2795
|
+
if (!process.env[name]) {
|
|
2796
|
+
process.env[name] = value;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
function sanitizePathSegment(input) {
|
|
2800
|
+
return input.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "default";
|
|
2801
|
+
}
|
|
2802
|
+
function parseManagedPathPid(name) {
|
|
2803
|
+
if (!MANAGED_DB_PATH_PREFIXES.some((prefix) => name.startsWith(prefix))) {
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
const match = /-(\d+)-[^-]+$/.exec(name);
|
|
2807
|
+
if (!match) {
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
const pid = Number.parseInt(match[1], 10);
|
|
2811
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
2812
|
+
}
|
|
2813
|
+
function isProcessAlive(pid) {
|
|
2814
|
+
if (pid === process.pid) {
|
|
2815
|
+
return true;
|
|
2816
|
+
}
|
|
2817
|
+
try {
|
|
2818
|
+
process.kill(pid, 0);
|
|
2819
|
+
return true;
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
return error.code === "EPERM";
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function pruneManagedDbRoot(dbRoot) {
|
|
2825
|
+
let entries;
|
|
2826
|
+
try {
|
|
2827
|
+
entries = (0, import_node_fs.readdirSync)(dbRoot, { withFileTypes: true });
|
|
2828
|
+
} catch {
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
for (const entry of entries) {
|
|
2832
|
+
if (!entry.isDirectory()) {
|
|
2833
|
+
continue;
|
|
2834
|
+
}
|
|
2835
|
+
const pid = parseManagedPathPid(entry.name);
|
|
2836
|
+
if (!pid || isProcessAlive(pid)) {
|
|
2837
|
+
continue;
|
|
2838
|
+
}
|
|
2839
|
+
try {
|
|
2840
|
+
(0, import_node_fs.rmSync)(import_node_path.default.join(dbRoot, entry.name), { recursive: true, force: true });
|
|
2841
|
+
} catch {
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
function resolveMemoryServerBinaryVersion(memoryServerOptions = {}) {
|
|
2846
|
+
return memoryServerOptions?.binary?.version || process.env.MONSQLIZE_REPLSET_BINARY_VERSION || process.env.MONSQLIZE_MEMORY_MONGO_BINARY_VERSION || process.env.MONGOMS_VERSION || DEFAULT_MEMORY_SERVER_VERSION;
|
|
2847
|
+
}
|
|
2848
|
+
function resolveMemoryServerPolicy(binaryVersion) {
|
|
2849
|
+
const cacheRoot = import_node_path.default.resolve(process.env.MONSQLIZE_MEMORY_SERVER_CACHE_DIR || import_node_path.default.join(process.cwd(), ".cache", "mongodb-memory-server"));
|
|
2850
|
+
const downloadDir = import_node_path.default.resolve(process.env.MONGOMS_DOWNLOAD_DIR || import_node_path.default.join(cacheRoot, "binaries"));
|
|
2851
|
+
const dbRoot = import_node_path.default.resolve(process.env.MONSQLIZE_MEMORY_SERVER_DB_DIR || import_node_path.default.join(cacheRoot, "db"));
|
|
2852
|
+
(0, import_node_fs.mkdirSync)(downloadDir, { recursive: true });
|
|
2853
|
+
(0, import_node_fs.mkdirSync)(dbRoot, { recursive: true });
|
|
2854
|
+
pruneManagedDbRoot(dbRoot);
|
|
2855
|
+
setDefaultEnv("MONGOMS_DOWNLOAD_DIR", downloadDir);
|
|
2856
|
+
setDefaultEnv("MONGOMS_PREFER_GLOBAL_PATH", "false");
|
|
2857
|
+
setDefaultEnv("MONGOMS_RUNTIME_DOWNLOAD", "true");
|
|
2858
|
+
setDefaultEnv("MONGOMS_VERSION", binaryVersion);
|
|
2859
|
+
return { downloadDir, dbRoot };
|
|
2860
|
+
}
|
|
2861
|
+
function createManagedDbPath(dbRoot, dbName) {
|
|
2862
|
+
return (0, import_node_fs.mkdtempSync)(import_node_path.default.join(dbRoot, `replset-${sanitizePathSegment(dbName)}-${process.pid}-`));
|
|
2863
|
+
}
|
|
2864
|
+
function isManagedCleanupError(error, dbPath) {
|
|
2865
|
+
if (!error || typeof error !== "object") {
|
|
2866
|
+
return false;
|
|
2867
|
+
}
|
|
2868
|
+
const candidate = error;
|
|
2869
|
+
if (!candidate.code || !["ENOTEMPTY", "EBUSY", "EPERM", "ENOENT"].includes(candidate.code)) {
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
return !candidate.path || import_node_path.default.resolve(candidate.path).startsWith(import_node_path.default.resolve(dbPath));
|
|
2873
|
+
}
|
|
2874
|
+
function delay(ms) {
|
|
2875
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2876
|
+
}
|
|
2877
|
+
async function cleanupManagedDbPath(dbPath) {
|
|
2878
|
+
if (!dbPath) {
|
|
2879
|
+
return true;
|
|
2880
|
+
}
|
|
2881
|
+
for (const waitMs of [0, 50, 100, 200, 400, 800]) {
|
|
2882
|
+
if (waitMs > 0) {
|
|
2883
|
+
await delay(waitMs);
|
|
2884
|
+
}
|
|
2885
|
+
try {
|
|
2886
|
+
(0, import_node_fs.rmSync)(dbPath, { recursive: true, force: true });
|
|
2887
|
+
if (!(0, import_node_fs.existsSync)(dbPath)) {
|
|
2888
|
+
return true;
|
|
2889
|
+
}
|
|
2890
|
+
} catch {
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
return !(0, import_node_fs.existsSync)(dbPath);
|
|
2894
|
+
}
|
|
2895
|
+
function resolveLaunchTimeout() {
|
|
2896
|
+
const raw = process.env.MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS;
|
|
2897
|
+
if (!raw) {
|
|
2898
|
+
return void 0;
|
|
2899
|
+
}
|
|
2900
|
+
const value = Number.parseInt(raw, 10);
|
|
2901
|
+
return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
2902
|
+
}
|
|
2903
|
+
async function seedMemoryServerBinaryCache(binaryVersion, downloadDir) {
|
|
2904
|
+
try {
|
|
2905
|
+
const { DryMongoBinary } = require("mongodb-memory-server-core/lib/util/DryMongoBinary");
|
|
2906
|
+
const options = await DryMongoBinary.generateOptions({ version: binaryVersion, downloadDir });
|
|
2907
|
+
options.downloadDir = downloadDir;
|
|
2908
|
+
const paths = await DryMongoBinary.generatePaths(options);
|
|
2909
|
+
if (paths.resolveConfig && paths.homeCache && import_node_path.default.resolve(paths.resolveConfig) !== import_node_path.default.resolve(paths.homeCache) && (0, import_node_fs.existsSync)(paths.homeCache) && !(0, import_node_fs.existsSync)(paths.resolveConfig)) {
|
|
2910
|
+
(0, import_node_fs.mkdirSync)(import_node_path.default.dirname(paths.resolveConfig), { recursive: true });
|
|
2911
|
+
(0, import_node_fs.copyFileSync)(paths.homeCache, paths.resolveConfig);
|
|
2912
|
+
}
|
|
2913
|
+
} catch {
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2531
2916
|
async function startMemoryServer(logger, memoryServerOptions = {}) {
|
|
2532
2917
|
if (_memoryServerInstance) {
|
|
2533
2918
|
return _memoryServerInstance.getUri();
|
|
2534
2919
|
}
|
|
2920
|
+
const binaryVersion = resolveMemoryServerBinaryVersion(memoryServerOptions);
|
|
2921
|
+
const { dbRoot, downloadDir } = resolveMemoryServerPolicy(binaryVersion);
|
|
2922
|
+
await seedMemoryServerBinaryCache(binaryVersion, downloadDir);
|
|
2535
2923
|
const { MongoMemoryReplSet } = require("mongodb-memory-server");
|
|
2536
|
-
logger?.info?.("
|
|
2924
|
+
logger?.info?.("Starting MongoDB Memory ReplSet", { binaryVersion });
|
|
2925
|
+
const dbName = memoryServerOptions?.instance?.dbName || "monsqlize_memory";
|
|
2926
|
+
const instanceConfig = { ...memoryServerOptions?.instance ?? {} };
|
|
2927
|
+
const hasUserDbPath = typeof instanceConfig.dbPath === "string" && instanceConfig.dbPath.length > 0;
|
|
2928
|
+
if (!hasUserDbPath) {
|
|
2929
|
+
_memoryServerDbPath = createManagedDbPath(dbRoot, dbName);
|
|
2930
|
+
instanceConfig.dbPath = _memoryServerDbPath;
|
|
2931
|
+
} else {
|
|
2932
|
+
_memoryServerDbPath = null;
|
|
2933
|
+
}
|
|
2934
|
+
if (instanceConfig.launchTimeout === void 0) {
|
|
2935
|
+
const launchTimeout = resolveLaunchTimeout();
|
|
2936
|
+
if (launchTimeout) {
|
|
2937
|
+
instanceConfig.launchTimeout = launchTimeout;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
_memoryServerCleanupOptions = { doCleanup: true, force: !hasUserDbPath };
|
|
2537
2941
|
const defaultConfig = {
|
|
2538
2942
|
replSet: { count: 1, storageEngine: "wiredTiger" },
|
|
2539
|
-
binary: { version:
|
|
2540
|
-
instanceOpts: [
|
|
2943
|
+
binary: { version: binaryVersion },
|
|
2944
|
+
instanceOpts: [instanceConfig]
|
|
2541
2945
|
};
|
|
2542
2946
|
const resolvedConfig = {
|
|
2543
2947
|
...defaultConfig,
|
|
@@ -2546,11 +2950,43 @@ async function startMemoryServer(logger, memoryServerOptions = {}) {
|
|
|
2546
2950
|
try {
|
|
2547
2951
|
_memoryServerInstance = await MongoMemoryReplSet.create(resolvedConfig);
|
|
2548
2952
|
const uri = _memoryServerInstance.getUri();
|
|
2549
|
-
logger?.info?.("
|
|
2953
|
+
logger?.info?.("MongoDB Memory ReplSet started", { uri });
|
|
2550
2954
|
return uri;
|
|
2551
2955
|
} catch (err) {
|
|
2552
|
-
|
|
2553
|
-
|
|
2956
|
+
if (!hasUserDbPath) {
|
|
2957
|
+
await cleanupManagedDbPath(_memoryServerDbPath);
|
|
2958
|
+
_memoryServerDbPath = null;
|
|
2959
|
+
}
|
|
2960
|
+
logger?.error?.("Failed to start MongoDB Memory ReplSet", err);
|
|
2961
|
+
throw createConnectionError(
|
|
2962
|
+
`Failed to start MongoDB Memory ReplSet: ${err.message}`,
|
|
2963
|
+
err instanceof Error ? err : void 0
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
async function stopMemoryServer(logger) {
|
|
2968
|
+
if (!_memoryServerInstance) {
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
const instance = _memoryServerInstance;
|
|
2972
|
+
const dbPath = _memoryServerDbPath;
|
|
2973
|
+
_memoryServerInstance = null;
|
|
2974
|
+
_memoryServerDbPath = null;
|
|
2975
|
+
let stopError = null;
|
|
2976
|
+
try {
|
|
2977
|
+
await instance.stop(_memoryServerCleanupOptions);
|
|
2978
|
+
logger?.info?.("MongoDB Memory ReplSet stopped");
|
|
2979
|
+
} catch (cause) {
|
|
2980
|
+
stopError = cause;
|
|
2981
|
+
logger?.warn?.("Failed to stop MongoDB Memory ReplSet cleanly.", cause);
|
|
2982
|
+
} finally {
|
|
2983
|
+
if (_memoryServerCleanupOptions.force) {
|
|
2984
|
+
const cleaned = await cleanupManagedDbPath(dbPath);
|
|
2985
|
+
if (!cleaned && (!stopError || isManagedCleanupError(stopError, dbPath ?? ""))) {
|
|
2986
|
+
logger?.warn?.("Failed to remove MongoDB Memory ReplSet dbPath.", { dbPath });
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
_memoryServerCleanupOptions = { doCleanup: true, force: true };
|
|
2554
2990
|
}
|
|
2555
2991
|
}
|
|
2556
2992
|
async function connectMongo(params) {
|
|
@@ -2559,13 +2995,15 @@ async function connectMongo(params) {
|
|
|
2559
2995
|
throw createError(ErrorCodes.INVALID_DATABASE_NAME, "Database name must be a non-empty string.");
|
|
2560
2996
|
}
|
|
2561
2997
|
let effectiveUri = params.config?.uri?.trim();
|
|
2998
|
+
let usesManagedMemoryServer = false;
|
|
2562
2999
|
if (!effectiveUri && params.config?.useMemoryServer === true) {
|
|
2563
3000
|
if (process.env["MONSQLIZE_USE_SYSTEM_MONGO"] === "true") {
|
|
2564
3001
|
const systemUri = process.env["MONSQLIZE_SYSTEM_MONGO_URI"] ?? "mongodb://127.0.0.1:27017";
|
|
2565
|
-
params.logger?.info?.("
|
|
3002
|
+
params.logger?.info?.("Using system MongoDB instead of memory server", { uri: systemUri });
|
|
2566
3003
|
effectiveUri = systemUri;
|
|
2567
3004
|
} else {
|
|
2568
3005
|
effectiveUri = await startMemoryServer(params.logger, params.config.memoryServerOptions);
|
|
3006
|
+
usesManagedMemoryServer = true;
|
|
2569
3007
|
}
|
|
2570
3008
|
}
|
|
2571
3009
|
if (!effectiveUri) {
|
|
@@ -2579,6 +3017,9 @@ async function connectMongo(params) {
|
|
|
2579
3017
|
try {
|
|
2580
3018
|
await client.connect();
|
|
2581
3019
|
const db = client.db(databaseName);
|
|
3020
|
+
if (usesManagedMemoryServer) {
|
|
3021
|
+
_memoryServerClients.add(client);
|
|
3022
|
+
}
|
|
2582
3023
|
params.logger?.info?.("MongoDB connected", { databaseName });
|
|
2583
3024
|
return { client, db };
|
|
2584
3025
|
} catch (cause) {
|
|
@@ -2586,6 +3027,9 @@ async function connectMongo(params) {
|
|
|
2586
3027
|
await client.close();
|
|
2587
3028
|
} catch {
|
|
2588
3029
|
}
|
|
3030
|
+
if (usesManagedMemoryServer && _memoryServerClients.size === 0) {
|
|
3031
|
+
await stopMemoryServer(params.logger);
|
|
3032
|
+
}
|
|
2589
3033
|
throw createConnectionError(
|
|
2590
3034
|
`Failed to connect to MongoDB database: ${databaseName}`,
|
|
2591
3035
|
cause instanceof Error ? cause : void 0
|
|
@@ -2596,17 +3040,26 @@ async function closeMongo(client, logger) {
|
|
|
2596
3040
|
if (!client) {
|
|
2597
3041
|
return;
|
|
2598
3042
|
}
|
|
3043
|
+
const shouldReleaseMemoryServer = _memoryServerClients.delete(client);
|
|
3044
|
+
let closeError = null;
|
|
2599
3045
|
try {
|
|
2600
3046
|
await client.close();
|
|
2601
3047
|
logger?.info?.("MongoDB connection closed");
|
|
2602
3048
|
} catch (cause) {
|
|
2603
|
-
|
|
3049
|
+
closeError = createError(
|
|
2604
3050
|
ErrorCodes.CONNECTION_CLOSED,
|
|
2605
3051
|
"Failed to close MongoDB connection cleanly.",
|
|
2606
3052
|
void 0,
|
|
2607
3053
|
cause instanceof Error ? cause : void 0
|
|
2608
3054
|
);
|
|
2609
|
-
logger?.warn?.(
|
|
3055
|
+
logger?.warn?.(closeError.message, closeError.cause);
|
|
3056
|
+
} finally {
|
|
3057
|
+
if (shouldReleaseMemoryServer && _memoryServerClients.size === 0) {
|
|
3058
|
+
await stopMemoryServer(logger);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (closeError) {
|
|
3062
|
+
return;
|
|
2610
3063
|
}
|
|
2611
3064
|
}
|
|
2612
3065
|
|
|
@@ -2618,8 +3071,9 @@ function loadSsh2Client() {
|
|
|
2618
3071
|
try {
|
|
2619
3072
|
return require("ssh2").Client;
|
|
2620
3073
|
} catch {
|
|
2621
|
-
throw
|
|
2622
|
-
|
|
3074
|
+
throw createError(
|
|
3075
|
+
ErrorCodes.INVALID_CONFIG,
|
|
3076
|
+
"Unable to load ssh2. monsqlize installs ssh2 by default; check that the package installation is complete and that your runtime can resolve bundled dependencies."
|
|
2623
3077
|
);
|
|
2624
3078
|
}
|
|
2625
3079
|
}
|
|
@@ -2647,10 +3101,10 @@ var SSHTunnelSSH2 = class {
|
|
|
2647
3101
|
keepaliveInterval = 3e4
|
|
2648
3102
|
} = this._sshConfig;
|
|
2649
3103
|
if (!host || !username) {
|
|
2650
|
-
throw
|
|
3104
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "SSH config requires: host, username");
|
|
2651
3105
|
}
|
|
2652
3106
|
if (!password && !privateKey && !privateKeyPath) {
|
|
2653
|
-
throw
|
|
3107
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "SSH authentication required: provide password, privateKey, or privateKeyPath");
|
|
2654
3108
|
}
|
|
2655
3109
|
const config = { host, port, username, readyTimeout, keepaliveInterval };
|
|
2656
3110
|
if (password) {
|
|
@@ -2725,7 +3179,7 @@ var SSHTunnelSSH2 = class {
|
|
|
2725
3179
|
}
|
|
2726
3180
|
getTunnelUri(_protocol, originalUri) {
|
|
2727
3181
|
if (!this.isConnected || this.localPort === null) {
|
|
2728
|
-
throw
|
|
3182
|
+
throw createError(ErrorCodes.NOT_CONNECTED, `SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
|
|
2729
3183
|
}
|
|
2730
3184
|
return originalUri.replace(
|
|
2731
3185
|
`${this.remoteHost}:${this.remotePort}`,
|
|
@@ -2734,7 +3188,7 @@ var SSHTunnelSSH2 = class {
|
|
|
2734
3188
|
}
|
|
2735
3189
|
getLocalAddress() {
|
|
2736
3190
|
if (!this.isConnected || this.localPort === null) {
|
|
2737
|
-
throw
|
|
3191
|
+
throw createError(ErrorCodes.NOT_CONNECTED, `SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
|
|
2738
3192
|
}
|
|
2739
3193
|
return `localhost:${this.localPort}`;
|
|
2740
3194
|
}
|
|
@@ -3183,7 +3637,7 @@ var HealthChecker = class {
|
|
|
3183
3637
|
async _pingPool(poolName, timeout) {
|
|
3184
3638
|
const stored = this._clients.get(poolName);
|
|
3185
3639
|
const client = stored ?? this._poolManager?._getPool(poolName);
|
|
3186
|
-
if (!client) throw
|
|
3640
|
+
if (!client) throw createError(ErrorCodes.POOL_NOT_FOUND, `No client for pool: ${poolName}`);
|
|
3187
3641
|
const db = client.db("admin");
|
|
3188
3642
|
const pingFn = db.command ? () => db.command({ ping: 1 }) : () => db.admin().ping();
|
|
3189
3643
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), timeout));
|
|
@@ -3215,7 +3669,7 @@ var PoolSelector = class {
|
|
|
3215
3669
|
}
|
|
3216
3670
|
select(pools, context) {
|
|
3217
3671
|
if (!pools || pools.length === 0) {
|
|
3218
|
-
throw
|
|
3672
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "No available pools");
|
|
3219
3673
|
}
|
|
3220
3674
|
switch (this._strategy) {
|
|
3221
3675
|
case "auto":
|
|
@@ -3236,7 +3690,10 @@ var PoolSelector = class {
|
|
|
3236
3690
|
selectByAuto(pools, context) {
|
|
3237
3691
|
const { operation, poolPreference } = context;
|
|
3238
3692
|
let candidates = pools;
|
|
3239
|
-
|
|
3693
|
+
const preferred = this.filterByPreference(pools, poolPreference);
|
|
3694
|
+
if (preferred.length > 0) {
|
|
3695
|
+
candidates = preferred;
|
|
3696
|
+
} else if (operation === "read") {
|
|
3240
3697
|
const secondaries = pools.filter((pool) => pool.role === "secondary");
|
|
3241
3698
|
if (secondaries.length > 0) {
|
|
3242
3699
|
candidates = secondaries;
|
|
@@ -3247,10 +3704,19 @@ var PoolSelector = class {
|
|
|
3247
3704
|
candidates = primaries;
|
|
3248
3705
|
}
|
|
3249
3706
|
}
|
|
3707
|
+
if (candidates.length === 1) {
|
|
3708
|
+
return candidates[0].name;
|
|
3709
|
+
}
|
|
3710
|
+
return this.selectByWeighted(candidates);
|
|
3711
|
+
}
|
|
3712
|
+
filterByPreference(pools, poolPreference) {
|
|
3713
|
+
let candidates = pools;
|
|
3714
|
+
let applied = false;
|
|
3250
3715
|
if (poolPreference?.role) {
|
|
3251
3716
|
const filteredByRole = candidates.filter((pool) => pool.role === poolPreference.role);
|
|
3252
3717
|
if (filteredByRole.length > 0) {
|
|
3253
3718
|
candidates = filteredByRole;
|
|
3719
|
+
applied = true;
|
|
3254
3720
|
}
|
|
3255
3721
|
}
|
|
3256
3722
|
if (poolPreference?.tags?.length) {
|
|
@@ -3263,12 +3729,10 @@ var PoolSelector = class {
|
|
|
3263
3729
|
});
|
|
3264
3730
|
if (filteredByTags.length > 0) {
|
|
3265
3731
|
candidates = filteredByTags;
|
|
3732
|
+
applied = true;
|
|
3266
3733
|
}
|
|
3267
3734
|
}
|
|
3268
|
-
|
|
3269
|
-
return candidates[0].name;
|
|
3270
|
-
}
|
|
3271
|
-
return this.selectByWeighted(candidates);
|
|
3735
|
+
return applied ? candidates : [];
|
|
3272
3736
|
}
|
|
3273
3737
|
selectByRoundRobin(pools, context) {
|
|
3274
3738
|
let candidates = pools;
|
|
@@ -3428,43 +3892,43 @@ var DEFAULT_POOL_CONNECT_OPTIONS = {
|
|
|
3428
3892
|
serverSelectionTimeoutMS: 5e3
|
|
3429
3893
|
};
|
|
3430
3894
|
function validatePoolConfig(config) {
|
|
3431
|
-
if (!config || typeof config !== "object") throw
|
|
3432
|
-
if (!config.name || typeof config.name !== "string") throw
|
|
3433
|
-
if (!config.uri || typeof config.uri !== "string") throw
|
|
3895
|
+
if (!config || typeof config !== "object") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config must be an object");
|
|
3896
|
+
if (!config.name || typeof config.name !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.name is required and must be a string");
|
|
3897
|
+
if (!config.uri || typeof config.uri !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.uri is required and must be a string");
|
|
3434
3898
|
if (!config.uri.startsWith("mongodb://") && !config.uri.startsWith("mongodb+srv://")) {
|
|
3435
|
-
throw
|
|
3899
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.uri must start with mongodb:// or mongodb+srv://");
|
|
3436
3900
|
}
|
|
3437
3901
|
if (config.role) {
|
|
3438
3902
|
const validRoles = ["primary", "secondary", "analytics", "custom"];
|
|
3439
|
-
if (!validRoles.includes(config.role)) throw
|
|
3903
|
+
if (!validRoles.includes(config.role)) throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.role must be one of: ${validRoles.join(", ")}`);
|
|
3440
3904
|
}
|
|
3441
3905
|
if (config.weight !== void 0) {
|
|
3442
|
-
if (typeof config.weight !== "number") throw
|
|
3443
|
-
if (config.weight < 0) throw
|
|
3906
|
+
if (typeof config.weight !== "number") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.weight must be a number");
|
|
3907
|
+
if (config.weight < 0) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.weight must be a non-negative number");
|
|
3444
3908
|
}
|
|
3445
3909
|
if (config.options !== void 0) {
|
|
3446
|
-
if (typeof config.options !== "object" || Array.isArray(config.options)) throw
|
|
3910
|
+
if (typeof config.options !== "object" || Array.isArray(config.options)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.options must be an object");
|
|
3447
3911
|
const opts = config.options;
|
|
3448
3912
|
for (const key of ["maxPoolSize", "minPoolSize", "maxIdleTimeMS", "waitQueueTimeoutMS", "connectTimeoutMS", "serverSelectionTimeoutMS"]) {
|
|
3449
3913
|
if (opts[key] !== void 0 && (typeof opts[key] !== "number" || opts[key] < 0)) {
|
|
3450
|
-
throw
|
|
3914
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.options.${key} must be a non-negative number`);
|
|
3451
3915
|
}
|
|
3452
3916
|
}
|
|
3453
3917
|
}
|
|
3454
3918
|
if (config.healthCheck !== void 0) {
|
|
3455
|
-
if (typeof config.healthCheck !== "object" || Array.isArray(config.healthCheck)) throw
|
|
3919
|
+
if (typeof config.healthCheck !== "object" || Array.isArray(config.healthCheck)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.healthCheck must be an object");
|
|
3456
3920
|
const hc = config.healthCheck;
|
|
3457
|
-
if (hc.enabled !== void 0 && typeof hc.enabled !== "boolean") throw
|
|
3921
|
+
if (hc.enabled !== void 0 && typeof hc.enabled !== "boolean") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.healthCheck.enabled must be a boolean");
|
|
3458
3922
|
for (const key of ["interval", "timeout", "retries"]) {
|
|
3459
3923
|
if (hc[key] !== void 0 && (typeof hc[key] !== "number" || hc[key] < 0)) {
|
|
3460
|
-
throw
|
|
3924
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.healthCheck.${key} must be a non-negative number`);
|
|
3461
3925
|
}
|
|
3462
3926
|
}
|
|
3463
3927
|
}
|
|
3464
3928
|
if (config.tags !== void 0) {
|
|
3465
|
-
if (!Array.isArray(config.tags)) throw
|
|
3929
|
+
if (!Array.isArray(config.tags)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.tags must be an array");
|
|
3466
3930
|
for (const tag of config.tags) {
|
|
3467
|
-
if (typeof tag !== "string") throw
|
|
3931
|
+
if (typeof tag !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.tags must be an array of strings");
|
|
3468
3932
|
}
|
|
3469
3933
|
}
|
|
3470
3934
|
}
|
|
@@ -3523,10 +3987,10 @@ function validatePoolConfigSafe(config) {
|
|
|
3523
3987
|
}
|
|
3524
3988
|
function validatePoolConfigInternal(config) {
|
|
3525
3989
|
if (!config.name?.trim()) {
|
|
3526
|
-
throw
|
|
3990
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "Pool config requires a non-empty name");
|
|
3527
3991
|
}
|
|
3528
3992
|
if (!config.uri?.trim()) {
|
|
3529
|
-
throw
|
|
3993
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "Pool config requires a non-empty uri");
|
|
3530
3994
|
}
|
|
3531
3995
|
}
|
|
3532
3996
|
function createEmptyPoolStats(name) {
|
|
@@ -3603,10 +4067,10 @@ var ConnectionPoolManager = class {
|
|
|
3603
4067
|
async addPool(config) {
|
|
3604
4068
|
validatePoolConfigInternal(config);
|
|
3605
4069
|
if (this.pools.has(config.name) || this._pendingAdds.has(config.name)) {
|
|
3606
|
-
throw
|
|
4070
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
3607
4071
|
}
|
|
3608
4072
|
if (this.maxPoolsCount > 0 && this.pools.size >= this.maxPoolsCount) {
|
|
3609
|
-
throw
|
|
4073
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `Maximum pool count (${this.maxPoolsCount}) reached`);
|
|
3610
4074
|
}
|
|
3611
4075
|
this._pendingAdds.add(config.name);
|
|
3612
4076
|
try {
|
|
@@ -3614,7 +4078,7 @@ var ConnectionPoolManager = class {
|
|
|
3614
4078
|
if (this.pools.has(config.name)) {
|
|
3615
4079
|
await client.close().catch(() => {
|
|
3616
4080
|
});
|
|
3617
|
-
throw
|
|
4081
|
+
throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
|
|
3618
4082
|
}
|
|
3619
4083
|
this.pools.set(config.name, {
|
|
3620
4084
|
client,
|
|
@@ -3653,7 +4117,7 @@ var ConnectionPoolManager = class {
|
|
|
3653
4117
|
async removePool(name) {
|
|
3654
4118
|
const pool = this.pools.get(name);
|
|
3655
4119
|
if (!pool) {
|
|
3656
|
-
throw
|
|
4120
|
+
throw createError(ErrorCodes.POOL_NOT_FOUND, `Pool '${name}' not found`);
|
|
3657
4121
|
}
|
|
3658
4122
|
this.stopHealthCheck(name);
|
|
3659
4123
|
await pool.client.close();
|
|
@@ -3677,27 +4141,28 @@ var ConnectionPoolManager = class {
|
|
|
3677
4141
|
selectPool(operation, options = {}) {
|
|
3678
4142
|
if (options.pool) {
|
|
3679
4143
|
const poolData2 = this.pools.get(options.pool);
|
|
3680
|
-
if (!poolData2) throw
|
|
4144
|
+
if (!poolData2) throw createError(ErrorCodes.POOL_NOT_FOUND, `Pool '${options.pool}' not found`);
|
|
3681
4145
|
return this._createPoolResult(options.pool, poolData2.client);
|
|
3682
4146
|
}
|
|
3683
4147
|
let candidates = this._getHealthyPools();
|
|
3684
4148
|
if (candidates.length === 0) {
|
|
3685
4149
|
if (!this.fallback.enabled) {
|
|
3686
|
-
throw
|
|
4150
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "No available connection pool");
|
|
3687
4151
|
}
|
|
3688
4152
|
candidates = this._handleAllPoolsDown(operation);
|
|
3689
4153
|
if (candidates.length === 0) {
|
|
3690
|
-
throw
|
|
4154
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "No available connection pool");
|
|
3691
4155
|
}
|
|
3692
4156
|
}
|
|
4157
|
+
const poolPreference = options.poolPreference ?? (options.tags?.length ? { tags: options.tags } : void 0);
|
|
3693
4158
|
const poolName = this._selector.select(candidates, {
|
|
3694
4159
|
operation,
|
|
3695
4160
|
stats: this._stats.getAllStats(),
|
|
3696
|
-
poolPreference
|
|
4161
|
+
poolPreference
|
|
3697
4162
|
});
|
|
3698
4163
|
const poolData = this.pools.get(poolName);
|
|
3699
4164
|
if (!poolData) {
|
|
3700
|
-
throw
|
|
4165
|
+
throw createError(ErrorCodes.INVALID_OPERATION, `Selected pool '${poolName}' not available`);
|
|
3701
4166
|
}
|
|
3702
4167
|
this._stats.recordSelection(poolName, operation);
|
|
3703
4168
|
this.recordSelection(poolName, true);
|
|
@@ -3820,10 +4285,12 @@ var ConnectionPoolManager = class {
|
|
|
3820
4285
|
_getHealthyPools() {
|
|
3821
4286
|
const result = [];
|
|
3822
4287
|
for (const [name, config] of this._configs.entries()) {
|
|
3823
|
-
const
|
|
3824
|
-
|
|
3825
|
-
|
|
4288
|
+
const compatStatus = this._healthChecker.getStatus(name);
|
|
4289
|
+
const publicStatus = this.healthStatus.get(name);
|
|
4290
|
+
if (compatStatus?.status === "down" || publicStatus?.status === "down") {
|
|
4291
|
+
continue;
|
|
3826
4292
|
}
|
|
4293
|
+
result.push(config);
|
|
3827
4294
|
}
|
|
3828
4295
|
return result;
|
|
3829
4296
|
}
|
|
@@ -3976,7 +4443,7 @@ async function initializeDistributedCacheInvalidator(options, cache, logger) {
|
|
|
3976
4443
|
logger
|
|
3977
4444
|
});
|
|
3978
4445
|
} catch (err) {
|
|
3979
|
-
logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014
|
|
4446
|
+
logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 check Redis config or package installation completeness.", err);
|
|
3980
4447
|
return null;
|
|
3981
4448
|
}
|
|
3982
4449
|
}
|
|
@@ -3984,7 +4451,7 @@ async function loadModelFiles(options, logger, opts = {}) {
|
|
|
3984
4451
|
const modelsConfig = options.models;
|
|
3985
4452
|
if (!modelsConfig) return;
|
|
3986
4453
|
if (typeof modelsConfig !== "string" && typeof modelsConfig !== "object") return;
|
|
3987
|
-
const { readdirSync } = await import("node:fs");
|
|
4454
|
+
const { readdirSync: readdirSync2 } = await import("node:fs");
|
|
3988
4455
|
const { resolve, join, isAbsolute } = await import("node:path");
|
|
3989
4456
|
const { createRequire } = await import("node:module");
|
|
3990
4457
|
let targetPath;
|
|
@@ -4008,7 +4475,7 @@ async function loadModelFiles(options, logger, opts = {}) {
|
|
|
4008
4475
|
const collectFiles = (dir) => {
|
|
4009
4476
|
let entries;
|
|
4010
4477
|
try {
|
|
4011
|
-
entries =
|
|
4478
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
4012
4479
|
} catch {
|
|
4013
4480
|
logger.warn?.(`[Models] cannot read directory: ${dir}`);
|
|
4014
4481
|
return [];
|
|
@@ -4487,7 +4954,7 @@ async function indexStatsForAccessor(collectionRef) {
|
|
|
4487
4954
|
}
|
|
4488
4955
|
async function setValidatorForAccessor(collectionRef, collectionName, dbRef, validator, options = {}) {
|
|
4489
4956
|
if (validator === null || typeof validator !== "object") {
|
|
4490
|
-
throw
|
|
4957
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Validator must be a non-null object");
|
|
4491
4958
|
}
|
|
4492
4959
|
const isEmptyValidator = Object.keys(validator).length === 0;
|
|
4493
4960
|
const command = {
|
|
@@ -4508,14 +4975,14 @@ async function setValidatorForAccessor(collectionRef, collectionName, dbRef, val
|
|
|
4508
4975
|
}
|
|
4509
4976
|
async function setValidationLevelForAccessor(collectionRef, collectionName, dbRef, level) {
|
|
4510
4977
|
if (typeof level !== "string" || !["off", "strict", "moderate"].includes(level)) {
|
|
4511
|
-
throw
|
|
4978
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, 'Invalid validation level: must be "off", "strict", or "moderate"');
|
|
4512
4979
|
}
|
|
4513
4980
|
const result = await resolveDb(collectionRef, dbRef).command({ collMod: collectionName, validationLevel: level });
|
|
4514
4981
|
return { ok: result["ok"], validationLevel: level };
|
|
4515
4982
|
}
|
|
4516
4983
|
async function setValidationActionForAccessor(collectionRef, collectionName, dbRef, action) {
|
|
4517
4984
|
if (typeof action !== "string" || !["error", "warn"].includes(action)) {
|
|
4518
|
-
throw
|
|
4985
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, 'Invalid validation action: must be "error" or "warn"');
|
|
4519
4986
|
}
|
|
4520
4987
|
const result = await resolveDb(collectionRef, dbRef).command({ collMod: collectionName, validationAction: action });
|
|
4521
4988
|
return { ok: result["ok"], validationAction: action };
|
|
@@ -4547,14 +5014,14 @@ async function statsForAccessor(collectionRef, dbName, collectionName, options =
|
|
|
4547
5014
|
}
|
|
4548
5015
|
async function renameCollectionForAccessor(collectionRef, collectionName, newName, options = {}) {
|
|
4549
5016
|
if (!newName || typeof newName !== "string") {
|
|
4550
|
-
throw
|
|
5017
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "New collection name is required and must be a non-empty string");
|
|
4551
5018
|
}
|
|
4552
5019
|
await collectionRef.rename(newName, { dropTarget: options.dropTarget ?? false });
|
|
4553
5020
|
return { renamed: true, from: collectionName, to: newName };
|
|
4554
5021
|
}
|
|
4555
5022
|
async function collModForAccessor(collectionRef, collectionName, dbRef, modifications) {
|
|
4556
5023
|
if (modifications === null || typeof modifications !== "object") {
|
|
4557
|
-
throw
|
|
5024
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Modifications must be a non-null object");
|
|
4558
5025
|
}
|
|
4559
5026
|
return resolveDb(collectionRef, dbRef).command({
|
|
4560
5027
|
collMod: collectionName,
|
|
@@ -4563,10 +5030,10 @@ async function collModForAccessor(collectionRef, collectionName, dbRef, modifica
|
|
|
4563
5030
|
}
|
|
4564
5031
|
async function convertToCappedForAccessor(collectionRef, collectionName, dbRef, size, options = {}) {
|
|
4565
5032
|
if (typeof size !== "number") {
|
|
4566
|
-
throw
|
|
5033
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Size must be a number");
|
|
4567
5034
|
}
|
|
4568
5035
|
if (size <= 0) {
|
|
4569
|
-
throw
|
|
5036
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Size must be a positive number");
|
|
4570
5037
|
}
|
|
4571
5038
|
const command = { convertToCapped: collectionName, size };
|
|
4572
5039
|
if (options.max !== void 0) {
|
|
@@ -4747,7 +5214,7 @@ function dispatchFunction(name, argsStr) {
|
|
|
4747
5214
|
}
|
|
4748
5215
|
case "REDUCE": {
|
|
4749
5216
|
const lambdaMatch = /\((\w+),\s*(\w+)\)\s*=>\s*(.+)/.exec(args[2]);
|
|
4750
|
-
if (!lambdaMatch) throw
|
|
5217
|
+
if (!lambdaMatch) throw createError(ErrorCodes.INVALID_EXPRESSION, "REDUCE requires a lambda: (acc, item) => expr");
|
|
4751
5218
|
const [, accVar, itemVar, lambdaExpr] = lambdaMatch;
|
|
4752
5219
|
const compiledExpr = lambdaExpr.replace(new RegExp(`\\b${accVar}\\b`, "g"), "$$value").replace(new RegExp(`\\b${itemVar}\\b`, "g"), "$$this");
|
|
4753
5220
|
return { $reduce: { input: parseValue(args[0]), initialValue: parseValue(args[1]), in: compileInnerExpression(compiledExpr) } };
|
|
@@ -4892,7 +5359,7 @@ function dispatchFunction(name, argsStr) {
|
|
|
4892
5359
|
return { $setUnion: unionArgs };
|
|
4893
5360
|
}
|
|
4894
5361
|
case "SWITCH": {
|
|
4895
|
-
if (args.length < 2) throw
|
|
5362
|
+
if (args.length < 2) throw createError(ErrorCodes.INVALID_EXPRESSION, "SWITCH requires at least 2 arguments");
|
|
4896
5363
|
const branches = [];
|
|
4897
5364
|
let defaultValue = null;
|
|
4898
5365
|
for (let index = 0; index < args.length - 1; index += 2) {
|
|
@@ -4910,15 +5377,15 @@ function dispatchFunction(name, argsStr) {
|
|
|
4910
5377
|
case "ANY_ELEMENT_TRUE":
|
|
4911
5378
|
return { $anyElementTrue: [parseValue(args[0])] };
|
|
4912
5379
|
case "COND": {
|
|
4913
|
-
if (args.length !== 3) throw
|
|
5380
|
+
if (args.length !== 3) throw createError(ErrorCodes.INVALID_EXPRESSION, "COND requires 3 arguments");
|
|
4914
5381
|
return { $cond: { if: compileInnerExpression(args[0]), then: parseValue(args[1]), else: parseValue(args[2]) } };
|
|
4915
5382
|
}
|
|
4916
5383
|
case "IF_NULL": {
|
|
4917
|
-
if (args.length !== 2) throw
|
|
5384
|
+
if (args.length !== 2) throw createError(ErrorCodes.INVALID_EXPRESSION, "IF_NULL requires 2 arguments");
|
|
4918
5385
|
return { $ifNull: [parseValue(args[0]), parseValue(args[1])] };
|
|
4919
5386
|
}
|
|
4920
5387
|
case "SET_FIELD": {
|
|
4921
|
-
if (args.length !== 3) throw
|
|
5388
|
+
if (args.length !== 3) throw createError(ErrorCodes.INVALID_EXPRESSION, "SET_FIELD requires 3 arguments: (field, value, input)");
|
|
4922
5389
|
return { $setField: { field: parseValue(args[0]), input: parseValue(args[2]), value: parseValue(args[1]) } };
|
|
4923
5390
|
}
|
|
4924
5391
|
case "UNSET_FIELD":
|
|
@@ -4935,7 +5402,7 @@ function dispatchFunction(name, argsStr) {
|
|
|
4935
5402
|
return { $setIsSubset: [parseValue(args[0]), parseValue(args[1])] };
|
|
4936
5403
|
case "LET": {
|
|
4937
5404
|
const varsMatch = /\{(.+)\}/.exec(args[0]);
|
|
4938
|
-
if (!varsMatch) throw
|
|
5405
|
+
if (!varsMatch) throw createError(ErrorCodes.INVALID_EXPRESSION, "LET requires an object literal for variables");
|
|
4939
5406
|
const varPairs = varsMatch[1].split(",").map((pair) => {
|
|
4940
5407
|
const [key, ...rest] = pair.split(":");
|
|
4941
5408
|
return [key.trim(), rest.join(":").trim()];
|
|
@@ -4953,7 +5420,7 @@ function dispatchFunction(name, argsStr) {
|
|
|
4953
5420
|
case "SAMPLE_RATE":
|
|
4954
5421
|
return { $sampleRate: parseValue(args[0]) };
|
|
4955
5422
|
default:
|
|
4956
|
-
throw
|
|
5423
|
+
throw createError(ErrorCodes.INVALID_EXPRESSION, `Unsupported function: ${name}`);
|
|
4957
5424
|
}
|
|
4958
5425
|
}
|
|
4959
5426
|
function compileFilterCondition(condition, varName) {
|
|
@@ -5280,7 +5747,7 @@ function decodeCursor(cursor, secret) {
|
|
|
5280
5747
|
}
|
|
5281
5748
|
const payload = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
5282
5749
|
if (payload?.v !== 1 || !Array.isArray(payload.values)) {
|
|
5283
|
-
throw
|
|
5750
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Invalid cursor payload.");
|
|
5284
5751
|
}
|
|
5285
5752
|
return payload.values;
|
|
5286
5753
|
} catch (cause) {
|
|
@@ -5379,6 +5846,7 @@ function buildEffectiveProjection(projection, sort) {
|
|
|
5379
5846
|
}
|
|
5380
5847
|
|
|
5381
5848
|
// src/adapters/mongodb/queries/find-page.ts
|
|
5849
|
+
var import_node_crypto4 = require("node:crypto");
|
|
5382
5850
|
function normalizePositiveInteger(value, fallback, field) {
|
|
5383
5851
|
if (value === void 0 || value === null) {
|
|
5384
5852
|
return fallback;
|
|
@@ -5397,54 +5865,192 @@ function mergeFilters(base, extra) {
|
|
|
5397
5865
|
}
|
|
5398
5866
|
return { $and: [base, extra] };
|
|
5399
5867
|
}
|
|
5868
|
+
function stableStringify2(value) {
|
|
5869
|
+
if (value === void 0) {
|
|
5870
|
+
return '"__undefined__"';
|
|
5871
|
+
}
|
|
5872
|
+
if (value === null) {
|
|
5873
|
+
return "null";
|
|
5874
|
+
}
|
|
5875
|
+
if (Array.isArray(value)) {
|
|
5876
|
+
return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
|
|
5877
|
+
}
|
|
5878
|
+
if (value instanceof Date) {
|
|
5879
|
+
return JSON.stringify(value.toISOString());
|
|
5880
|
+
}
|
|
5881
|
+
if (typeof value === "object") {
|
|
5882
|
+
const customJson = value.toJSON;
|
|
5883
|
+
if (typeof customJson === "function" && value.constructor?.name !== "Object") {
|
|
5884
|
+
return stableStringify2(customJson.call(value));
|
|
5885
|
+
}
|
|
5886
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`);
|
|
5887
|
+
return `{${entries.join(",")}}`;
|
|
5888
|
+
}
|
|
5889
|
+
return JSON.stringify(value);
|
|
5890
|
+
}
|
|
5891
|
+
function hashPayload(payload) {
|
|
5892
|
+
return (0, import_node_crypto4.createHash)("sha256").update(stableStringify2(payload)).digest("hex");
|
|
5893
|
+
}
|
|
5894
|
+
function buildFindPageCacheKey(collection, options, normalized) {
|
|
5895
|
+
const payload = {
|
|
5896
|
+
query: normalized.query,
|
|
5897
|
+
sort: normalized.sort,
|
|
5898
|
+
limit: normalized.limit,
|
|
5899
|
+
page: normalized.page,
|
|
5900
|
+
after: options.after,
|
|
5901
|
+
before: options.before,
|
|
5902
|
+
projection: options.projection,
|
|
5903
|
+
pipeline: options.pipeline ?? [],
|
|
5904
|
+
totals: options.totals,
|
|
5905
|
+
jump: options.jump,
|
|
5906
|
+
offsetJump: options.offsetJump,
|
|
5907
|
+
maxTimeMS: normalized.maxTimeMS,
|
|
5908
|
+
hint: options.hint,
|
|
5909
|
+
collation: options.collation,
|
|
5910
|
+
batchSize: options.batchSize,
|
|
5911
|
+
options: options.options
|
|
5912
|
+
};
|
|
5913
|
+
const keyHash = hashPayload(payload);
|
|
5914
|
+
return { key: `findPage:${collection.namespace}:${keyHash}`, keyHash };
|
|
5915
|
+
}
|
|
5916
|
+
function buildTotalsCacheKey(collection, query, limit, totals) {
|
|
5917
|
+
const payload = {
|
|
5918
|
+
query,
|
|
5919
|
+
limit,
|
|
5920
|
+
mode: totals.mode ?? "sync",
|
|
5921
|
+
hint: totals.hint,
|
|
5922
|
+
collation: totals.collation,
|
|
5923
|
+
maxTimeMS: totals.maxTimeMS
|
|
5924
|
+
};
|
|
5925
|
+
const token = hashPayload(payload);
|
|
5926
|
+
return { key: `findPageTotals:${collection.namespace}:${token}`, token };
|
|
5927
|
+
}
|
|
5928
|
+
function cloneFindPageResult(result) {
|
|
5929
|
+
return {
|
|
5930
|
+
...result,
|
|
5931
|
+
items: Array.isArray(result.items) ? [...result.items] : result.items,
|
|
5932
|
+
pageInfo: result.pageInfo && typeof result.pageInfo === "object" ? { ...result.pageInfo } : result.pageInfo,
|
|
5933
|
+
totals: result.totals && typeof result.totals === "object" ? { ...result.totals } : result.totals,
|
|
5934
|
+
meta: result.meta && typeof result.meta === "object" ? {
|
|
5935
|
+
...result.meta,
|
|
5936
|
+
ns: { ...result.meta.ns },
|
|
5937
|
+
steps: result.meta.steps ? [...result.meta.steps] : void 0
|
|
5938
|
+
} : result.meta
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
function getPositiveTtl(value, fallback) {
|
|
5942
|
+
return typeof value === "number" && value > 0 ? value : fallback;
|
|
5943
|
+
}
|
|
5400
5944
|
var _asyncTotalsCache = new MemoryCache({
|
|
5401
5945
|
maxEntries: 1e4,
|
|
5402
5946
|
enableStats: false
|
|
5403
5947
|
});
|
|
5404
|
-
|
|
5948
|
+
var _totalsInflight = /* @__PURE__ */ new Map();
|
|
5949
|
+
function runTotalsOnce(key, task) {
|
|
5950
|
+
if (_totalsInflight.has(key)) {
|
|
5951
|
+
return;
|
|
5952
|
+
}
|
|
5953
|
+
const promise = task().catch(() => {
|
|
5954
|
+
}).finally(() => {
|
|
5955
|
+
_totalsInflight.delete(key);
|
|
5956
|
+
});
|
|
5957
|
+
_totalsInflight.set(key, promise);
|
|
5958
|
+
}
|
|
5959
|
+
async function computeTotals(coll, query, limit, totals, defaults = {}, queryCache) {
|
|
5405
5960
|
const mode = totals.mode ?? "sync";
|
|
5406
|
-
|
|
5961
|
+
const cache = queryCache ?? _asyncTotalsCache;
|
|
5962
|
+
const ttlMs = getPositiveTtl(totals.ttlMs, 10 * 6e4);
|
|
5963
|
+
const { key: cacheKey, token } = buildTotalsCacheKey(coll, query, limit, totals);
|
|
5964
|
+
const buildCountOptions = (fallbackMaxTimeMS) => {
|
|
5407
5965
|
const countOpts = {};
|
|
5408
|
-
|
|
5409
|
-
|
|
5966
|
+
const maxTimeMS = totals.maxTimeMS ?? fallbackMaxTimeMS;
|
|
5967
|
+
if (maxTimeMS !== void 0) {
|
|
5968
|
+
countOpts.maxTimeMS = maxTimeMS;
|
|
5969
|
+
}
|
|
5970
|
+
if (totals.hint !== void 0) {
|
|
5971
|
+
countOpts.hint = totals.hint;
|
|
5410
5972
|
}
|
|
5411
|
-
|
|
5412
|
-
|
|
5973
|
+
if (totals.collation !== void 0) {
|
|
5974
|
+
countOpts.collation = totals.collation;
|
|
5975
|
+
}
|
|
5976
|
+
return countOpts;
|
|
5977
|
+
};
|
|
5978
|
+
const countWithOptions = async () => {
|
|
5979
|
+
const countOpts = buildCountOptions(2e3);
|
|
5980
|
+
const countQuery = query;
|
|
5981
|
+
const runner = () => coll.countDocuments(
|
|
5982
|
+
countQuery,
|
|
5413
5983
|
countOpts
|
|
5414
5984
|
);
|
|
5415
|
-
|
|
5416
|
-
|
|
5985
|
+
return defaults.countQueue ? defaults.countQueue.execute(runner) : runner();
|
|
5986
|
+
};
|
|
5987
|
+
const buildPayload = (total, approx = false) => ({
|
|
5988
|
+
mode,
|
|
5989
|
+
total,
|
|
5990
|
+
totalPages: total > 0 ? Math.ceil(total / limit) : 0,
|
|
5991
|
+
ts: Date.now(),
|
|
5992
|
+
...approx ? { approx: true } : {}
|
|
5993
|
+
});
|
|
5994
|
+
const buildFailurePayload = (error) => ({
|
|
5995
|
+
mode,
|
|
5996
|
+
total: null,
|
|
5997
|
+
totalPages: null,
|
|
5998
|
+
ts: Date.now(),
|
|
5999
|
+
error
|
|
6000
|
+
});
|
|
6001
|
+
if (mode === "sync") {
|
|
6002
|
+
const cached = cache.get(cacheKey);
|
|
6003
|
+
if (cached !== void 0) {
|
|
6004
|
+
return { ...cached, mode: "sync" };
|
|
6005
|
+
}
|
|
6006
|
+
try {
|
|
6007
|
+
const payload = buildPayload(await countWithOptions());
|
|
6008
|
+
await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
|
|
6009
|
+
return { ...payload, mode: "sync" };
|
|
6010
|
+
} catch {
|
|
6011
|
+
const payload = buildFailurePayload("count_failed");
|
|
6012
|
+
await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
|
|
6013
|
+
return { ...payload, mode: "sync" };
|
|
6014
|
+
}
|
|
5417
6015
|
}
|
|
5418
6016
|
if (mode === "async") {
|
|
5419
|
-
const
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
if (cachedTotal !== void 0) {
|
|
5423
|
-
return { mode: "async", total: cachedTotal, token };
|
|
6017
|
+
const cached = cache.get(cacheKey);
|
|
6018
|
+
if (cached !== void 0) {
|
|
6019
|
+
return { ...cached, mode: "async", token };
|
|
5424
6020
|
}
|
|
5425
|
-
setImmediate(
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
6021
|
+
setImmediate(() => {
|
|
6022
|
+
runTotalsOnce(cacheKey, async () => {
|
|
6023
|
+
try {
|
|
6024
|
+
const payload = buildPayload(await countWithOptions());
|
|
6025
|
+
await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
|
|
6026
|
+
} catch {
|
|
6027
|
+
await Promise.resolve(cache.set(cacheKey, buildFailurePayload("count_failed"), ttlMs));
|
|
6028
|
+
}
|
|
6029
|
+
});
|
|
5433
6030
|
});
|
|
5434
6031
|
return { mode: "async", total: null, token };
|
|
5435
6032
|
}
|
|
5436
6033
|
if (mode === "approx") {
|
|
5437
|
-
const
|
|
5438
|
-
if (
|
|
5439
|
-
|
|
6034
|
+
const cached = cache.get(cacheKey);
|
|
6035
|
+
if (cached !== void 0) {
|
|
6036
|
+
return { ...cached, mode: "approx" };
|
|
6037
|
+
}
|
|
6038
|
+
try {
|
|
6039
|
+
const total = Object.keys(query ?? {}).length > 0 ? await countWithOptions() : await coll.estimatedDocumentCount({
|
|
6040
|
+
maxTimeMS: totals.maxTimeMS ?? 1e3
|
|
6041
|
+
});
|
|
6042
|
+
const payload = buildPayload(total, true);
|
|
6043
|
+
await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
|
|
6044
|
+
return { ...payload, mode: "approx" };
|
|
6045
|
+
} catch {
|
|
6046
|
+
const payload = buildFailurePayload("approx_failed");
|
|
6047
|
+
await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
|
|
6048
|
+
return { ...payload, mode: "approx" };
|
|
5440
6049
|
}
|
|
5441
|
-
const total = await coll.estimatedDocumentCount(countOpts);
|
|
5442
|
-
const totalPages = total > 0 ? Math.ceil(total / limit) : 0;
|
|
5443
|
-
return { mode: "approx", total, totalPages, ts: Date.now() };
|
|
5444
6050
|
}
|
|
5445
6051
|
return { mode: mode ?? "sync" };
|
|
5446
6052
|
}
|
|
5447
|
-
async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
6053
|
+
async function executeFindPage(collection, options = {}, defaults = {}, queryCache) {
|
|
5448
6054
|
const metaEnabled = options.meta === true || typeof options.meta === "object" && options.meta !== null;
|
|
5449
6055
|
const metaOptions = options.meta && typeof options.meta === "object" ? options.meta : {};
|
|
5450
6056
|
const metaLevel = options.meta === true ? "op" : metaOptions.level ?? "op";
|
|
@@ -5475,6 +6081,10 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5475
6081
|
driverOpts.projection = buildEffectiveProjection(options.projection, sort);
|
|
5476
6082
|
}
|
|
5477
6083
|
const jumpOpts = ext.jump;
|
|
6084
|
+
const cacheTTL = typeof ext.cache === "number" && ext.cache > 0 ? ext.cache : 0;
|
|
6085
|
+
const pageResultCache = cacheTTL > 0 && queryCache && options.stream !== true && (options.explain === void 0 || options.explain === false) ? buildFindPageCacheKey(collection, options, { query: baseQuery, sort, limit, page, maxTimeMS: effectiveMaxTimeMS }) : null;
|
|
6086
|
+
const shouldRefreshAsyncTotals = options.totals?.mode === "async";
|
|
6087
|
+
let findPageCacheHit = false;
|
|
5478
6088
|
const finishResult = (result2) => {
|
|
5479
6089
|
if (!metaEnabled) {
|
|
5480
6090
|
return result2;
|
|
@@ -5505,6 +6115,9 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5505
6115
|
endTs: metaEndTs,
|
|
5506
6116
|
durationMs: metaEndTs - metaStartTs,
|
|
5507
6117
|
...typeof effectiveMaxTimeMS === "number" ? { maxTimeMS: effectiveMaxTimeMS } : {},
|
|
6118
|
+
cacheHit: findPageCacheHit,
|
|
6119
|
+
...findPageCacheHit ? { fromCache: true } : {},
|
|
6120
|
+
...pageResultCache ? { cacheTtl: cacheTTL, keyHash: pageResultCache.keyHash } : {},
|
|
5508
6121
|
page,
|
|
5509
6122
|
after: Boolean(options.after),
|
|
5510
6123
|
before: Boolean(options.before),
|
|
@@ -5573,7 +6186,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5573
6186
|
};
|
|
5574
6187
|
const timedComputeTotals = async () => {
|
|
5575
6188
|
const stepStartTs = Date.now();
|
|
5576
|
-
const result2 = await computeTotals(collection, baseQuery, limit, options.totals);
|
|
6189
|
+
const result2 = await computeTotals(collection, baseQuery, limit, options.totals, defaults, queryCache);
|
|
5577
6190
|
pushMetaStep("computeTotals", Date.now() - stepStartTs, "totals");
|
|
5578
6191
|
return result2;
|
|
5579
6192
|
};
|
|
@@ -5592,6 +6205,32 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5592
6205
|
...extra.currentPage !== void 0 ? { currentPage: extra.currentPage } : {}
|
|
5593
6206
|
};
|
|
5594
6207
|
};
|
|
6208
|
+
const writePageResultCache = (result2) => {
|
|
6209
|
+
if (!pageResultCache || !queryCache) {
|
|
6210
|
+
return;
|
|
6211
|
+
}
|
|
6212
|
+
const cacheValue = cloneFindPageResult(result2);
|
|
6213
|
+
delete cacheValue.meta;
|
|
6214
|
+
if (shouldRefreshAsyncTotals) {
|
|
6215
|
+
delete cacheValue.totals;
|
|
6216
|
+
}
|
|
6217
|
+
void queryCache.set(pageResultCache.key, cacheValue, cacheTTL);
|
|
6218
|
+
};
|
|
6219
|
+
const finishAndCache = (result2) => {
|
|
6220
|
+
writePageResultCache(result2);
|
|
6221
|
+
return finishResult(result2);
|
|
6222
|
+
};
|
|
6223
|
+
if (pageResultCache && queryCache) {
|
|
6224
|
+
const cached = queryCache.get(pageResultCache.key);
|
|
6225
|
+
if (cached !== void 0) {
|
|
6226
|
+
findPageCacheHit = true;
|
|
6227
|
+
const result2 = cloneFindPageResult(cached);
|
|
6228
|
+
if (options.totals && options.totals.mode !== "none" && (shouldRefreshAsyncTotals || result2.totals === void 0)) {
|
|
6229
|
+
result2.totals = await timedComputeTotals();
|
|
6230
|
+
}
|
|
6231
|
+
return finishResult(result2);
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
5595
6234
|
if (options.stream === true) {
|
|
5596
6235
|
const direction = options.before ? "before" : "after";
|
|
5597
6236
|
const { queryFilter, effectiveSort } = buildPageQuery(options.after ?? options.before, direction);
|
|
@@ -5634,7 +6273,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5634
6273
|
if (options.totals && options.totals.mode !== "none") {
|
|
5635
6274
|
result2.totals = await timedComputeTotals();
|
|
5636
6275
|
}
|
|
5637
|
-
return
|
|
6276
|
+
return finishAndCache(result2);
|
|
5638
6277
|
}
|
|
5639
6278
|
if (options.after || options.before) {
|
|
5640
6279
|
const direction = options.after ? "after" : "before";
|
|
@@ -5644,7 +6283,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5644
6283
|
const first = items2[0] ?? null;
|
|
5645
6284
|
const last = items2[items2.length - 1] ?? null;
|
|
5646
6285
|
const enc = (item) => item ? encodeCursor(Object.keys(sort).map((f) => item[f]), cursorSecret) : null;
|
|
5647
|
-
|
|
6286
|
+
const result2 = {
|
|
5648
6287
|
items: items2,
|
|
5649
6288
|
pageInfo: {
|
|
5650
6289
|
hasNext: direction === "before" ? Boolean(options.before) : hasMore2,
|
|
@@ -5652,7 +6291,11 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5652
6291
|
startCursor: enc(first),
|
|
5653
6292
|
endCursor: enc(last)
|
|
5654
6293
|
}
|
|
5655
|
-
}
|
|
6294
|
+
};
|
|
6295
|
+
if (options.totals && options.totals.mode !== "none") {
|
|
6296
|
+
result2.totals = await timedComputeTotals();
|
|
6297
|
+
}
|
|
6298
|
+
return finishAndCache(result2);
|
|
5656
6299
|
}
|
|
5657
6300
|
const { queryFilter: q0, effectiveSort: es0 } = buildPageQuery();
|
|
5658
6301
|
let { items, hasMore } = await timedFetchItems("initialFetch", page > 1 ? "hop" : "fetch", q0, es0, {}, 1);
|
|
@@ -5664,15 +6307,19 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5664
6307
|
if (options.totals && options.totals.mode !== "none") {
|
|
5665
6308
|
result2.totals = await timedComputeTotals();
|
|
5666
6309
|
}
|
|
5667
|
-
return
|
|
6310
|
+
return finishAndCache(result2);
|
|
5668
6311
|
}
|
|
5669
6312
|
for (let cp = 2; cp <= page; cp++) {
|
|
5670
6313
|
const lastItem = items[items.length - 1];
|
|
5671
6314
|
if (!lastItem) {
|
|
5672
|
-
|
|
6315
|
+
const result2 = {
|
|
5673
6316
|
items,
|
|
5674
6317
|
pageInfo: buildPageInfo(items, false, { hasPrev: cp > 2, currentPage: cp - 1 })
|
|
5675
|
-
}
|
|
6318
|
+
};
|
|
6319
|
+
if (options.totals && options.totals.mode !== "none") {
|
|
6320
|
+
result2.totals = await timedComputeTotals();
|
|
6321
|
+
}
|
|
6322
|
+
return finishAndCache(result2);
|
|
5676
6323
|
}
|
|
5677
6324
|
const endCursor = encodeCursor(
|
|
5678
6325
|
Object.keys(sort).map((f) => lastItem[f]),
|
|
@@ -5690,10 +6337,10 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
|
|
|
5690
6337
|
if (options.totals && options.totals.mode !== "none") {
|
|
5691
6338
|
result.totals = await timedComputeTotals();
|
|
5692
6339
|
}
|
|
5693
|
-
return
|
|
6340
|
+
return finishAndCache(result);
|
|
5694
6341
|
}
|
|
5695
|
-
async function findPageDocuments(collection, options = {}, defaults) {
|
|
5696
|
-
return executeFindPage(collection, options, defaults ?? {});
|
|
6342
|
+
async function findPageDocuments(collection, options = {}, defaults, queryCache) {
|
|
6343
|
+
return executeFindPage(collection, options, defaults ?? {}, queryCache);
|
|
5697
6344
|
}
|
|
5698
6345
|
|
|
5699
6346
|
// src/adapters/mongodb/queries/find-by-id.ts
|
|
@@ -5915,21 +6562,21 @@ var FindChain = class {
|
|
|
5915
6562
|
}
|
|
5916
6563
|
limit(value) {
|
|
5917
6564
|
if (typeof value !== "number" || value < 0) {
|
|
5918
|
-
throw
|
|
6565
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, `limit() requires a non-negative number, got: ${typeof value} (${value})`);
|
|
5919
6566
|
}
|
|
5920
6567
|
this.options.limit = value;
|
|
5921
6568
|
return this;
|
|
5922
6569
|
}
|
|
5923
6570
|
skip(value) {
|
|
5924
6571
|
if (typeof value !== "number" || value < 0) {
|
|
5925
|
-
throw
|
|
6572
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, `skip() requires a non-negative number, got: ${typeof value} (${value})`);
|
|
5926
6573
|
}
|
|
5927
6574
|
this.options.skip = value;
|
|
5928
6575
|
return this;
|
|
5929
6576
|
}
|
|
5930
6577
|
sort(value) {
|
|
5931
6578
|
if (!value || typeof value !== "object") {
|
|
5932
|
-
throw
|
|
6579
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, `sort() requires an object or array, got: ${typeof value}`);
|
|
5933
6580
|
}
|
|
5934
6581
|
this.options.sort = value;
|
|
5935
6582
|
return this;
|
|
@@ -5973,7 +6620,7 @@ var FindChain = class {
|
|
|
5973
6620
|
}
|
|
5974
6621
|
toArray() {
|
|
5975
6622
|
if (this.executed) {
|
|
5976
|
-
throw
|
|
6623
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
|
|
5977
6624
|
}
|
|
5978
6625
|
this.executed = true;
|
|
5979
6626
|
return this.collection.find(this.normalizedQuery, buildFindDriverOptions(this.buildExecuteOptions())).toArray();
|
|
@@ -6061,7 +6708,7 @@ var AggregateChain = class {
|
|
|
6061
6708
|
}
|
|
6062
6709
|
toArray() {
|
|
6063
6710
|
if (this.executed) {
|
|
6064
|
-
throw
|
|
6711
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
|
|
6065
6712
|
}
|
|
6066
6713
|
this.executed = true;
|
|
6067
6714
|
return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).toArray();
|
|
@@ -6856,7 +7503,7 @@ async function insertOneForAccessor(context, doc, options) {
|
|
|
6856
7503
|
const threshold = context.defaults?.slowQueryMs ?? 500;
|
|
6857
7504
|
if (elapsed > threshold && context.logger) {
|
|
6858
7505
|
try {
|
|
6859
|
-
context.logger.warn("[insertOne]
|
|
7506
|
+
context.logger.warn("[insertOne] slow operation warning", {
|
|
6860
7507
|
ns: `${context.dbName}.${context.collectionName}`,
|
|
6861
7508
|
threshold,
|
|
6862
7509
|
duration: elapsed,
|
|
@@ -6901,7 +7548,7 @@ async function insertManyForAccessor(context, documents, options) {
|
|
|
6901
7548
|
const elapsed = Date.now() - startedAt;
|
|
6902
7549
|
const threshold = context.defaults?.slowQueryMs ?? 500;
|
|
6903
7550
|
if (elapsed >= threshold && context.logger) {
|
|
6904
|
-
context.logger.warn("[insertMany]
|
|
7551
|
+
context.logger.warn("[insertMany] slow operation warning", {
|
|
6905
7552
|
ns: `${context.dbName}.${context.collectionName}`,
|
|
6906
7553
|
threshold,
|
|
6907
7554
|
duration: elapsed,
|
|
@@ -7032,11 +7679,17 @@ var MongoCollectionAccessor = class {
|
|
|
7032
7679
|
const legacyNamespacePatterns = [
|
|
7033
7680
|
`${String(this.management.defaults?.namespace?.instanceId)}:mongodb:${this.dbName}:${this.collectionName}:*`
|
|
7034
7681
|
];
|
|
7035
|
-
const
|
|
7682
|
+
const findPagePatterns = [
|
|
7683
|
+
`findPage:${namespace}:*`,
|
|
7684
|
+
`findPageTotals:${namespace}:*`,
|
|
7685
|
+
`bookmark:${bookmarkNamespace}:*`,
|
|
7686
|
+
`${bookmarkNamespace}:bm:*`
|
|
7687
|
+
];
|
|
7688
|
+
const patterns = operation === "find" ? [`find:${namespace}:*`] : operation === "findOne" ? [`findOne:${namespace}:*`] : operation === "count" ? [`count:${namespace}:*`] : operation === "findPage" ? findPagePatterns : [
|
|
7036
7689
|
`find:${namespace}:*`,
|
|
7037
7690
|
`findOne:${namespace}:*`,
|
|
7038
7691
|
`count:${namespace}:*`,
|
|
7039
|
-
|
|
7692
|
+
...findPagePatterns
|
|
7040
7693
|
];
|
|
7041
7694
|
patterns.push(...legacyNamespacePatterns);
|
|
7042
7695
|
let deleted = 0;
|
|
@@ -7166,7 +7819,7 @@ var MongoCollectionAccessor = class {
|
|
|
7166
7819
|
}
|
|
7167
7820
|
async findPage(options = {}) {
|
|
7168
7821
|
const resolvedOptions = options.query ? { ...options, query: this._cvFilter(options.query) } : options;
|
|
7169
|
-
return findPageDocuments(this.collectionRef, resolvedOptions, this.management.defaults);
|
|
7822
|
+
return findPageDocuments(this.collectionRef, resolvedOptions, this.management.defaults, this.management.queryCache);
|
|
7170
7823
|
}
|
|
7171
7824
|
/** Opens a change stream on the collection with an optional aggregation pipeline. */
|
|
7172
7825
|
watch(pipeline = [], options) {
|
|
@@ -7643,6 +8296,26 @@ function createRuntimeModelInstance(host, name, scope) {
|
|
|
7643
8296
|
});
|
|
7644
8297
|
return instance;
|
|
7645
8298
|
}
|
|
8299
|
+
async function ensureRuntimeModelIndexes(host, options = {}) {
|
|
8300
|
+
const modelNames = options.models ?? Model.list();
|
|
8301
|
+
const models = [];
|
|
8302
|
+
for (const name of modelNames) {
|
|
8303
|
+
const model = host.scopedModel(name, {
|
|
8304
|
+
database: options.database,
|
|
8305
|
+
pool: options.pool
|
|
8306
|
+
});
|
|
8307
|
+
const result = await model.ensureIndexes({
|
|
8308
|
+
dryRun: options.dryRun,
|
|
8309
|
+
throwOnError: options.throwOnError
|
|
8310
|
+
});
|
|
8311
|
+
models.push({ name, result });
|
|
8312
|
+
}
|
|
8313
|
+
return {
|
|
8314
|
+
dryRun: options.dryRun === true,
|
|
8315
|
+
models,
|
|
8316
|
+
totals: summarizeModelIndexEnsureResults(models.map((item) => item.result))
|
|
8317
|
+
};
|
|
8318
|
+
}
|
|
7646
8319
|
|
|
7647
8320
|
// src/entry/runtime-core-hosts.ts
|
|
7648
8321
|
function resolveAdapterCache(state) {
|
|
@@ -7858,7 +8531,7 @@ function createRuntimeAdapterBridge(host) {
|
|
|
7858
8531
|
dropDatabase: async (name, adminOptions) => {
|
|
7859
8532
|
host.ensureConnected();
|
|
7860
8533
|
if (!name || typeof name !== "string") {
|
|
7861
|
-
throw
|
|
8534
|
+
throw createError(ErrorCodes.INVALID_DATABASE_NAME, "Database name is required and must be a non-empty string");
|
|
7862
8535
|
}
|
|
7863
8536
|
if (!adminOptions?.confirm) {
|
|
7864
8537
|
const error = new Error(
|
|
@@ -7894,7 +8567,7 @@ function createRuntimeAdapterBridge(host) {
|
|
|
7894
8567
|
runCommand: async (command, adminOptions) => {
|
|
7895
8568
|
host.ensureConnected();
|
|
7896
8569
|
if (command === null || typeof command !== "object") {
|
|
7897
|
-
throw
|
|
8570
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Command must be a non-null object");
|
|
7898
8571
|
}
|
|
7899
8572
|
return host.db().runCommand(command, adminOptions ?? {});
|
|
7900
8573
|
},
|
|
@@ -7910,7 +8583,7 @@ function createRuntimeAdapterBridge(host) {
|
|
|
7910
8583
|
}
|
|
7911
8584
|
|
|
7912
8585
|
// src/capabilities/lock/index.ts
|
|
7913
|
-
var
|
|
8586
|
+
var import_node_crypto5 = require("node:crypto");
|
|
7914
8587
|
var LockAcquireError = class extends Error {
|
|
7915
8588
|
constructor(message) {
|
|
7916
8589
|
super(message);
|
|
@@ -8020,13 +8693,13 @@ var LockManager = class {
|
|
|
8020
8693
|
if (attempt === retryTimes) {
|
|
8021
8694
|
break;
|
|
8022
8695
|
}
|
|
8023
|
-
const
|
|
8024
|
-
await sleep3(
|
|
8696
|
+
const delay2 = retryDelay * Math.pow(retryBackoff, attempt);
|
|
8697
|
+
await sleep3(delay2);
|
|
8025
8698
|
}
|
|
8026
8699
|
this.stats.errors += 1;
|
|
8027
8700
|
if (options.fallbackToNoLock) {
|
|
8028
8701
|
this.logger?.warn?.(`[LockManager] fallback to no-lock execution for ${key}`);
|
|
8029
|
-
return new Lock(this.normalizeKey(key), `noop:${(0,
|
|
8702
|
+
return new Lock(this.normalizeKey(key), `noop:${(0, import_node_crypto5.randomUUID)()}`, new NoopLockManager(), options.ttl ?? 1e4);
|
|
8030
8703
|
}
|
|
8031
8704
|
throw new LockTimeoutError(`Failed to acquire lock for key '${key}' within retry budget.`);
|
|
8032
8705
|
}
|
|
@@ -8042,7 +8715,7 @@ var LockManager = class {
|
|
|
8042
8715
|
if (globalStore.has(normalizedKey)) {
|
|
8043
8716
|
return null;
|
|
8044
8717
|
}
|
|
8045
|
-
const lockId = (0,
|
|
8718
|
+
const lockId = (0, import_node_crypto5.randomUUID)();
|
|
8046
8719
|
globalStore.set(normalizedKey, {
|
|
8047
8720
|
lockId,
|
|
8048
8721
|
expiresAt: Date.now() + ttl
|
|
@@ -8145,7 +8818,7 @@ var DistributedCacheLockManager = class {
|
|
|
8145
8818
|
errors: 0
|
|
8146
8819
|
};
|
|
8147
8820
|
if (!options.redis) {
|
|
8148
|
-
throw
|
|
8821
|
+
throw createError(ErrorCodes.INVALID_CONFIG, "DistributedCacheLockManager requires a Redis instance");
|
|
8149
8822
|
}
|
|
8150
8823
|
this.redis = options.redis;
|
|
8151
8824
|
this.lockKeyPrefix = options.lockKeyPrefix ?? "monsqlize:cache:lock:";
|
|
@@ -8334,7 +9007,7 @@ var DistributedCacheLockManager = class {
|
|
|
8334
9007
|
};
|
|
8335
9008
|
|
|
8336
9009
|
// src/capabilities/saga/index.ts
|
|
8337
|
-
var
|
|
9010
|
+
var import_node_crypto6 = require("node:crypto");
|
|
8338
9011
|
var SagaExecutionContext = class {
|
|
8339
9012
|
constructor(executionId, data) {
|
|
8340
9013
|
this.executionId = executionId;
|
|
@@ -8430,7 +9103,7 @@ var SagaOrchestrator = class {
|
|
|
8430
9103
|
if (!definition) {
|
|
8431
9104
|
throw createError(ErrorCodes.INVALID_ARGUMENT, `Saga '${name}' is not defined`);
|
|
8432
9105
|
}
|
|
8433
|
-
const sagaId = `saga_${(0,
|
|
9106
|
+
const sagaId = `saga_${(0, import_node_crypto6.randomBytes)(8).toString("hex")}`;
|
|
8434
9107
|
const startedAt = Date.now();
|
|
8435
9108
|
const context = new SagaExecutionContext(sagaId, data);
|
|
8436
9109
|
const completedSteps = [];
|
|
@@ -8803,17 +9476,17 @@ var BatchQueue = class {
|
|
|
8803
9476
|
var import_mongodb6 = require("mongodb");
|
|
8804
9477
|
|
|
8805
9478
|
// src/capabilities/slow-query-log/slow-query-log-records.ts
|
|
8806
|
-
var
|
|
8807
|
-
function
|
|
9479
|
+
var import_node_crypto7 = require("node:crypto");
|
|
9480
|
+
function stableStringify3(value) {
|
|
8808
9481
|
if (Array.isArray(value)) {
|
|
8809
|
-
return `[${value.map((item) =>
|
|
9482
|
+
return `[${value.map((item) => stableStringify3(item)).join(",")}]`;
|
|
8810
9483
|
}
|
|
8811
9484
|
if (value instanceof Date) {
|
|
8812
9485
|
return JSON.stringify(value.toISOString());
|
|
8813
9486
|
}
|
|
8814
9487
|
if (value && typeof value === "object") {
|
|
8815
9488
|
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
8816
|
-
return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${
|
|
9489
|
+
return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableStringify3(current)}`).join(",")}}`;
|
|
8817
9490
|
}
|
|
8818
9491
|
return JSON.stringify(value);
|
|
8819
9492
|
}
|
|
@@ -8830,7 +9503,7 @@ function normalizeHashInput(input) {
|
|
|
8830
9503
|
};
|
|
8831
9504
|
}
|
|
8832
9505
|
function generateQueryHash(input) {
|
|
8833
|
-
return (0,
|
|
9506
|
+
return (0, import_node_crypto7.createHash)("sha256").update(stableStringify3(normalizeHashInput(input))).digest("hex").slice(0, 16);
|
|
8834
9507
|
}
|
|
8835
9508
|
function handleSlowQueryLogError(logger, policy, error) {
|
|
8836
9509
|
if (policy === "throw") {
|
|
@@ -9280,7 +9953,7 @@ var SlowQueryLogManager = class {
|
|
|
9280
9953
|
|
|
9281
9954
|
// src/capabilities/sync/index.ts
|
|
9282
9955
|
var import_promises = require("node:fs/promises");
|
|
9283
|
-
var
|
|
9956
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
9284
9957
|
var import_mongodb7 = require("mongodb");
|
|
9285
9958
|
function validateTargetConfig(target, index) {
|
|
9286
9959
|
if (!target || typeof target !== "object") {
|
|
@@ -9387,7 +10060,7 @@ var ResumeTokenStore = class {
|
|
|
9387
10060
|
await Promise.resolve(this.redis.set(this.redisKey, payload));
|
|
9388
10061
|
return;
|
|
9389
10062
|
}
|
|
9390
|
-
await (0, import_promises.mkdir)(
|
|
10063
|
+
await (0, import_promises.mkdir)(import_node_path2.default.dirname(this.path), { recursive: true });
|
|
9391
10064
|
await (0, import_promises.writeFile)(this.path, payload, "utf8");
|
|
9392
10065
|
} catch (error) {
|
|
9393
10066
|
this.logger?.error?.("[Sync] failed to save resume token", error);
|
|
@@ -9659,7 +10332,16 @@ function getOrCreateTransactionManager(config) {
|
|
|
9659
10332
|
client: config.client,
|
|
9660
10333
|
cache: config.cache,
|
|
9661
10334
|
logger: config.logger,
|
|
9662
|
-
lockManager: config.lockManager
|
|
10335
|
+
lockManager: config.lockManager,
|
|
10336
|
+
maxDuration: config.transaction?.maxDuration ?? config.transaction?.defaultTimeout,
|
|
10337
|
+
enableRetry: config.transaction?.enableRetry,
|
|
10338
|
+
maxRetries: config.transaction?.maxRetries,
|
|
10339
|
+
retryDelay: config.transaction?.retryDelay,
|
|
10340
|
+
retryBackoff: config.transaction?.retryBackoff,
|
|
10341
|
+
defaultReadConcern: config.transaction?.defaultReadConcern,
|
|
10342
|
+
defaultWriteConcern: config.transaction?.defaultWriteConcern,
|
|
10343
|
+
defaultReadPreference: config.transaction?.defaultReadPreference,
|
|
10344
|
+
maxStatsSamples: config.transaction?.maxStatsSamples
|
|
9663
10345
|
});
|
|
9664
10346
|
}
|
|
9665
10347
|
function getOrCreateLockManager(current, logger) {
|
|
@@ -9946,7 +10628,7 @@ function resolveCacheSource(cacheOrDb) {
|
|
|
9946
10628
|
return cache;
|
|
9947
10629
|
}
|
|
9948
10630
|
}
|
|
9949
|
-
throw
|
|
10631
|
+
throw createError(ErrorCodes.CACHE_UNAVAILABLE, "Invalid cache instance from MonSQLize");
|
|
9950
10632
|
}
|
|
9951
10633
|
function toWithCacheStats(stats, totalTime = 0) {
|
|
9952
10634
|
const calls = stats.hits + stats.misses;
|
|
@@ -9975,46 +10657,46 @@ function normalizeFunctionCacheStats(stats, timings, name) {
|
|
|
9975
10657
|
}
|
|
9976
10658
|
function validateFunctionCacheOptions(options) {
|
|
9977
10659
|
if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) {
|
|
9978
|
-
throw
|
|
10660
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "options must be an object");
|
|
9979
10661
|
}
|
|
9980
10662
|
const opts = options ?? {};
|
|
9981
10663
|
if (opts.namespace !== void 0 && typeof opts.namespace !== "string") {
|
|
9982
|
-
throw
|
|
10664
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "namespace must be a string");
|
|
9983
10665
|
}
|
|
9984
10666
|
const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
|
|
9985
10667
|
if (ttl !== void 0 && (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)) {
|
|
9986
|
-
throw
|
|
10668
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "defaultTTL must be a non-negative number");
|
|
9987
10669
|
}
|
|
9988
10670
|
return opts;
|
|
9989
10671
|
}
|
|
9990
10672
|
function validateFunctionCachePerFnOptions(options) {
|
|
9991
10673
|
if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) {
|
|
9992
|
-
throw
|
|
10674
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "options must be an object");
|
|
9993
10675
|
}
|
|
9994
10676
|
const opts = options ?? {};
|
|
9995
10677
|
if (opts.keyBuilder !== void 0 && typeof opts.keyBuilder !== "function") {
|
|
9996
|
-
throw
|
|
10678
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "keyBuilder must be a function");
|
|
9997
10679
|
}
|
|
9998
10680
|
if (opts.condition !== void 0 && typeof opts.condition !== "function") {
|
|
9999
|
-
throw
|
|
10681
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "condition must be a function");
|
|
10000
10682
|
}
|
|
10001
10683
|
const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
|
|
10002
10684
|
if (ttl !== void 0 && (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)) {
|
|
10003
|
-
throw
|
|
10685
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "defaultTTL must be a non-negative number");
|
|
10004
10686
|
}
|
|
10005
10687
|
return opts;
|
|
10006
10688
|
}
|
|
10007
10689
|
function withCache(fn, options = {}) {
|
|
10008
|
-
if (typeof fn !== "function") throw
|
|
10690
|
+
if (typeof fn !== "function") throw createError(ErrorCodes.INVALID_ARGUMENT, "fn must be a function");
|
|
10009
10691
|
const { ttl = 6e4, namespace, keyBuilder, condition, cache: externalCache, enableStats = true } = options;
|
|
10010
10692
|
if (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)
|
|
10011
|
-
throw
|
|
10693
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "ttl must be a non-negative number");
|
|
10012
10694
|
if (keyBuilder !== void 0 && typeof keyBuilder !== "function")
|
|
10013
|
-
throw
|
|
10695
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "keyBuilder must be a function");
|
|
10014
10696
|
if (condition !== void 0 && typeof condition !== "function")
|
|
10015
|
-
throw
|
|
10697
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "condition must be a function");
|
|
10016
10698
|
if (externalCache !== void 0 && !isValidCache(externalCache))
|
|
10017
|
-
throw
|
|
10699
|
+
throw createError(ErrorCodes.CACHE_UNAVAILABLE, "Invalid cache instance");
|
|
10018
10700
|
const wrapped = (0, import_function_cache.withCache)(fn, {
|
|
10019
10701
|
ttl,
|
|
10020
10702
|
namespace: namespace ?? "fn",
|
|
@@ -10056,9 +10738,9 @@ var FunctionCache = class {
|
|
|
10056
10738
|
}
|
|
10057
10739
|
register(name, fn, options) {
|
|
10058
10740
|
if (!name || typeof name !== "string")
|
|
10059
|
-
throw
|
|
10741
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Function name must be a non-empty string");
|
|
10060
10742
|
if (typeof fn !== "function")
|
|
10061
|
-
throw
|
|
10743
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "fn must be a function");
|
|
10062
10744
|
const opts = validateFunctionCachePerFnOptions(options);
|
|
10063
10745
|
const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
|
|
10064
10746
|
this._inner.register(name, fn, {
|
|
@@ -10084,13 +10766,13 @@ var FunctionCache = class {
|
|
|
10084
10766
|
}
|
|
10085
10767
|
async invalidate(name, ...args) {
|
|
10086
10768
|
if (!name || typeof name !== "string") {
|
|
10087
|
-
throw
|
|
10769
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Function name must be a non-empty string");
|
|
10088
10770
|
}
|
|
10089
10771
|
await this._inner.invalidate(name, ...args);
|
|
10090
10772
|
}
|
|
10091
10773
|
async invalidatePattern(pattern) {
|
|
10092
10774
|
if (!pattern || typeof pattern !== "string")
|
|
10093
|
-
throw
|
|
10775
|
+
throw createError(ErrorCodes.INVALID_ARGUMENT, "Pattern must be a non-empty string");
|
|
10094
10776
|
return this._inner.invalidatePattern(pattern);
|
|
10095
10777
|
}
|
|
10096
10778
|
list() {
|
|
@@ -10125,15 +10807,15 @@ function b64urlDecodeStr(s) {
|
|
|
10125
10807
|
const b64 = pad(String(s || "")).replace(/-/g, "+").replace(/_/g, "/");
|
|
10126
10808
|
return Buffer.from(b64, "base64").toString();
|
|
10127
10809
|
}
|
|
10128
|
-
function makeInvalidCursorError(cause) {
|
|
10129
|
-
const err = new Error(
|
|
10810
|
+
function makeInvalidCursorError(message = "Invalid cursor", cause) {
|
|
10811
|
+
const err = new Error(message);
|
|
10130
10812
|
err.code = "INVALID_CURSOR";
|
|
10131
10813
|
if (cause !== void 0) err.cause = cause;
|
|
10132
10814
|
return err;
|
|
10133
10815
|
}
|
|
10134
10816
|
function encodeCursor2(payload) {
|
|
10135
10817
|
if (!payload.s || !payload.a) {
|
|
10136
|
-
throw
|
|
10818
|
+
throw makeInvalidCursorError("encodeCursor requires sort (s) and anchor (a)");
|
|
10137
10819
|
}
|
|
10138
10820
|
const json = JSON.stringify({ v: payload.v ?? 1, s: payload.s, a: payload.a, d: payload.d });
|
|
10139
10821
|
return b64urlEncodeStr(json);
|
|
@@ -10142,11 +10824,11 @@ function decodeCursor2(str) {
|
|
|
10142
10824
|
try {
|
|
10143
10825
|
const obj = JSON.parse(b64urlDecodeStr(str));
|
|
10144
10826
|
if (!obj || obj["v"] !== 1 || !obj["s"] || !obj["a"]) {
|
|
10145
|
-
throw
|
|
10827
|
+
throw makeInvalidCursorError("bad-structure");
|
|
10146
10828
|
}
|
|
10147
10829
|
return obj;
|
|
10148
10830
|
} catch (e) {
|
|
10149
|
-
throw makeInvalidCursorError(e);
|
|
10831
|
+
throw makeInvalidCursorError("Invalid cursor", e);
|
|
10150
10832
|
}
|
|
10151
10833
|
}
|
|
10152
10834
|
|
|
@@ -10218,7 +10900,11 @@ var MonSQLizeRuntime = class {
|
|
|
10218
10900
|
} : rawCacheInput;
|
|
10219
10901
|
this._cache = normalizeRuntimeCache(cacheInput);
|
|
10220
10902
|
this._logger = Logger.create(options.logger ?? null);
|
|
10221
|
-
this._cacheLockManager = new CacheLockManager({
|
|
10903
|
+
this._cacheLockManager = new CacheLockManager({
|
|
10904
|
+
logger: options.logger ?? null,
|
|
10905
|
+
maxDuration: options.transaction?.lockMaxDuration,
|
|
10906
|
+
cleanupInterval: options.transaction?.lockCleanupInterval
|
|
10907
|
+
});
|
|
10222
10908
|
this._cache.setLockManager?.(this._cacheLockManager);
|
|
10223
10909
|
this._runtimeDefaults = buildRuntimeDefaults(options);
|
|
10224
10910
|
this._adapterCacheOverride = void 0;
|
|
@@ -10338,7 +11024,8 @@ var MonSQLizeRuntime = class {
|
|
|
10338
11024
|
namespace: d.namespace,
|
|
10339
11025
|
log: d.log,
|
|
10340
11026
|
countQueue: this.options.countQueue,
|
|
10341
|
-
models: this.options.models
|
|
11027
|
+
models: this.options.models,
|
|
11028
|
+
autoIndex: this.options.autoIndex
|
|
10342
11029
|
};
|
|
10343
11030
|
}
|
|
10344
11031
|
async close() {
|
|
@@ -10535,6 +11222,10 @@ var MonSQLizeRuntime = class {
|
|
|
10535
11222
|
cache.set(name, instance);
|
|
10536
11223
|
return instance;
|
|
10537
11224
|
}
|
|
11225
|
+
async ensureModelIndexes(options = {}) {
|
|
11226
|
+
this.ensureConnected();
|
|
11227
|
+
return ensureRuntimeModelIndexes(this, options);
|
|
11228
|
+
}
|
|
10538
11229
|
// Capability delegation ----------------------------------------------------
|
|
10539
11230
|
async startSession(options = {}) {
|
|
10540
11231
|
this.ensureConnected();
|
|
@@ -10589,9 +11280,15 @@ var MonSQLizeRuntime = class {
|
|
|
10589
11280
|
listSagas() {
|
|
10590
11281
|
return this.initializeSagaOrchestrator().listSagas();
|
|
10591
11282
|
}
|
|
11283
|
+
getTransactionStats() {
|
|
11284
|
+
return this._transactionManager?.getStats() ?? null;
|
|
11285
|
+
}
|
|
10592
11286
|
getSagaStats() {
|
|
10593
11287
|
return this.initializeSagaOrchestrator().getStats();
|
|
10594
11288
|
}
|
|
11289
|
+
getDistributedCacheInvalidatorStats() {
|
|
11290
|
+
return this._distributedInvalidator?.getStats() ?? null;
|
|
11291
|
+
}
|
|
10595
11292
|
async startSync() {
|
|
10596
11293
|
this.ensureConnected();
|
|
10597
11294
|
const manager = await this.initializeSyncManager();
|
|
@@ -10670,7 +11367,8 @@ var MonSQLizeRuntime = class {
|
|
|
10670
11367
|
client: this._client,
|
|
10671
11368
|
cache: this._cache,
|
|
10672
11369
|
logger: this.options.logger ?? null,
|
|
10673
|
-
lockManager: this._cacheLockManager
|
|
11370
|
+
lockManager: this._cacheLockManager,
|
|
11371
|
+
transaction: this.options.transaction
|
|
10674
11372
|
});
|
|
10675
11373
|
return this._transactionManager;
|
|
10676
11374
|
}
|