monsqlize 2.0.2 → 2.0.3

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.
@@ -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 \u5FC5\u987B\u662F Redis URL \u5B57\u7B26\u4E32\u6216 ioredis \u5B9E\u4F8B";
149
- var LEGACY_IOREDIS_MISSING_ERROR = "ioredis \u672A\u5B89\u88C5\u3002\u8BF7\u8FD0\u884C: npm install ioredis\n\u6216\u4F20\u5165\u5DF2\u521B\u5EFA\u7684 ioredis \u5B9E\u4F8B";
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 new Error("DistributedCacheInvalidator requires a cache instance");
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 new Error("DistributedCacheInvalidator requires either redis or redisUrl");
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(path2, options) {
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(path2)) {
391
- return new _PopulatePromise(this.executor, [...this.paths, ...path2.map(toConfig)]);
390
+ if (Array.isArray(path3)) {
391
+ return new _PopulatePromise(this.executor, [...this.paths, ...path3.map(toConfig)]);
392
392
  }
393
- const config = toConfig(path2);
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(path2) {
508
- return typeof path2 === "string" ? { path: path2 } : path2;
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, path2) {
660
- return path2.split(".").reduce((current, key) => {
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, path2) {
716
- const config = normalizePopulateConfig(path2);
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: (path2) => {
834
- const paths = Array.isArray(path2) ? path2 : [path2];
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
@@ -2066,18 +2066,18 @@ var ModelInstance = class {
2066
2066
  }
2067
2067
  async populateDocuments(docs, paths) {
2068
2068
  let current = docs;
2069
- for (const path2 of paths) {
2070
- current = await this.populatePath(current, path2);
2069
+ for (const path3 of paths) {
2070
+ current = await this.populatePath(current, path3);
2071
2071
  }
2072
2072
  return current;
2073
2073
  }
2074
- async populatePath(docs, path2) {
2074
+ async populatePath(docs, path3) {
2075
2075
  return populateModelPath({
2076
2076
  relations: this.relations,
2077
2077
  runtime: this.runtime,
2078
2078
  dbName: this.dbName,
2079
2079
  poolName: this.poolName
2080
- }, docs, path2);
2080
+ }, docs, path3);
2081
2081
  }
2082
2082
  hydrateDocuments(docs) {
2083
2083
  return docs.filter(Boolean).map((doc) => this.hydrateDocument(doc));
@@ -2250,9 +2250,9 @@ var Transaction = class {
2250
2250
  */
2251
2251
  async start() {
2252
2252
  if (this.state !== "pending") {
2253
- throw new Error(`Cannot start transaction in state: ${this.state}`);
2253
+ throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
2254
2254
  }
2255
- this.session.startTransaction();
2255
+ this.session.startTransaction(this.options.transactionOptions);
2256
2256
  this.state = "active";
2257
2257
  this.startedAt = Date.now();
2258
2258
  const timeout = this.options.timeout ?? 3e4;
@@ -2272,7 +2272,7 @@ var Transaction = class {
2272
2272
  */
2273
2273
  async commit() {
2274
2274
  if (this.state !== "active") {
2275
- throw new Error(`Cannot commit transaction in state: ${this.state}`);
2275
+ throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
2276
2276
  }
2277
2277
  if (typeof this.session.commitTransaction === "function") {
2278
2278
  await this.session.commitTransaction();
@@ -2370,7 +2370,9 @@ var TransactionManager = class {
2370
2370
  this.stats = {
2371
2371
  totalTransactions: 0,
2372
2372
  successfulTransactions: 0,
2373
- failedTransactions: 0
2373
+ failedTransactions: 0,
2374
+ readOnlyTransactions: 0,
2375
+ writeTransactions: 0
2374
2376
  };
2375
2377
  const options = "client" in input ? input : {
2376
2378
  client: input,
@@ -2381,6 +2383,10 @@ var TransactionManager = class {
2381
2383
  this.cache = options.cache ?? null;
2382
2384
  this.logger = options.logger ?? null;
2383
2385
  this.lockManager = options.lockManager ?? null;
2386
+ this.defaultReadConcern = options.defaultReadConcern;
2387
+ this.defaultWriteConcern = options.defaultWriteConcern;
2388
+ this.defaultReadPreference = options.defaultReadPreference;
2389
+ this.maxStatsSamples = options.maxStatsSamples ?? 1e3;
2384
2390
  this.defaultOptions = {
2385
2391
  maxDuration: options.maxDuration ?? 3e4,
2386
2392
  enableRetry: options.enableRetry ?? true,
@@ -2397,11 +2403,17 @@ var TransactionManager = class {
2397
2403
  const session = this.client.startSession({
2398
2404
  causalConsistency: options.causalConsistency !== false
2399
2405
  });
2406
+ const transactionOptions = {
2407
+ readConcern: options.readConcern ?? this.defaultReadConcern,
2408
+ writeConcern: options.writeConcern ?? this.defaultWriteConcern,
2409
+ readPreference: options.readPreference ?? this.defaultReadPreference
2410
+ };
2400
2411
  const transaction = new Transaction(session, {
2401
2412
  cache: this.cache,
2402
2413
  logger: this.logger,
2403
2414
  lockManager: options.enableCacheLock === false ? null : this.lockManager,
2404
- timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration
2415
+ timeout: options.timeout ?? options.maxDuration ?? this.defaultOptions.maxDuration,
2416
+ transactionOptions: compactUndefined(transactionOptions)
2405
2417
  });
2406
2418
  const originalEnd = transaction.end.bind(transaction);
2407
2419
  transaction.end = async () => {
@@ -2428,12 +2440,12 @@ var TransactionManager = class {
2428
2440
  await transaction.start();
2429
2441
  const result = await callback(transaction);
2430
2442
  await transaction.commit();
2431
- this.recordStats(Date.now() - startedAt, true);
2443
+ this.recordStats(transaction, Date.now() - startedAt, true);
2432
2444
  return result;
2433
2445
  } catch (error) {
2434
2446
  lastError = error;
2435
2447
  await transaction.abort();
2436
- this.recordStats(Date.now() - startedAt, false);
2448
+ this.recordStats(transaction, Date.now() - startedAt, false);
2437
2449
  if (!enableRetry || attempt === maxRetries || !isTransientTransactionError(error)) {
2438
2450
  throw error;
2439
2451
  }
@@ -2469,27 +2481,54 @@ var TransactionManager = class {
2469
2481
  */
2470
2482
  getStats() {
2471
2483
  const averageDuration = this.durations.length === 0 ? 0 : this.durations.reduce((sum, item) => sum + item, 0) / this.durations.length;
2484
+ const sortedDurations = [...this.durations].sort((a, b) => a - b);
2485
+ const p95Duration = percentile(sortedDurations, 0.95);
2486
+ const p99Duration = percentile(sortedDurations, 0.99);
2487
+ const totalTransactions = this.stats.totalTransactions;
2472
2488
  return {
2473
- totalTransactions: this.stats.totalTransactions,
2489
+ totalTransactions,
2474
2490
  successfulTransactions: this.stats.successfulTransactions,
2475
2491
  failedTransactions: this.stats.failedTransactions,
2492
+ readOnlyTransactions: this.stats.readOnlyTransactions,
2493
+ writeTransactions: this.stats.writeTransactions,
2476
2494
  activeTransactions: this.activeTransactions.size,
2477
- averageDuration
2495
+ averageDuration,
2496
+ p95Duration,
2497
+ p99Duration,
2498
+ successRate: totalTransactions > 0 ? `${(this.stats.successfulTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
2499
+ readOnlyRatio: totalTransactions > 0 ? `${(this.stats.readOnlyTransactions / totalTransactions * 100).toFixed(2)}%` : "0%",
2500
+ sampleCount: this.durations.length
2478
2501
  };
2479
2502
  }
2480
- recordStats(duration, success) {
2503
+ recordStats(transaction, duration, success) {
2481
2504
  this.stats.totalTransactions += 1;
2482
2505
  if (success) {
2483
2506
  this.stats.successfulTransactions += 1;
2484
2507
  } else {
2485
2508
  this.stats.failedTransactions += 1;
2486
2509
  }
2510
+ if (transaction.pendingInvalidations.size > 0) {
2511
+ this.stats.writeTransactions += 1;
2512
+ } else {
2513
+ this.stats.readOnlyTransactions += 1;
2514
+ }
2487
2515
  this.durations.push(duration);
2488
- if (this.durations.length > 100) {
2516
+ if (this.durations.length > this.maxStatsSamples) {
2489
2517
  this.durations.shift();
2490
2518
  }
2491
2519
  }
2492
2520
  };
2521
+ function percentile(sortedValues, ratio) {
2522
+ if (sortedValues.length === 0) {
2523
+ return 0;
2524
+ }
2525
+ const index = Math.floor(sortedValues.length * ratio);
2526
+ return sortedValues[Math.min(index, sortedValues.length - 1)] ?? 0;
2527
+ }
2528
+ function compactUndefined(value) {
2529
+ const entries = Object.entries(value).filter(([, item]) => item !== void 0);
2530
+ return entries.length === 0 ? void 0 : Object.fromEntries(entries);
2531
+ }
2493
2532
  function stringifySessionId(id) {
2494
2533
  if (typeof id === "string") {
2495
2534
  return id;
@@ -2526,18 +2565,166 @@ async function sleep(ms) {
2526
2565
  }
2527
2566
 
2528
2567
  // src/adapters/mongodb/common/connect.ts
2568
+ var import_node_fs = require("node:fs");
2569
+ var import_node_path = __toESM(require("node:path"));
2529
2570
  var import_mongodb = require("mongodb");
2571
+ var DEFAULT_MEMORY_SERVER_VERSION = "7.0.14";
2572
+ var MANAGED_DB_PATH_PREFIXES = ["single-", "replset-", "examples-single-", "examples-replset-", "probe-single-", "probe-replset-"];
2530
2573
  var _memoryServerInstance = null;
2574
+ var _memoryServerCleanupOptions = { doCleanup: true, force: true };
2575
+ var _memoryServerDbPath = null;
2576
+ var _memoryServerClients = /* @__PURE__ */ new Set();
2577
+ function setDefaultEnv(name, value) {
2578
+ if (!process.env[name]) {
2579
+ process.env[name] = value;
2580
+ }
2581
+ }
2582
+ function sanitizePathSegment(input) {
2583
+ return input.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "default";
2584
+ }
2585
+ function parseManagedPathPid(name) {
2586
+ if (!MANAGED_DB_PATH_PREFIXES.some((prefix) => name.startsWith(prefix))) {
2587
+ return null;
2588
+ }
2589
+ const match = /-(\d+)-[^-]+$/.exec(name);
2590
+ if (!match) {
2591
+ return null;
2592
+ }
2593
+ const pid = Number.parseInt(match[1], 10);
2594
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
2595
+ }
2596
+ function isProcessAlive(pid) {
2597
+ if (pid === process.pid) {
2598
+ return true;
2599
+ }
2600
+ try {
2601
+ process.kill(pid, 0);
2602
+ return true;
2603
+ } catch (error) {
2604
+ return error.code === "EPERM";
2605
+ }
2606
+ }
2607
+ function pruneManagedDbRoot(dbRoot) {
2608
+ let entries;
2609
+ try {
2610
+ entries = (0, import_node_fs.readdirSync)(dbRoot, { withFileTypes: true });
2611
+ } catch {
2612
+ return;
2613
+ }
2614
+ for (const entry of entries) {
2615
+ if (!entry.isDirectory()) {
2616
+ continue;
2617
+ }
2618
+ const pid = parseManagedPathPid(entry.name);
2619
+ if (!pid || isProcessAlive(pid)) {
2620
+ continue;
2621
+ }
2622
+ try {
2623
+ (0, import_node_fs.rmSync)(import_node_path.default.join(dbRoot, entry.name), { recursive: true, force: true });
2624
+ } catch {
2625
+ }
2626
+ }
2627
+ }
2628
+ function resolveMemoryServerBinaryVersion(memoryServerOptions = {}) {
2629
+ 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;
2630
+ }
2631
+ function resolveMemoryServerPolicy(binaryVersion) {
2632
+ 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"));
2633
+ const downloadDir = import_node_path.default.resolve(process.env.MONGOMS_DOWNLOAD_DIR || import_node_path.default.join(cacheRoot, "binaries"));
2634
+ const dbRoot = import_node_path.default.resolve(process.env.MONSQLIZE_MEMORY_SERVER_DB_DIR || import_node_path.default.join(cacheRoot, "db"));
2635
+ (0, import_node_fs.mkdirSync)(downloadDir, { recursive: true });
2636
+ (0, import_node_fs.mkdirSync)(dbRoot, { recursive: true });
2637
+ pruneManagedDbRoot(dbRoot);
2638
+ setDefaultEnv("MONGOMS_DOWNLOAD_DIR", downloadDir);
2639
+ setDefaultEnv("MONGOMS_PREFER_GLOBAL_PATH", "false");
2640
+ setDefaultEnv("MONGOMS_RUNTIME_DOWNLOAD", "true");
2641
+ setDefaultEnv("MONGOMS_VERSION", binaryVersion);
2642
+ return { downloadDir, dbRoot };
2643
+ }
2644
+ function createManagedDbPath(dbRoot, dbName) {
2645
+ return (0, import_node_fs.mkdtempSync)(import_node_path.default.join(dbRoot, `replset-${sanitizePathSegment(dbName)}-${process.pid}-`));
2646
+ }
2647
+ function isManagedCleanupError(error, dbPath) {
2648
+ if (!error || typeof error !== "object") {
2649
+ return false;
2650
+ }
2651
+ const candidate = error;
2652
+ if (!candidate.code || !["ENOTEMPTY", "EBUSY", "EPERM", "ENOENT"].includes(candidate.code)) {
2653
+ return false;
2654
+ }
2655
+ return !candidate.path || import_node_path.default.resolve(candidate.path).startsWith(import_node_path.default.resolve(dbPath));
2656
+ }
2657
+ function delay(ms) {
2658
+ return new Promise((resolve) => setTimeout(resolve, ms));
2659
+ }
2660
+ async function cleanupManagedDbPath(dbPath) {
2661
+ if (!dbPath) {
2662
+ return true;
2663
+ }
2664
+ for (const waitMs of [0, 50, 100, 200, 400, 800]) {
2665
+ if (waitMs > 0) {
2666
+ await delay(waitMs);
2667
+ }
2668
+ try {
2669
+ (0, import_node_fs.rmSync)(dbPath, { recursive: true, force: true });
2670
+ if (!(0, import_node_fs.existsSync)(dbPath)) {
2671
+ return true;
2672
+ }
2673
+ } catch {
2674
+ }
2675
+ }
2676
+ return !(0, import_node_fs.existsSync)(dbPath);
2677
+ }
2678
+ function resolveLaunchTimeout() {
2679
+ const raw = process.env.MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS;
2680
+ if (!raw) {
2681
+ return void 0;
2682
+ }
2683
+ const value = Number.parseInt(raw, 10);
2684
+ return Number.isFinite(value) && value > 0 ? value : void 0;
2685
+ }
2686
+ async function seedMemoryServerBinaryCache(binaryVersion, downloadDir) {
2687
+ try {
2688
+ const { DryMongoBinary } = require("mongodb-memory-server-core/lib/util/DryMongoBinary");
2689
+ const options = await DryMongoBinary.generateOptions({ version: binaryVersion, downloadDir });
2690
+ options.downloadDir = downloadDir;
2691
+ const paths = await DryMongoBinary.generatePaths(options);
2692
+ 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)) {
2693
+ (0, import_node_fs.mkdirSync)(import_node_path.default.dirname(paths.resolveConfig), { recursive: true });
2694
+ (0, import_node_fs.copyFileSync)(paths.homeCache, paths.resolveConfig);
2695
+ }
2696
+ } catch {
2697
+ }
2698
+ }
2531
2699
  async function startMemoryServer(logger, memoryServerOptions = {}) {
2532
2700
  if (_memoryServerInstance) {
2533
2701
  return _memoryServerInstance.getUri();
2534
2702
  }
2703
+ const binaryVersion = resolveMemoryServerBinaryVersion(memoryServerOptions);
2704
+ const { dbRoot, downloadDir } = resolveMemoryServerPolicy(binaryVersion);
2705
+ await seedMemoryServerBinaryCache(binaryVersion, downloadDir);
2535
2706
  const { MongoMemoryReplSet } = require("mongodb-memory-server");
2536
- logger?.info?.("\u{1F680} Starting MongoDB Memory ReplSet (transactions supported)...");
2707
+ logger?.info?.("Starting MongoDB Memory ReplSet", { binaryVersion });
2708
+ const dbName = memoryServerOptions?.instance?.dbName || "monsqlize_memory";
2709
+ const instanceConfig = { ...memoryServerOptions?.instance ?? {} };
2710
+ const hasUserDbPath = typeof instanceConfig.dbPath === "string" && instanceConfig.dbPath.length > 0;
2711
+ if (!hasUserDbPath) {
2712
+ _memoryServerDbPath = createManagedDbPath(dbRoot, dbName);
2713
+ instanceConfig.dbPath = _memoryServerDbPath;
2714
+ } else {
2715
+ _memoryServerDbPath = null;
2716
+ }
2717
+ if (instanceConfig.launchTimeout === void 0) {
2718
+ const launchTimeout = resolveLaunchTimeout();
2719
+ if (launchTimeout) {
2720
+ instanceConfig.launchTimeout = launchTimeout;
2721
+ }
2722
+ }
2723
+ _memoryServerCleanupOptions = { doCleanup: true, force: !hasUserDbPath };
2537
2724
  const defaultConfig = {
2538
2725
  replSet: { count: 1, storageEngine: "wiredTiger" },
2539
- binary: { version: "6.0.12" },
2540
- instanceOpts: [{ ...memoryServerOptions?.instance ?? {} }]
2726
+ binary: { version: binaryVersion },
2727
+ instanceOpts: [instanceConfig]
2541
2728
  };
2542
2729
  const resolvedConfig = {
2543
2730
  ...defaultConfig,
@@ -2546,11 +2733,43 @@ async function startMemoryServer(logger, memoryServerOptions = {}) {
2546
2733
  try {
2547
2734
  _memoryServerInstance = await MongoMemoryReplSet.create(resolvedConfig);
2548
2735
  const uri = _memoryServerInstance.getUri();
2549
- logger?.info?.("\u2705 MongoDB Memory ReplSet started", { uri });
2736
+ logger?.info?.("MongoDB Memory ReplSet started", { uri });
2550
2737
  return uri;
2551
2738
  } catch (err) {
2552
- logger?.error?.("\u274C Failed to start MongoDB Memory ReplSet", err);
2553
- throw new Error(`Failed to start MongoDB Memory ReplSet: ${err.message}`);
2739
+ if (!hasUserDbPath) {
2740
+ await cleanupManagedDbPath(_memoryServerDbPath);
2741
+ _memoryServerDbPath = null;
2742
+ }
2743
+ logger?.error?.("Failed to start MongoDB Memory ReplSet", err);
2744
+ throw createConnectionError(
2745
+ `Failed to start MongoDB Memory ReplSet: ${err.message}`,
2746
+ err instanceof Error ? err : void 0
2747
+ );
2748
+ }
2749
+ }
2750
+ async function stopMemoryServer(logger) {
2751
+ if (!_memoryServerInstance) {
2752
+ return;
2753
+ }
2754
+ const instance = _memoryServerInstance;
2755
+ const dbPath = _memoryServerDbPath;
2756
+ _memoryServerInstance = null;
2757
+ _memoryServerDbPath = null;
2758
+ let stopError = null;
2759
+ try {
2760
+ await instance.stop(_memoryServerCleanupOptions);
2761
+ logger?.info?.("MongoDB Memory ReplSet stopped");
2762
+ } catch (cause) {
2763
+ stopError = cause;
2764
+ logger?.warn?.("Failed to stop MongoDB Memory ReplSet cleanly.", cause);
2765
+ } finally {
2766
+ if (_memoryServerCleanupOptions.force) {
2767
+ const cleaned = await cleanupManagedDbPath(dbPath);
2768
+ if (!cleaned && (!stopError || isManagedCleanupError(stopError, dbPath ?? ""))) {
2769
+ logger?.warn?.("Failed to remove MongoDB Memory ReplSet dbPath.", { dbPath });
2770
+ }
2771
+ }
2772
+ _memoryServerCleanupOptions = { doCleanup: true, force: true };
2554
2773
  }
2555
2774
  }
2556
2775
  async function connectMongo(params) {
@@ -2559,13 +2778,15 @@ async function connectMongo(params) {
2559
2778
  throw createError(ErrorCodes.INVALID_DATABASE_NAME, "Database name must be a non-empty string.");
2560
2779
  }
2561
2780
  let effectiveUri = params.config?.uri?.trim();
2781
+ let usesManagedMemoryServer = false;
2562
2782
  if (!effectiveUri && params.config?.useMemoryServer === true) {
2563
2783
  if (process.env["MONSQLIZE_USE_SYSTEM_MONGO"] === "true") {
2564
2784
  const systemUri = process.env["MONSQLIZE_SYSTEM_MONGO_URI"] ?? "mongodb://127.0.0.1:27017";
2565
- params.logger?.info?.("\u{1F527} Using system MongoDB instead of memory server", { uri: systemUri });
2785
+ params.logger?.info?.("Using system MongoDB instead of memory server", { uri: systemUri });
2566
2786
  effectiveUri = systemUri;
2567
2787
  } else {
2568
2788
  effectiveUri = await startMemoryServer(params.logger, params.config.memoryServerOptions);
2789
+ usesManagedMemoryServer = true;
2569
2790
  }
2570
2791
  }
2571
2792
  if (!effectiveUri) {
@@ -2579,6 +2800,9 @@ async function connectMongo(params) {
2579
2800
  try {
2580
2801
  await client.connect();
2581
2802
  const db = client.db(databaseName);
2803
+ if (usesManagedMemoryServer) {
2804
+ _memoryServerClients.add(client);
2805
+ }
2582
2806
  params.logger?.info?.("MongoDB connected", { databaseName });
2583
2807
  return { client, db };
2584
2808
  } catch (cause) {
@@ -2586,6 +2810,9 @@ async function connectMongo(params) {
2586
2810
  await client.close();
2587
2811
  } catch {
2588
2812
  }
2813
+ if (usesManagedMemoryServer && _memoryServerClients.size === 0) {
2814
+ await stopMemoryServer(params.logger);
2815
+ }
2589
2816
  throw createConnectionError(
2590
2817
  `Failed to connect to MongoDB database: ${databaseName}`,
2591
2818
  cause instanceof Error ? cause : void 0
@@ -2596,17 +2823,26 @@ async function closeMongo(client, logger) {
2596
2823
  if (!client) {
2597
2824
  return;
2598
2825
  }
2826
+ const shouldReleaseMemoryServer = _memoryServerClients.delete(client);
2827
+ let closeError = null;
2599
2828
  try {
2600
2829
  await client.close();
2601
2830
  logger?.info?.("MongoDB connection closed");
2602
2831
  } catch (cause) {
2603
- const error = createError(
2832
+ closeError = createError(
2604
2833
  ErrorCodes.CONNECTION_CLOSED,
2605
2834
  "Failed to close MongoDB connection cleanly.",
2606
2835
  void 0,
2607
2836
  cause instanceof Error ? cause : void 0
2608
2837
  );
2609
- logger?.warn?.(error.message, error.cause);
2838
+ logger?.warn?.(closeError.message, closeError.cause);
2839
+ } finally {
2840
+ if (shouldReleaseMemoryServer && _memoryServerClients.size === 0) {
2841
+ await stopMemoryServer(logger);
2842
+ }
2843
+ }
2844
+ if (closeError) {
2845
+ return;
2610
2846
  }
2611
2847
  }
2612
2848
 
@@ -2618,8 +2854,9 @@ function loadSsh2Client() {
2618
2854
  try {
2619
2855
  return require("ssh2").Client;
2620
2856
  } catch {
2621
- throw new Error(
2622
- 'ssh2 is not installed. SSH tunnel support requires the optional "ssh2" package.\nRun: npm install ssh2'
2857
+ throw createError(
2858
+ ErrorCodes.INVALID_CONFIG,
2859
+ "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
2860
  );
2624
2861
  }
2625
2862
  }
@@ -2647,10 +2884,10 @@ var SSHTunnelSSH2 = class {
2647
2884
  keepaliveInterval = 3e4
2648
2885
  } = this._sshConfig;
2649
2886
  if (!host || !username) {
2650
- throw new Error("SSH config requires: host, username");
2887
+ throw createError(ErrorCodes.INVALID_CONFIG, "SSH config requires: host, username");
2651
2888
  }
2652
2889
  if (!password && !privateKey && !privateKeyPath) {
2653
- throw new Error("SSH authentication required: provide password, privateKey, or privateKeyPath");
2890
+ throw createError(ErrorCodes.INVALID_CONFIG, "SSH authentication required: provide password, privateKey, or privateKeyPath");
2654
2891
  }
2655
2892
  const config = { host, port, username, readyTimeout, keepaliveInterval };
2656
2893
  if (password) {
@@ -2725,7 +2962,7 @@ var SSHTunnelSSH2 = class {
2725
2962
  }
2726
2963
  getTunnelUri(_protocol, originalUri) {
2727
2964
  if (!this.isConnected || this.localPort === null) {
2728
- throw new Error(`SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
2965
+ throw createError(ErrorCodes.NOT_CONNECTED, `SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
2729
2966
  }
2730
2967
  return originalUri.replace(
2731
2968
  `${this.remoteHost}:${this.remotePort}`,
@@ -2734,7 +2971,7 @@ var SSHTunnelSSH2 = class {
2734
2971
  }
2735
2972
  getLocalAddress() {
2736
2973
  if (!this.isConnected || this.localPort === null) {
2737
- throw new Error(`SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
2974
+ throw createError(ErrorCodes.NOT_CONNECTED, `SSH tunnel [${this.remoteHost}:${this.remotePort}] not connected`);
2738
2975
  }
2739
2976
  return `localhost:${this.localPort}`;
2740
2977
  }
@@ -3183,7 +3420,7 @@ var HealthChecker = class {
3183
3420
  async _pingPool(poolName, timeout) {
3184
3421
  const stored = this._clients.get(poolName);
3185
3422
  const client = stored ?? this._poolManager?._getPool(poolName);
3186
- if (!client) throw new Error(`No client for pool: ${poolName}`);
3423
+ if (!client) throw createError(ErrorCodes.POOL_NOT_FOUND, `No client for pool: ${poolName}`);
3187
3424
  const db = client.db("admin");
3188
3425
  const pingFn = db.command ? () => db.command({ ping: 1 }) : () => db.admin().ping();
3189
3426
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Ping timeout")), timeout));
@@ -3215,7 +3452,7 @@ var PoolSelector = class {
3215
3452
  }
3216
3453
  select(pools, context) {
3217
3454
  if (!pools || pools.length === 0) {
3218
- throw new Error("No available pools");
3455
+ throw createError(ErrorCodes.INVALID_OPERATION, "No available pools");
3219
3456
  }
3220
3457
  switch (this._strategy) {
3221
3458
  case "auto":
@@ -3236,7 +3473,10 @@ var PoolSelector = class {
3236
3473
  selectByAuto(pools, context) {
3237
3474
  const { operation, poolPreference } = context;
3238
3475
  let candidates = pools;
3239
- if (operation === "read") {
3476
+ const preferred = this.filterByPreference(pools, poolPreference);
3477
+ if (preferred.length > 0) {
3478
+ candidates = preferred;
3479
+ } else if (operation === "read") {
3240
3480
  const secondaries = pools.filter((pool) => pool.role === "secondary");
3241
3481
  if (secondaries.length > 0) {
3242
3482
  candidates = secondaries;
@@ -3247,10 +3487,19 @@ var PoolSelector = class {
3247
3487
  candidates = primaries;
3248
3488
  }
3249
3489
  }
3490
+ if (candidates.length === 1) {
3491
+ return candidates[0].name;
3492
+ }
3493
+ return this.selectByWeighted(candidates);
3494
+ }
3495
+ filterByPreference(pools, poolPreference) {
3496
+ let candidates = pools;
3497
+ let applied = false;
3250
3498
  if (poolPreference?.role) {
3251
3499
  const filteredByRole = candidates.filter((pool) => pool.role === poolPreference.role);
3252
3500
  if (filteredByRole.length > 0) {
3253
3501
  candidates = filteredByRole;
3502
+ applied = true;
3254
3503
  }
3255
3504
  }
3256
3505
  if (poolPreference?.tags?.length) {
@@ -3263,12 +3512,10 @@ var PoolSelector = class {
3263
3512
  });
3264
3513
  if (filteredByTags.length > 0) {
3265
3514
  candidates = filteredByTags;
3515
+ applied = true;
3266
3516
  }
3267
3517
  }
3268
- if (candidates.length === 1) {
3269
- return candidates[0].name;
3270
- }
3271
- return this.selectByWeighted(candidates);
3518
+ return applied ? candidates : [];
3272
3519
  }
3273
3520
  selectByRoundRobin(pools, context) {
3274
3521
  let candidates = pools;
@@ -3428,43 +3675,43 @@ var DEFAULT_POOL_CONNECT_OPTIONS = {
3428
3675
  serverSelectionTimeoutMS: 5e3
3429
3676
  };
3430
3677
  function validatePoolConfig(config) {
3431
- if (!config || typeof config !== "object") throw new Error("Pool config must be an object");
3432
- if (!config.name || typeof config.name !== "string") throw new Error("Pool config.name is required and must be a string");
3433
- if (!config.uri || typeof config.uri !== "string") throw new Error("Pool config.uri is required and must be a string");
3678
+ if (!config || typeof config !== "object") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config must be an object");
3679
+ if (!config.name || typeof config.name !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.name is required and must be a string");
3680
+ if (!config.uri || typeof config.uri !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.uri is required and must be a string");
3434
3681
  if (!config.uri.startsWith("mongodb://") && !config.uri.startsWith("mongodb+srv://")) {
3435
- throw new Error("Pool config.uri must start with mongodb:// or mongodb+srv://");
3682
+ throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.uri must start with mongodb:// or mongodb+srv://");
3436
3683
  }
3437
3684
  if (config.role) {
3438
3685
  const validRoles = ["primary", "secondary", "analytics", "custom"];
3439
- if (!validRoles.includes(config.role)) throw new Error(`Pool config.role must be one of: ${validRoles.join(", ")}`);
3686
+ if (!validRoles.includes(config.role)) throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.role must be one of: ${validRoles.join(", ")}`);
3440
3687
  }
3441
3688
  if (config.weight !== void 0) {
3442
- if (typeof config.weight !== "number") throw new Error("Pool config.weight must be a number");
3443
- if (config.weight < 0) throw new Error("Pool config.weight must be a non-negative number");
3689
+ if (typeof config.weight !== "number") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.weight must be a number");
3690
+ if (config.weight < 0) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.weight must be a non-negative number");
3444
3691
  }
3445
3692
  if (config.options !== void 0) {
3446
- if (typeof config.options !== "object" || Array.isArray(config.options)) throw new Error("Pool config.options must be an object");
3693
+ if (typeof config.options !== "object" || Array.isArray(config.options)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.options must be an object");
3447
3694
  const opts = config.options;
3448
3695
  for (const key of ["maxPoolSize", "minPoolSize", "maxIdleTimeMS", "waitQueueTimeoutMS", "connectTimeoutMS", "serverSelectionTimeoutMS"]) {
3449
3696
  if (opts[key] !== void 0 && (typeof opts[key] !== "number" || opts[key] < 0)) {
3450
- throw new Error(`Pool config.options.${key} must be a non-negative number`);
3697
+ throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.options.${key} must be a non-negative number`);
3451
3698
  }
3452
3699
  }
3453
3700
  }
3454
3701
  if (config.healthCheck !== void 0) {
3455
- if (typeof config.healthCheck !== "object" || Array.isArray(config.healthCheck)) throw new Error("Pool config.healthCheck must be an object");
3702
+ if (typeof config.healthCheck !== "object" || Array.isArray(config.healthCheck)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.healthCheck must be an object");
3456
3703
  const hc = config.healthCheck;
3457
- if (hc.enabled !== void 0 && typeof hc.enabled !== "boolean") throw new Error("Pool config.healthCheck.enabled must be a boolean");
3704
+ if (hc.enabled !== void 0 && typeof hc.enabled !== "boolean") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.healthCheck.enabled must be a boolean");
3458
3705
  for (const key of ["interval", "timeout", "retries"]) {
3459
3706
  if (hc[key] !== void 0 && (typeof hc[key] !== "number" || hc[key] < 0)) {
3460
- throw new Error(`Pool config.healthCheck.${key} must be a non-negative number`);
3707
+ throw createError(ErrorCodes.INVALID_CONFIG, `Pool config.healthCheck.${key} must be a non-negative number`);
3461
3708
  }
3462
3709
  }
3463
3710
  }
3464
3711
  if (config.tags !== void 0) {
3465
- if (!Array.isArray(config.tags)) throw new Error("Pool config.tags must be an array");
3712
+ if (!Array.isArray(config.tags)) throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.tags must be an array");
3466
3713
  for (const tag of config.tags) {
3467
- if (typeof tag !== "string") throw new Error("Pool config.tags must be an array of strings");
3714
+ if (typeof tag !== "string") throw createError(ErrorCodes.INVALID_CONFIG, "Pool config.tags must be an array of strings");
3468
3715
  }
3469
3716
  }
3470
3717
  }
@@ -3523,10 +3770,10 @@ function validatePoolConfigSafe(config) {
3523
3770
  }
3524
3771
  function validatePoolConfigInternal(config) {
3525
3772
  if (!config.name?.trim()) {
3526
- throw new Error("Pool config requires a non-empty name");
3773
+ throw createError(ErrorCodes.INVALID_CONFIG, "Pool config requires a non-empty name");
3527
3774
  }
3528
3775
  if (!config.uri?.trim()) {
3529
- throw new Error("Pool config requires a non-empty uri");
3776
+ throw createError(ErrorCodes.INVALID_CONFIG, "Pool config requires a non-empty uri");
3530
3777
  }
3531
3778
  }
3532
3779
  function createEmptyPoolStats(name) {
@@ -3603,10 +3850,10 @@ var ConnectionPoolManager = class {
3603
3850
  async addPool(config) {
3604
3851
  validatePoolConfigInternal(config);
3605
3852
  if (this.pools.has(config.name) || this._pendingAdds.has(config.name)) {
3606
- throw new Error(`Pool '${config.name}' already exists`);
3853
+ throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
3607
3854
  }
3608
3855
  if (this.maxPoolsCount > 0 && this.pools.size >= this.maxPoolsCount) {
3609
- throw new Error(`Maximum pool count (${this.maxPoolsCount}) reached`);
3856
+ throw createError(ErrorCodes.INVALID_CONFIG, `Maximum pool count (${this.maxPoolsCount}) reached`);
3610
3857
  }
3611
3858
  this._pendingAdds.add(config.name);
3612
3859
  try {
@@ -3614,7 +3861,7 @@ var ConnectionPoolManager = class {
3614
3861
  if (this.pools.has(config.name)) {
3615
3862
  await client.close().catch(() => {
3616
3863
  });
3617
- throw new Error(`Pool '${config.name}' already exists`);
3864
+ throw createError(ErrorCodes.INVALID_CONFIG, `Pool '${config.name}' already exists`);
3618
3865
  }
3619
3866
  this.pools.set(config.name, {
3620
3867
  client,
@@ -3653,7 +3900,7 @@ var ConnectionPoolManager = class {
3653
3900
  async removePool(name) {
3654
3901
  const pool = this.pools.get(name);
3655
3902
  if (!pool) {
3656
- throw new Error(`Pool '${name}' not found`);
3903
+ throw createError(ErrorCodes.POOL_NOT_FOUND, `Pool '${name}' not found`);
3657
3904
  }
3658
3905
  this.stopHealthCheck(name);
3659
3906
  await pool.client.close();
@@ -3677,27 +3924,28 @@ var ConnectionPoolManager = class {
3677
3924
  selectPool(operation, options = {}) {
3678
3925
  if (options.pool) {
3679
3926
  const poolData2 = this.pools.get(options.pool);
3680
- if (!poolData2) throw new Error(`Pool '${options.pool}' not found`);
3927
+ if (!poolData2) throw createError(ErrorCodes.POOL_NOT_FOUND, `Pool '${options.pool}' not found`);
3681
3928
  return this._createPoolResult(options.pool, poolData2.client);
3682
3929
  }
3683
3930
  let candidates = this._getHealthyPools();
3684
3931
  if (candidates.length === 0) {
3685
3932
  if (!this.fallback.enabled) {
3686
- throw new Error("No available connection pool");
3933
+ throw createError(ErrorCodes.INVALID_OPERATION, "No available connection pool");
3687
3934
  }
3688
3935
  candidates = this._handleAllPoolsDown(operation);
3689
3936
  if (candidates.length === 0) {
3690
- throw new Error("No available connection pool");
3937
+ throw createError(ErrorCodes.INVALID_OPERATION, "No available connection pool");
3691
3938
  }
3692
3939
  }
3940
+ const poolPreference = options.poolPreference ?? (options.tags?.length ? { tags: options.tags } : void 0);
3693
3941
  const poolName = this._selector.select(candidates, {
3694
3942
  operation,
3695
3943
  stats: this._stats.getAllStats(),
3696
- poolPreference: options.poolPreference
3944
+ poolPreference
3697
3945
  });
3698
3946
  const poolData = this.pools.get(poolName);
3699
3947
  if (!poolData) {
3700
- throw new Error(`Selected pool '${poolName}' not available`);
3948
+ throw createError(ErrorCodes.INVALID_OPERATION, `Selected pool '${poolName}' not available`);
3701
3949
  }
3702
3950
  this._stats.recordSelection(poolName, operation);
3703
3951
  this.recordSelection(poolName, true);
@@ -3820,10 +4068,12 @@ var ConnectionPoolManager = class {
3820
4068
  _getHealthyPools() {
3821
4069
  const result = [];
3822
4070
  for (const [name, config] of this._configs.entries()) {
3823
- const status = this._healthChecker.getStatus(name);
3824
- if (!status || status.status !== "down") {
3825
- result.push(config);
4071
+ const compatStatus = this._healthChecker.getStatus(name);
4072
+ const publicStatus = this.healthStatus.get(name);
4073
+ if (compatStatus?.status === "down" || publicStatus?.status === "down") {
4074
+ continue;
3826
4075
  }
4076
+ result.push(config);
3827
4077
  }
3828
4078
  return result;
3829
4079
  }
@@ -3976,7 +4226,7 @@ async function initializeDistributedCacheInvalidator(options, cache, logger) {
3976
4226
  logger
3977
4227
  });
3978
4228
  } catch (err) {
3979
- logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 is ioredis installed?", err);
4229
+ logger.warn?.("[Cache] Failed to initialize distributed cache invalidator \u2014 check Redis config or package installation completeness.", err);
3980
4230
  return null;
3981
4231
  }
3982
4232
  }
@@ -3984,7 +4234,7 @@ async function loadModelFiles(options, logger, opts = {}) {
3984
4234
  const modelsConfig = options.models;
3985
4235
  if (!modelsConfig) return;
3986
4236
  if (typeof modelsConfig !== "string" && typeof modelsConfig !== "object") return;
3987
- const { readdirSync } = await import("node:fs");
4237
+ const { readdirSync: readdirSync2 } = await import("node:fs");
3988
4238
  const { resolve, join, isAbsolute } = await import("node:path");
3989
4239
  const { createRequire } = await import("node:module");
3990
4240
  let targetPath;
@@ -4008,7 +4258,7 @@ async function loadModelFiles(options, logger, opts = {}) {
4008
4258
  const collectFiles = (dir) => {
4009
4259
  let entries;
4010
4260
  try {
4011
- entries = readdirSync(dir, { withFileTypes: true });
4261
+ entries = readdirSync2(dir, { withFileTypes: true });
4012
4262
  } catch {
4013
4263
  logger.warn?.(`[Models] cannot read directory: ${dir}`);
4014
4264
  return [];
@@ -4487,7 +4737,7 @@ async function indexStatsForAccessor(collectionRef) {
4487
4737
  }
4488
4738
  async function setValidatorForAccessor(collectionRef, collectionName, dbRef, validator, options = {}) {
4489
4739
  if (validator === null || typeof validator !== "object") {
4490
- throw new Error("Validator must be a non-null object");
4740
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Validator must be a non-null object");
4491
4741
  }
4492
4742
  const isEmptyValidator = Object.keys(validator).length === 0;
4493
4743
  const command = {
@@ -4508,14 +4758,14 @@ async function setValidatorForAccessor(collectionRef, collectionName, dbRef, val
4508
4758
  }
4509
4759
  async function setValidationLevelForAccessor(collectionRef, collectionName, dbRef, level) {
4510
4760
  if (typeof level !== "string" || !["off", "strict", "moderate"].includes(level)) {
4511
- throw new Error('Invalid validation level: must be "off", "strict", or "moderate"');
4761
+ throw createError(ErrorCodes.INVALID_ARGUMENT, 'Invalid validation level: must be "off", "strict", or "moderate"');
4512
4762
  }
4513
4763
  const result = await resolveDb(collectionRef, dbRef).command({ collMod: collectionName, validationLevel: level });
4514
4764
  return { ok: result["ok"], validationLevel: level };
4515
4765
  }
4516
4766
  async function setValidationActionForAccessor(collectionRef, collectionName, dbRef, action) {
4517
4767
  if (typeof action !== "string" || !["error", "warn"].includes(action)) {
4518
- throw new Error('Invalid validation action: must be "error" or "warn"');
4768
+ throw createError(ErrorCodes.INVALID_ARGUMENT, 'Invalid validation action: must be "error" or "warn"');
4519
4769
  }
4520
4770
  const result = await resolveDb(collectionRef, dbRef).command({ collMod: collectionName, validationAction: action });
4521
4771
  return { ok: result["ok"], validationAction: action };
@@ -4547,14 +4797,14 @@ async function statsForAccessor(collectionRef, dbName, collectionName, options =
4547
4797
  }
4548
4798
  async function renameCollectionForAccessor(collectionRef, collectionName, newName, options = {}) {
4549
4799
  if (!newName || typeof newName !== "string") {
4550
- throw new Error("New collection name is required and must be a non-empty string");
4800
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "New collection name is required and must be a non-empty string");
4551
4801
  }
4552
4802
  await collectionRef.rename(newName, { dropTarget: options.dropTarget ?? false });
4553
4803
  return { renamed: true, from: collectionName, to: newName };
4554
4804
  }
4555
4805
  async function collModForAccessor(collectionRef, collectionName, dbRef, modifications) {
4556
4806
  if (modifications === null || typeof modifications !== "object") {
4557
- throw new Error("Modifications must be a non-null object");
4807
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Modifications must be a non-null object");
4558
4808
  }
4559
4809
  return resolveDb(collectionRef, dbRef).command({
4560
4810
  collMod: collectionName,
@@ -4563,10 +4813,10 @@ async function collModForAccessor(collectionRef, collectionName, dbRef, modifica
4563
4813
  }
4564
4814
  async function convertToCappedForAccessor(collectionRef, collectionName, dbRef, size, options = {}) {
4565
4815
  if (typeof size !== "number") {
4566
- throw new Error("Size must be a number");
4816
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Size must be a number");
4567
4817
  }
4568
4818
  if (size <= 0) {
4569
- throw new Error("Size must be a positive number");
4819
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Size must be a positive number");
4570
4820
  }
4571
4821
  const command = { convertToCapped: collectionName, size };
4572
4822
  if (options.max !== void 0) {
@@ -4747,7 +4997,7 @@ function dispatchFunction(name, argsStr) {
4747
4997
  }
4748
4998
  case "REDUCE": {
4749
4999
  const lambdaMatch = /\((\w+),\s*(\w+)\)\s*=>\s*(.+)/.exec(args[2]);
4750
- if (!lambdaMatch) throw new Error("REDUCE requires a lambda: (acc, item) => expr");
5000
+ if (!lambdaMatch) throw createError(ErrorCodes.INVALID_EXPRESSION, "REDUCE requires a lambda: (acc, item) => expr");
4751
5001
  const [, accVar, itemVar, lambdaExpr] = lambdaMatch;
4752
5002
  const compiledExpr = lambdaExpr.replace(new RegExp(`\\b${accVar}\\b`, "g"), "$$value").replace(new RegExp(`\\b${itemVar}\\b`, "g"), "$$this");
4753
5003
  return { $reduce: { input: parseValue(args[0]), initialValue: parseValue(args[1]), in: compileInnerExpression(compiledExpr) } };
@@ -4892,7 +5142,7 @@ function dispatchFunction(name, argsStr) {
4892
5142
  return { $setUnion: unionArgs };
4893
5143
  }
4894
5144
  case "SWITCH": {
4895
- if (args.length < 2) throw new Error("SWITCH requires at least 2 arguments");
5145
+ if (args.length < 2) throw createError(ErrorCodes.INVALID_EXPRESSION, "SWITCH requires at least 2 arguments");
4896
5146
  const branches = [];
4897
5147
  let defaultValue = null;
4898
5148
  for (let index = 0; index < args.length - 1; index += 2) {
@@ -4910,15 +5160,15 @@ function dispatchFunction(name, argsStr) {
4910
5160
  case "ANY_ELEMENT_TRUE":
4911
5161
  return { $anyElementTrue: [parseValue(args[0])] };
4912
5162
  case "COND": {
4913
- if (args.length !== 3) throw new Error("COND requires 3 arguments");
5163
+ if (args.length !== 3) throw createError(ErrorCodes.INVALID_EXPRESSION, "COND requires 3 arguments");
4914
5164
  return { $cond: { if: compileInnerExpression(args[0]), then: parseValue(args[1]), else: parseValue(args[2]) } };
4915
5165
  }
4916
5166
  case "IF_NULL": {
4917
- if (args.length !== 2) throw new Error("IF_NULL requires 2 arguments");
5167
+ if (args.length !== 2) throw createError(ErrorCodes.INVALID_EXPRESSION, "IF_NULL requires 2 arguments");
4918
5168
  return { $ifNull: [parseValue(args[0]), parseValue(args[1])] };
4919
5169
  }
4920
5170
  case "SET_FIELD": {
4921
- if (args.length !== 3) throw new Error("SET_FIELD requires 3 arguments: (field, value, input)");
5171
+ if (args.length !== 3) throw createError(ErrorCodes.INVALID_EXPRESSION, "SET_FIELD requires 3 arguments: (field, value, input)");
4922
5172
  return { $setField: { field: parseValue(args[0]), input: parseValue(args[2]), value: parseValue(args[1]) } };
4923
5173
  }
4924
5174
  case "UNSET_FIELD":
@@ -4935,7 +5185,7 @@ function dispatchFunction(name, argsStr) {
4935
5185
  return { $setIsSubset: [parseValue(args[0]), parseValue(args[1])] };
4936
5186
  case "LET": {
4937
5187
  const varsMatch = /\{(.+)\}/.exec(args[0]);
4938
- if (!varsMatch) throw new Error("LET requires an object literal for variables");
5188
+ if (!varsMatch) throw createError(ErrorCodes.INVALID_EXPRESSION, "LET requires an object literal for variables");
4939
5189
  const varPairs = varsMatch[1].split(",").map((pair) => {
4940
5190
  const [key, ...rest] = pair.split(":");
4941
5191
  return [key.trim(), rest.join(":").trim()];
@@ -4953,7 +5203,7 @@ function dispatchFunction(name, argsStr) {
4953
5203
  case "SAMPLE_RATE":
4954
5204
  return { $sampleRate: parseValue(args[0]) };
4955
5205
  default:
4956
- throw new Error(`Unsupported function: ${name}`);
5206
+ throw createError(ErrorCodes.INVALID_EXPRESSION, `Unsupported function: ${name}`);
4957
5207
  }
4958
5208
  }
4959
5209
  function compileFilterCondition(condition, varName) {
@@ -5280,7 +5530,7 @@ function decodeCursor(cursor, secret) {
5280
5530
  }
5281
5531
  const payload = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
5282
5532
  if (payload?.v !== 1 || !Array.isArray(payload.values)) {
5283
- throw new Error("Invalid cursor payload.");
5533
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Invalid cursor payload.");
5284
5534
  }
5285
5535
  return payload.values;
5286
5536
  } catch (cause) {
@@ -5379,6 +5629,7 @@ function buildEffectiveProjection(projection, sort) {
5379
5629
  }
5380
5630
 
5381
5631
  // src/adapters/mongodb/queries/find-page.ts
5632
+ var import_node_crypto4 = require("node:crypto");
5382
5633
  function normalizePositiveInteger(value, fallback, field) {
5383
5634
  if (value === void 0 || value === null) {
5384
5635
  return fallback;
@@ -5397,54 +5648,192 @@ function mergeFilters(base, extra) {
5397
5648
  }
5398
5649
  return { $and: [base, extra] };
5399
5650
  }
5651
+ function stableStringify2(value) {
5652
+ if (value === void 0) {
5653
+ return '"__undefined__"';
5654
+ }
5655
+ if (value === null) {
5656
+ return "null";
5657
+ }
5658
+ if (Array.isArray(value)) {
5659
+ return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
5660
+ }
5661
+ if (value instanceof Date) {
5662
+ return JSON.stringify(value.toISOString());
5663
+ }
5664
+ if (typeof value === "object") {
5665
+ const customJson = value.toJSON;
5666
+ if (typeof customJson === "function" && value.constructor?.name !== "Object") {
5667
+ return stableStringify2(customJson.call(value));
5668
+ }
5669
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify2(item)}`);
5670
+ return `{${entries.join(",")}}`;
5671
+ }
5672
+ return JSON.stringify(value);
5673
+ }
5674
+ function hashPayload(payload) {
5675
+ return (0, import_node_crypto4.createHash)("sha256").update(stableStringify2(payload)).digest("hex");
5676
+ }
5677
+ function buildFindPageCacheKey(collection, options, normalized) {
5678
+ const payload = {
5679
+ query: normalized.query,
5680
+ sort: normalized.sort,
5681
+ limit: normalized.limit,
5682
+ page: normalized.page,
5683
+ after: options.after,
5684
+ before: options.before,
5685
+ projection: options.projection,
5686
+ pipeline: options.pipeline ?? [],
5687
+ totals: options.totals,
5688
+ jump: options.jump,
5689
+ offsetJump: options.offsetJump,
5690
+ maxTimeMS: normalized.maxTimeMS,
5691
+ hint: options.hint,
5692
+ collation: options.collation,
5693
+ batchSize: options.batchSize,
5694
+ options: options.options
5695
+ };
5696
+ const keyHash = hashPayload(payload);
5697
+ return { key: `findPage:${collection.namespace}:${keyHash}`, keyHash };
5698
+ }
5699
+ function buildTotalsCacheKey(collection, query, limit, totals) {
5700
+ const payload = {
5701
+ query,
5702
+ limit,
5703
+ mode: totals.mode ?? "sync",
5704
+ hint: totals.hint,
5705
+ collation: totals.collation,
5706
+ maxTimeMS: totals.maxTimeMS
5707
+ };
5708
+ const token = hashPayload(payload);
5709
+ return { key: `findPageTotals:${collection.namespace}:${token}`, token };
5710
+ }
5711
+ function cloneFindPageResult(result) {
5712
+ return {
5713
+ ...result,
5714
+ items: Array.isArray(result.items) ? [...result.items] : result.items,
5715
+ pageInfo: result.pageInfo && typeof result.pageInfo === "object" ? { ...result.pageInfo } : result.pageInfo,
5716
+ totals: result.totals && typeof result.totals === "object" ? { ...result.totals } : result.totals,
5717
+ meta: result.meta && typeof result.meta === "object" ? {
5718
+ ...result.meta,
5719
+ ns: { ...result.meta.ns },
5720
+ steps: result.meta.steps ? [...result.meta.steps] : void 0
5721
+ } : result.meta
5722
+ };
5723
+ }
5724
+ function getPositiveTtl(value, fallback) {
5725
+ return typeof value === "number" && value > 0 ? value : fallback;
5726
+ }
5400
5727
  var _asyncTotalsCache = new MemoryCache({
5401
5728
  maxEntries: 1e4,
5402
5729
  enableStats: false
5403
5730
  });
5404
- async function computeTotals(coll, query, limit, totals) {
5731
+ var _totalsInflight = /* @__PURE__ */ new Map();
5732
+ function runTotalsOnce(key, task) {
5733
+ if (_totalsInflight.has(key)) {
5734
+ return;
5735
+ }
5736
+ const promise = task().catch(() => {
5737
+ }).finally(() => {
5738
+ _totalsInflight.delete(key);
5739
+ });
5740
+ _totalsInflight.set(key, promise);
5741
+ }
5742
+ async function computeTotals(coll, query, limit, totals, defaults = {}, queryCache) {
5405
5743
  const mode = totals.mode ?? "sync";
5406
- if (mode === "sync") {
5744
+ const cache = queryCache ?? _asyncTotalsCache;
5745
+ const ttlMs = getPositiveTtl(totals.ttlMs, 10 * 6e4);
5746
+ const { key: cacheKey, token } = buildTotalsCacheKey(coll, query, limit, totals);
5747
+ const buildCountOptions = (fallbackMaxTimeMS) => {
5407
5748
  const countOpts = {};
5408
- if (totals.maxTimeMS !== void 0) {
5409
- countOpts.maxTimeMS = totals.maxTimeMS;
5749
+ const maxTimeMS = totals.maxTimeMS ?? fallbackMaxTimeMS;
5750
+ if (maxTimeMS !== void 0) {
5751
+ countOpts.maxTimeMS = maxTimeMS;
5752
+ }
5753
+ if (totals.hint !== void 0) {
5754
+ countOpts.hint = totals.hint;
5755
+ }
5756
+ if (totals.collation !== void 0) {
5757
+ countOpts.collation = totals.collation;
5410
5758
  }
5411
- const total = await coll.countDocuments(
5412
- query,
5759
+ return countOpts;
5760
+ };
5761
+ const countWithOptions = async () => {
5762
+ const countOpts = buildCountOptions(2e3);
5763
+ const countQuery = query;
5764
+ const runner = () => coll.countDocuments(
5765
+ countQuery,
5413
5766
  countOpts
5414
5767
  );
5415
- const totalPages = total > 0 ? Math.ceil(total / limit) : 0;
5416
- return { mode: "sync", total, totalPages, ts: Date.now() };
5768
+ return defaults.countQueue ? defaults.countQueue.execute(runner) : runner();
5769
+ };
5770
+ const buildPayload = (total, approx = false) => ({
5771
+ mode,
5772
+ total,
5773
+ totalPages: total > 0 ? Math.ceil(total / limit) : 0,
5774
+ ts: Date.now(),
5775
+ ...approx ? { approx: true } : {}
5776
+ });
5777
+ const buildFailurePayload = (error) => ({
5778
+ mode,
5779
+ total: null,
5780
+ totalPages: null,
5781
+ ts: Date.now(),
5782
+ error
5783
+ });
5784
+ if (mode === "sync") {
5785
+ const cached = cache.get(cacheKey);
5786
+ if (cached !== void 0) {
5787
+ return { ...cached, mode: "sync" };
5788
+ }
5789
+ try {
5790
+ const payload = buildPayload(await countWithOptions());
5791
+ await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
5792
+ return { ...payload, mode: "sync" };
5793
+ } catch {
5794
+ const payload = buildFailurePayload("count_failed");
5795
+ await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
5796
+ return { ...payload, mode: "sync" };
5797
+ }
5417
5798
  }
5418
5799
  if (mode === "async") {
5419
- const cacheKey = JSON.stringify({ ns: coll.namespace, q: query });
5420
- const token = Buffer.from(cacheKey).toString("base64url");
5421
- const cachedTotal = _asyncTotalsCache.get(cacheKey);
5422
- if (cachedTotal !== void 0) {
5423
- return { mode: "async", total: cachedTotal, token };
5800
+ const cached = cache.get(cacheKey);
5801
+ if (cached !== void 0) {
5802
+ return { ...cached, mode: "async", token };
5424
5803
  }
5425
- setImmediate(async () => {
5426
- try {
5427
- const n = await coll.countDocuments(
5428
- query
5429
- );
5430
- _asyncTotalsCache.set(cacheKey, n);
5431
- } catch {
5432
- }
5804
+ setImmediate(() => {
5805
+ runTotalsOnce(cacheKey, async () => {
5806
+ try {
5807
+ const payload = buildPayload(await countWithOptions());
5808
+ await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
5809
+ } catch {
5810
+ await Promise.resolve(cache.set(cacheKey, buildFailurePayload("count_failed"), ttlMs));
5811
+ }
5812
+ });
5433
5813
  });
5434
5814
  return { mode: "async", total: null, token };
5435
5815
  }
5436
5816
  if (mode === "approx") {
5437
- const countOpts = {};
5438
- if (totals.maxTimeMS !== void 0) {
5439
- countOpts.maxTimeMS = totals.maxTimeMS;
5817
+ const cached = cache.get(cacheKey);
5818
+ if (cached !== void 0) {
5819
+ return { ...cached, mode: "approx" };
5820
+ }
5821
+ try {
5822
+ const total = Object.keys(query ?? {}).length > 0 ? await countWithOptions() : await coll.estimatedDocumentCount({
5823
+ maxTimeMS: totals.maxTimeMS ?? 1e3
5824
+ });
5825
+ const payload = buildPayload(total, true);
5826
+ await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
5827
+ return { ...payload, mode: "approx" };
5828
+ } catch {
5829
+ const payload = buildFailurePayload("approx_failed");
5830
+ await Promise.resolve(cache.set(cacheKey, payload, ttlMs));
5831
+ return { ...payload, mode: "approx" };
5440
5832
  }
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
5833
  }
5445
5834
  return { mode: mode ?? "sync" };
5446
5835
  }
5447
- async function executeFindPage(collection, options = {}, defaults = {}) {
5836
+ async function executeFindPage(collection, options = {}, defaults = {}, queryCache) {
5448
5837
  const metaEnabled = options.meta === true || typeof options.meta === "object" && options.meta !== null;
5449
5838
  const metaOptions = options.meta && typeof options.meta === "object" ? options.meta : {};
5450
5839
  const metaLevel = options.meta === true ? "op" : metaOptions.level ?? "op";
@@ -5475,6 +5864,10 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5475
5864
  driverOpts.projection = buildEffectiveProjection(options.projection, sort);
5476
5865
  }
5477
5866
  const jumpOpts = ext.jump;
5867
+ const cacheTTL = typeof ext.cache === "number" && ext.cache > 0 ? ext.cache : 0;
5868
+ 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;
5869
+ const shouldRefreshAsyncTotals = options.totals?.mode === "async";
5870
+ let findPageCacheHit = false;
5478
5871
  const finishResult = (result2) => {
5479
5872
  if (!metaEnabled) {
5480
5873
  return result2;
@@ -5505,6 +5898,9 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5505
5898
  endTs: metaEndTs,
5506
5899
  durationMs: metaEndTs - metaStartTs,
5507
5900
  ...typeof effectiveMaxTimeMS === "number" ? { maxTimeMS: effectiveMaxTimeMS } : {},
5901
+ cacheHit: findPageCacheHit,
5902
+ ...findPageCacheHit ? { fromCache: true } : {},
5903
+ ...pageResultCache ? { cacheTtl: cacheTTL, keyHash: pageResultCache.keyHash } : {},
5508
5904
  page,
5509
5905
  after: Boolean(options.after),
5510
5906
  before: Boolean(options.before),
@@ -5573,7 +5969,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5573
5969
  };
5574
5970
  const timedComputeTotals = async () => {
5575
5971
  const stepStartTs = Date.now();
5576
- const result2 = await computeTotals(collection, baseQuery, limit, options.totals);
5972
+ const result2 = await computeTotals(collection, baseQuery, limit, options.totals, defaults, queryCache);
5577
5973
  pushMetaStep("computeTotals", Date.now() - stepStartTs, "totals");
5578
5974
  return result2;
5579
5975
  };
@@ -5592,6 +5988,32 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5592
5988
  ...extra.currentPage !== void 0 ? { currentPage: extra.currentPage } : {}
5593
5989
  };
5594
5990
  };
5991
+ const writePageResultCache = (result2) => {
5992
+ if (!pageResultCache || !queryCache) {
5993
+ return;
5994
+ }
5995
+ const cacheValue = cloneFindPageResult(result2);
5996
+ delete cacheValue.meta;
5997
+ if (shouldRefreshAsyncTotals) {
5998
+ delete cacheValue.totals;
5999
+ }
6000
+ void queryCache.set(pageResultCache.key, cacheValue, cacheTTL);
6001
+ };
6002
+ const finishAndCache = (result2) => {
6003
+ writePageResultCache(result2);
6004
+ return finishResult(result2);
6005
+ };
6006
+ if (pageResultCache && queryCache) {
6007
+ const cached = queryCache.get(pageResultCache.key);
6008
+ if (cached !== void 0) {
6009
+ findPageCacheHit = true;
6010
+ const result2 = cloneFindPageResult(cached);
6011
+ if (options.totals && options.totals.mode !== "none" && (shouldRefreshAsyncTotals || result2.totals === void 0)) {
6012
+ result2.totals = await timedComputeTotals();
6013
+ }
6014
+ return finishResult(result2);
6015
+ }
6016
+ }
5595
6017
  if (options.stream === true) {
5596
6018
  const direction = options.before ? "before" : "after";
5597
6019
  const { queryFilter, effectiveSort } = buildPageQuery(options.after ?? options.before, direction);
@@ -5634,7 +6056,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5634
6056
  if (options.totals && options.totals.mode !== "none") {
5635
6057
  result2.totals = await timedComputeTotals();
5636
6058
  }
5637
- return finishResult(result2);
6059
+ return finishAndCache(result2);
5638
6060
  }
5639
6061
  if (options.after || options.before) {
5640
6062
  const direction = options.after ? "after" : "before";
@@ -5644,7 +6066,7 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5644
6066
  const first = items2[0] ?? null;
5645
6067
  const last = items2[items2.length - 1] ?? null;
5646
6068
  const enc = (item) => item ? encodeCursor(Object.keys(sort).map((f) => item[f]), cursorSecret) : null;
5647
- return finishResult({
6069
+ const result2 = {
5648
6070
  items: items2,
5649
6071
  pageInfo: {
5650
6072
  hasNext: direction === "before" ? Boolean(options.before) : hasMore2,
@@ -5652,7 +6074,11 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5652
6074
  startCursor: enc(first),
5653
6075
  endCursor: enc(last)
5654
6076
  }
5655
- });
6077
+ };
6078
+ if (options.totals && options.totals.mode !== "none") {
6079
+ result2.totals = await timedComputeTotals();
6080
+ }
6081
+ return finishAndCache(result2);
5656
6082
  }
5657
6083
  const { queryFilter: q0, effectiveSort: es0 } = buildPageQuery();
5658
6084
  let { items, hasMore } = await timedFetchItems("initialFetch", page > 1 ? "hop" : "fetch", q0, es0, {}, 1);
@@ -5664,15 +6090,19 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5664
6090
  if (options.totals && options.totals.mode !== "none") {
5665
6091
  result2.totals = await timedComputeTotals();
5666
6092
  }
5667
- return finishResult(result2);
6093
+ return finishAndCache(result2);
5668
6094
  }
5669
6095
  for (let cp = 2; cp <= page; cp++) {
5670
6096
  const lastItem = items[items.length - 1];
5671
6097
  if (!lastItem) {
5672
- return finishResult({
6098
+ const result2 = {
5673
6099
  items,
5674
6100
  pageInfo: buildPageInfo(items, false, { hasPrev: cp > 2, currentPage: cp - 1 })
5675
- });
6101
+ };
6102
+ if (options.totals && options.totals.mode !== "none") {
6103
+ result2.totals = await timedComputeTotals();
6104
+ }
6105
+ return finishAndCache(result2);
5676
6106
  }
5677
6107
  const endCursor = encodeCursor(
5678
6108
  Object.keys(sort).map((f) => lastItem[f]),
@@ -5690,10 +6120,10 @@ async function executeFindPage(collection, options = {}, defaults = {}) {
5690
6120
  if (options.totals && options.totals.mode !== "none") {
5691
6121
  result.totals = await timedComputeTotals();
5692
6122
  }
5693
- return finishResult(result);
6123
+ return finishAndCache(result);
5694
6124
  }
5695
- async function findPageDocuments(collection, options = {}, defaults) {
5696
- return executeFindPage(collection, options, defaults ?? {});
6125
+ async function findPageDocuments(collection, options = {}, defaults, queryCache) {
6126
+ return executeFindPage(collection, options, defaults ?? {}, queryCache);
5697
6127
  }
5698
6128
 
5699
6129
  // src/adapters/mongodb/queries/find-by-id.ts
@@ -5915,21 +6345,21 @@ var FindChain = class {
5915
6345
  }
5916
6346
  limit(value) {
5917
6347
  if (typeof value !== "number" || value < 0) {
5918
- throw new Error(`limit() requires a non-negative number, got: ${typeof value} (${value})`);
6348
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `limit() requires a non-negative number, got: ${typeof value} (${value})`);
5919
6349
  }
5920
6350
  this.options.limit = value;
5921
6351
  return this;
5922
6352
  }
5923
6353
  skip(value) {
5924
6354
  if (typeof value !== "number" || value < 0) {
5925
- throw new Error(`skip() requires a non-negative number, got: ${typeof value} (${value})`);
6355
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `skip() requires a non-negative number, got: ${typeof value} (${value})`);
5926
6356
  }
5927
6357
  this.options.skip = value;
5928
6358
  return this;
5929
6359
  }
5930
6360
  sort(value) {
5931
6361
  if (!value || typeof value !== "object") {
5932
- throw new Error(`sort() requires an object or array, got: ${typeof value}`);
6362
+ throw createError(ErrorCodes.INVALID_ARGUMENT, `sort() requires an object or array, got: ${typeof value}`);
5933
6363
  }
5934
6364
  this.options.sort = value;
5935
6365
  return this;
@@ -5973,7 +6403,7 @@ var FindChain = class {
5973
6403
  }
5974
6404
  toArray() {
5975
6405
  if (this.executed) {
5976
- throw new Error("Query already executed.");
6406
+ throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
5977
6407
  }
5978
6408
  this.executed = true;
5979
6409
  return this.collection.find(this.normalizedQuery, buildFindDriverOptions(this.buildExecuteOptions())).toArray();
@@ -6061,7 +6491,7 @@ var AggregateChain = class {
6061
6491
  }
6062
6492
  toArray() {
6063
6493
  if (this.executed) {
6064
- throw new Error("Query already executed.");
6494
+ throw createError(ErrorCodes.INVALID_OPERATION, "Query already executed.");
6065
6495
  }
6066
6496
  this.executed = true;
6067
6497
  return this.collection.aggregate(this.pipeline, buildAggregateDriverOptions(this.buildExecuteOptions())).toArray();
@@ -6856,7 +7286,7 @@ async function insertOneForAccessor(context, doc, options) {
6856
7286
  const threshold = context.defaults?.slowQueryMs ?? 500;
6857
7287
  if (elapsed > threshold && context.logger) {
6858
7288
  try {
6859
- context.logger.warn("[insertOne] \u6162\u64CD\u4F5C\u8B66\u544A", {
7289
+ context.logger.warn("[insertOne] slow operation warning", {
6860
7290
  ns: `${context.dbName}.${context.collectionName}`,
6861
7291
  threshold,
6862
7292
  duration: elapsed,
@@ -6901,7 +7331,7 @@ async function insertManyForAccessor(context, documents, options) {
6901
7331
  const elapsed = Date.now() - startedAt;
6902
7332
  const threshold = context.defaults?.slowQueryMs ?? 500;
6903
7333
  if (elapsed >= threshold && context.logger) {
6904
- context.logger.warn("[insertMany] \u6162\u64CD\u4F5C\u8B66\u544A", {
7334
+ context.logger.warn("[insertMany] slow operation warning", {
6905
7335
  ns: `${context.dbName}.${context.collectionName}`,
6906
7336
  threshold,
6907
7337
  duration: elapsed,
@@ -7032,11 +7462,17 @@ var MongoCollectionAccessor = class {
7032
7462
  const legacyNamespacePatterns = [
7033
7463
  `${String(this.management.defaults?.namespace?.instanceId)}:mongodb:${this.dbName}:${this.collectionName}:*`
7034
7464
  ];
7035
- const patterns = operation === "find" ? [`find:${namespace}:*`] : operation === "findOne" ? [`findOne:${namespace}:*`] : operation === "count" ? [`count:${namespace}:*`] : operation === "findPage" ? [`bookmark:${bookmarkNamespace}:*`] : [
7465
+ const findPagePatterns = [
7466
+ `findPage:${namespace}:*`,
7467
+ `findPageTotals:${namespace}:*`,
7468
+ `bookmark:${bookmarkNamespace}:*`,
7469
+ `${bookmarkNamespace}:bm:*`
7470
+ ];
7471
+ const patterns = operation === "find" ? [`find:${namespace}:*`] : operation === "findOne" ? [`findOne:${namespace}:*`] : operation === "count" ? [`count:${namespace}:*`] : operation === "findPage" ? findPagePatterns : [
7036
7472
  `find:${namespace}:*`,
7037
7473
  `findOne:${namespace}:*`,
7038
7474
  `count:${namespace}:*`,
7039
- `bookmark:${bookmarkNamespace}:*`
7475
+ ...findPagePatterns
7040
7476
  ];
7041
7477
  patterns.push(...legacyNamespacePatterns);
7042
7478
  let deleted = 0;
@@ -7166,7 +7602,7 @@ var MongoCollectionAccessor = class {
7166
7602
  }
7167
7603
  async findPage(options = {}) {
7168
7604
  const resolvedOptions = options.query ? { ...options, query: this._cvFilter(options.query) } : options;
7169
- return findPageDocuments(this.collectionRef, resolvedOptions, this.management.defaults);
7605
+ return findPageDocuments(this.collectionRef, resolvedOptions, this.management.defaults, this.management.queryCache);
7170
7606
  }
7171
7607
  /** Opens a change stream on the collection with an optional aggregation pipeline. */
7172
7608
  watch(pipeline = [], options) {
@@ -7858,7 +8294,7 @@ function createRuntimeAdapterBridge(host) {
7858
8294
  dropDatabase: async (name, adminOptions) => {
7859
8295
  host.ensureConnected();
7860
8296
  if (!name || typeof name !== "string") {
7861
- throw new Error("Database name is required and must be a non-empty string");
8297
+ throw createError(ErrorCodes.INVALID_DATABASE_NAME, "Database name is required and must be a non-empty string");
7862
8298
  }
7863
8299
  if (!adminOptions?.confirm) {
7864
8300
  const error = new Error(
@@ -7894,7 +8330,7 @@ function createRuntimeAdapterBridge(host) {
7894
8330
  runCommand: async (command, adminOptions) => {
7895
8331
  host.ensureConnected();
7896
8332
  if (command === null || typeof command !== "object") {
7897
- throw new Error("Command must be a non-null object");
8333
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Command must be a non-null object");
7898
8334
  }
7899
8335
  return host.db().runCommand(command, adminOptions ?? {});
7900
8336
  },
@@ -7910,7 +8346,7 @@ function createRuntimeAdapterBridge(host) {
7910
8346
  }
7911
8347
 
7912
8348
  // src/capabilities/lock/index.ts
7913
- var import_node_crypto4 = require("node:crypto");
8349
+ var import_node_crypto5 = require("node:crypto");
7914
8350
  var LockAcquireError = class extends Error {
7915
8351
  constructor(message) {
7916
8352
  super(message);
@@ -8020,13 +8456,13 @@ var LockManager = class {
8020
8456
  if (attempt === retryTimes) {
8021
8457
  break;
8022
8458
  }
8023
- const delay = retryDelay * Math.pow(retryBackoff, attempt);
8024
- await sleep3(delay);
8459
+ const delay2 = retryDelay * Math.pow(retryBackoff, attempt);
8460
+ await sleep3(delay2);
8025
8461
  }
8026
8462
  this.stats.errors += 1;
8027
8463
  if (options.fallbackToNoLock) {
8028
8464
  this.logger?.warn?.(`[LockManager] fallback to no-lock execution for ${key}`);
8029
- return new Lock(this.normalizeKey(key), `noop:${(0, import_node_crypto4.randomUUID)()}`, new NoopLockManager(), options.ttl ?? 1e4);
8465
+ return new Lock(this.normalizeKey(key), `noop:${(0, import_node_crypto5.randomUUID)()}`, new NoopLockManager(), options.ttl ?? 1e4);
8030
8466
  }
8031
8467
  throw new LockTimeoutError(`Failed to acquire lock for key '${key}' within retry budget.`);
8032
8468
  }
@@ -8042,7 +8478,7 @@ var LockManager = class {
8042
8478
  if (globalStore.has(normalizedKey)) {
8043
8479
  return null;
8044
8480
  }
8045
- const lockId = (0, import_node_crypto4.randomUUID)();
8481
+ const lockId = (0, import_node_crypto5.randomUUID)();
8046
8482
  globalStore.set(normalizedKey, {
8047
8483
  lockId,
8048
8484
  expiresAt: Date.now() + ttl
@@ -8145,7 +8581,7 @@ var DistributedCacheLockManager = class {
8145
8581
  errors: 0
8146
8582
  };
8147
8583
  if (!options.redis) {
8148
- throw new Error("DistributedCacheLockManager requires a Redis instance");
8584
+ throw createError(ErrorCodes.INVALID_CONFIG, "DistributedCacheLockManager requires a Redis instance");
8149
8585
  }
8150
8586
  this.redis = options.redis;
8151
8587
  this.lockKeyPrefix = options.lockKeyPrefix ?? "monsqlize:cache:lock:";
@@ -8334,7 +8770,7 @@ var DistributedCacheLockManager = class {
8334
8770
  };
8335
8771
 
8336
8772
  // src/capabilities/saga/index.ts
8337
- var import_node_crypto5 = require("node:crypto");
8773
+ var import_node_crypto6 = require("node:crypto");
8338
8774
  var SagaExecutionContext = class {
8339
8775
  constructor(executionId, data) {
8340
8776
  this.executionId = executionId;
@@ -8430,7 +8866,7 @@ var SagaOrchestrator = class {
8430
8866
  if (!definition) {
8431
8867
  throw createError(ErrorCodes.INVALID_ARGUMENT, `Saga '${name}' is not defined`);
8432
8868
  }
8433
- const sagaId = `saga_${(0, import_node_crypto5.randomBytes)(8).toString("hex")}`;
8869
+ const sagaId = `saga_${(0, import_node_crypto6.randomBytes)(8).toString("hex")}`;
8434
8870
  const startedAt = Date.now();
8435
8871
  const context = new SagaExecutionContext(sagaId, data);
8436
8872
  const completedSteps = [];
@@ -8803,17 +9239,17 @@ var BatchQueue = class {
8803
9239
  var import_mongodb6 = require("mongodb");
8804
9240
 
8805
9241
  // src/capabilities/slow-query-log/slow-query-log-records.ts
8806
- var import_node_crypto6 = require("node:crypto");
8807
- function stableStringify2(value) {
9242
+ var import_node_crypto7 = require("node:crypto");
9243
+ function stableStringify3(value) {
8808
9244
  if (Array.isArray(value)) {
8809
- return `[${value.map((item) => stableStringify2(item)).join(",")}]`;
9245
+ return `[${value.map((item) => stableStringify3(item)).join(",")}]`;
8810
9246
  }
8811
9247
  if (value instanceof Date) {
8812
9248
  return JSON.stringify(value.toISOString());
8813
9249
  }
8814
9250
  if (value && typeof value === "object") {
8815
9251
  const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
8816
- return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableStringify2(current)}`).join(",")}}`;
9252
+ return `{${entries.map(([key, current]) => `${JSON.stringify(key)}:${stableStringify3(current)}`).join(",")}}`;
8817
9253
  }
8818
9254
  return JSON.stringify(value);
8819
9255
  }
@@ -8830,7 +9266,7 @@ function normalizeHashInput(input) {
8830
9266
  };
8831
9267
  }
8832
9268
  function generateQueryHash(input) {
8833
- return (0, import_node_crypto6.createHash)("sha256").update(stableStringify2(normalizeHashInput(input))).digest("hex").slice(0, 16);
9269
+ return (0, import_node_crypto7.createHash)("sha256").update(stableStringify3(normalizeHashInput(input))).digest("hex").slice(0, 16);
8834
9270
  }
8835
9271
  function handleSlowQueryLogError(logger, policy, error) {
8836
9272
  if (policy === "throw") {
@@ -9280,7 +9716,7 @@ var SlowQueryLogManager = class {
9280
9716
 
9281
9717
  // src/capabilities/sync/index.ts
9282
9718
  var import_promises = require("node:fs/promises");
9283
- var import_node_path = __toESM(require("node:path"));
9719
+ var import_node_path2 = __toESM(require("node:path"));
9284
9720
  var import_mongodb7 = require("mongodb");
9285
9721
  function validateTargetConfig(target, index) {
9286
9722
  if (!target || typeof target !== "object") {
@@ -9387,7 +9823,7 @@ var ResumeTokenStore = class {
9387
9823
  await Promise.resolve(this.redis.set(this.redisKey, payload));
9388
9824
  return;
9389
9825
  }
9390
- await (0, import_promises.mkdir)(import_node_path.default.dirname(this.path), { recursive: true });
9826
+ await (0, import_promises.mkdir)(import_node_path2.default.dirname(this.path), { recursive: true });
9391
9827
  await (0, import_promises.writeFile)(this.path, payload, "utf8");
9392
9828
  } catch (error) {
9393
9829
  this.logger?.error?.("[Sync] failed to save resume token", error);
@@ -9659,7 +10095,16 @@ function getOrCreateTransactionManager(config) {
9659
10095
  client: config.client,
9660
10096
  cache: config.cache,
9661
10097
  logger: config.logger,
9662
- lockManager: config.lockManager
10098
+ lockManager: config.lockManager,
10099
+ maxDuration: config.transaction?.maxDuration ?? config.transaction?.defaultTimeout,
10100
+ enableRetry: config.transaction?.enableRetry,
10101
+ maxRetries: config.transaction?.maxRetries,
10102
+ retryDelay: config.transaction?.retryDelay,
10103
+ retryBackoff: config.transaction?.retryBackoff,
10104
+ defaultReadConcern: config.transaction?.defaultReadConcern,
10105
+ defaultWriteConcern: config.transaction?.defaultWriteConcern,
10106
+ defaultReadPreference: config.transaction?.defaultReadPreference,
10107
+ maxStatsSamples: config.transaction?.maxStatsSamples
9663
10108
  });
9664
10109
  }
9665
10110
  function getOrCreateLockManager(current, logger) {
@@ -9946,7 +10391,7 @@ function resolveCacheSource(cacheOrDb) {
9946
10391
  return cache;
9947
10392
  }
9948
10393
  }
9949
- throw new Error("Invalid cache instance from MonSQLize");
10394
+ throw createError(ErrorCodes.CACHE_UNAVAILABLE, "Invalid cache instance from MonSQLize");
9950
10395
  }
9951
10396
  function toWithCacheStats(stats, totalTime = 0) {
9952
10397
  const calls = stats.hits + stats.misses;
@@ -9975,46 +10420,46 @@ function normalizeFunctionCacheStats(stats, timings, name) {
9975
10420
  }
9976
10421
  function validateFunctionCacheOptions(options) {
9977
10422
  if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) {
9978
- throw new Error("options must be an object");
10423
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "options must be an object");
9979
10424
  }
9980
10425
  const opts = options ?? {};
9981
10426
  if (opts.namespace !== void 0 && typeof opts.namespace !== "string") {
9982
- throw new Error("namespace must be a string");
10427
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "namespace must be a string");
9983
10428
  }
9984
10429
  const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
9985
10430
  if (ttl !== void 0 && (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)) {
9986
- throw new Error("defaultTTL must be a non-negative number");
10431
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "defaultTTL must be a non-negative number");
9987
10432
  }
9988
10433
  return opts;
9989
10434
  }
9990
10435
  function validateFunctionCachePerFnOptions(options) {
9991
10436
  if (options !== void 0 && (typeof options !== "object" || options === null || Array.isArray(options))) {
9992
- throw new Error("options must be an object");
10437
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "options must be an object");
9993
10438
  }
9994
10439
  const opts = options ?? {};
9995
10440
  if (opts.keyBuilder !== void 0 && typeof opts.keyBuilder !== "function") {
9996
- throw new Error("keyBuilder must be a function");
10441
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "keyBuilder must be a function");
9997
10442
  }
9998
10443
  if (opts.condition !== void 0 && typeof opts.condition !== "function") {
9999
- throw new Error("condition must be a function");
10444
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "condition must be a function");
10000
10445
  }
10001
10446
  const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
10002
10447
  if (ttl !== void 0 && (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)) {
10003
- throw new Error("defaultTTL must be a non-negative number");
10448
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "defaultTTL must be a non-negative number");
10004
10449
  }
10005
10450
  return opts;
10006
10451
  }
10007
10452
  function withCache(fn, options = {}) {
10008
- if (typeof fn !== "function") throw new Error("fn must be a function");
10453
+ if (typeof fn !== "function") throw createError(ErrorCodes.INVALID_ARGUMENT, "fn must be a function");
10009
10454
  const { ttl = 6e4, namespace, keyBuilder, condition, cache: externalCache, enableStats = true } = options;
10010
10455
  if (typeof ttl !== "number" || Number.isNaN(ttl) || ttl < 0)
10011
- throw new Error("ttl must be a non-negative number");
10456
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "ttl must be a non-negative number");
10012
10457
  if (keyBuilder !== void 0 && typeof keyBuilder !== "function")
10013
- throw new Error("keyBuilder must be a function");
10458
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "keyBuilder must be a function");
10014
10459
  if (condition !== void 0 && typeof condition !== "function")
10015
- throw new Error("condition must be a function");
10460
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "condition must be a function");
10016
10461
  if (externalCache !== void 0 && !isValidCache(externalCache))
10017
- throw new Error("Invalid cache instance");
10462
+ throw createError(ErrorCodes.CACHE_UNAVAILABLE, "Invalid cache instance");
10018
10463
  const wrapped = (0, import_function_cache.withCache)(fn, {
10019
10464
  ttl,
10020
10465
  namespace: namespace ?? "fn",
@@ -10056,9 +10501,9 @@ var FunctionCache = class {
10056
10501
  }
10057
10502
  register(name, fn, options) {
10058
10503
  if (!name || typeof name !== "string")
10059
- throw new Error("Function name must be a non-empty string");
10504
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Function name must be a non-empty string");
10060
10505
  if (typeof fn !== "function")
10061
- throw new Error("fn must be a function");
10506
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "fn must be a function");
10062
10507
  const opts = validateFunctionCachePerFnOptions(options);
10063
10508
  const ttl = opts.ttl !== void 0 ? opts.ttl : opts.defaultTTL;
10064
10509
  this._inner.register(name, fn, {
@@ -10084,13 +10529,13 @@ var FunctionCache = class {
10084
10529
  }
10085
10530
  async invalidate(name, ...args) {
10086
10531
  if (!name || typeof name !== "string") {
10087
- throw new Error("Function name must be a non-empty string");
10532
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Function name must be a non-empty string");
10088
10533
  }
10089
10534
  await this._inner.invalidate(name, ...args);
10090
10535
  }
10091
10536
  async invalidatePattern(pattern) {
10092
10537
  if (!pattern || typeof pattern !== "string")
10093
- throw new Error("Pattern must be a non-empty string");
10538
+ throw createError(ErrorCodes.INVALID_ARGUMENT, "Pattern must be a non-empty string");
10094
10539
  return this._inner.invalidatePattern(pattern);
10095
10540
  }
10096
10541
  list() {
@@ -10125,15 +10570,15 @@ function b64urlDecodeStr(s) {
10125
10570
  const b64 = pad(String(s || "")).replace(/-/g, "+").replace(/_/g, "/");
10126
10571
  return Buffer.from(b64, "base64").toString();
10127
10572
  }
10128
- function makeInvalidCursorError(cause) {
10129
- const err = new Error("Invalid cursor");
10573
+ function makeInvalidCursorError(message = "Invalid cursor", cause) {
10574
+ const err = new Error(message);
10130
10575
  err.code = "INVALID_CURSOR";
10131
10576
  if (cause !== void 0) err.cause = cause;
10132
10577
  return err;
10133
10578
  }
10134
10579
  function encodeCursor2(payload) {
10135
10580
  if (!payload.s || !payload.a) {
10136
- throw new Error("encodeCursor requires sort (s) and anchor (a)");
10581
+ throw makeInvalidCursorError("encodeCursor requires sort (s) and anchor (a)");
10137
10582
  }
10138
10583
  const json = JSON.stringify({ v: payload.v ?? 1, s: payload.s, a: payload.a, d: payload.d });
10139
10584
  return b64urlEncodeStr(json);
@@ -10142,11 +10587,11 @@ function decodeCursor2(str) {
10142
10587
  try {
10143
10588
  const obj = JSON.parse(b64urlDecodeStr(str));
10144
10589
  if (!obj || obj["v"] !== 1 || !obj["s"] || !obj["a"]) {
10145
- throw new Error("bad-structure");
10590
+ throw makeInvalidCursorError("bad-structure");
10146
10591
  }
10147
10592
  return obj;
10148
10593
  } catch (e) {
10149
- throw makeInvalidCursorError(e);
10594
+ throw makeInvalidCursorError("Invalid cursor", e);
10150
10595
  }
10151
10596
  }
10152
10597
 
@@ -10218,7 +10663,11 @@ var MonSQLizeRuntime = class {
10218
10663
  } : rawCacheInput;
10219
10664
  this._cache = normalizeRuntimeCache(cacheInput);
10220
10665
  this._logger = Logger.create(options.logger ?? null);
10221
- this._cacheLockManager = new CacheLockManager({ logger: options.logger ?? null });
10666
+ this._cacheLockManager = new CacheLockManager({
10667
+ logger: options.logger ?? null,
10668
+ maxDuration: options.transaction?.lockMaxDuration,
10669
+ cleanupInterval: options.transaction?.lockCleanupInterval
10670
+ });
10222
10671
  this._cache.setLockManager?.(this._cacheLockManager);
10223
10672
  this._runtimeDefaults = buildRuntimeDefaults(options);
10224
10673
  this._adapterCacheOverride = void 0;
@@ -10589,9 +11038,15 @@ var MonSQLizeRuntime = class {
10589
11038
  listSagas() {
10590
11039
  return this.initializeSagaOrchestrator().listSagas();
10591
11040
  }
11041
+ getTransactionStats() {
11042
+ return this._transactionManager?.getStats() ?? null;
11043
+ }
10592
11044
  getSagaStats() {
10593
11045
  return this.initializeSagaOrchestrator().getStats();
10594
11046
  }
11047
+ getDistributedCacheInvalidatorStats() {
11048
+ return this._distributedInvalidator?.getStats() ?? null;
11049
+ }
10595
11050
  async startSync() {
10596
11051
  this.ensureConnected();
10597
11052
  const manager = await this.initializeSyncManager();
@@ -10670,7 +11125,8 @@ var MonSQLizeRuntime = class {
10670
11125
  client: this._client,
10671
11126
  cache: this._cache,
10672
11127
  logger: this.options.logger ?? null,
10673
- lockManager: this._cacheLockManager
11128
+ lockManager: this._cacheLockManager,
11129
+ transaction: this.options.transaction
10674
11130
  });
10675
11131
  return this._transactionManager;
10676
11132
  }