@classytic/mongokit 3.44.0 → 3.45.0

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.
Files changed (46) hide show
  1. package/README.md +4 -3
  2. package/dist/{PaginationEngine-C9iS0U5l.mjs → PaginationEngine-BRlLTQQz.mjs} +1 -1
  3. package/dist/{PaginationEngine-D17AthKw.d.mts → PaginationEngine-BWb-c47a.d.mts} +1 -1
  4. package/dist/{Repository-B6oL6OMv.d.mts → Repository-DJuRmBNx.d.mts} +16 -7
  5. package/dist/{Repository-eMRYZ_2Y.mjs → Repository-DheA7oW-.mjs} +135 -29
  6. package/dist/actions/index.d.mts +1 -1
  7. package/dist/actions/index.mjs +2 -2
  8. package/dist/{actions-DExyXMTy.mjs → actions-DKQx9ToJ.mjs} +1 -1
  9. package/dist/ai/index.d.mts +1 -1
  10. package/dist/better-auth/index.d.mts +5 -2
  11. package/dist/better-auth/index.mjs +10 -20
  12. package/dist/{create-repository-D7MQpIZo.mjs → create-repository-C9DHHcl7.mjs} +3 -3
  13. package/dist/{create-repository-BOFUYj1l.d.mts → create-repository-DG-gwOf0.d.mts} +5 -5
  14. package/dist/{index-8QUEsOFI.d.mts → index-5mhQ63_B.d.mts} +2 -2
  15. package/dist/index.d.mts +19 -18
  16. package/dist/index.mjs +10 -10
  17. package/dist/{method-registry.plugin-CGAepsBU.mjs → method-registry.plugin-BeJfQBJV.mjs} +2 -2
  18. package/dist/{multi-tenant.plugin-DfeDMiI8.d.mts → multi-tenant.plugin-BTPtCLNK.d.mts} +2 -2
  19. package/dist/{operations-Dw9OFV0V.d.mts → operations-D6XsHx4e.d.mts} +6 -7
  20. package/dist/outbox/index.d.mts +2 -2
  21. package/dist/outbox/index.mjs +2 -2
  22. package/dist/pagination/PaginationEngine.d.mts +1 -1
  23. package/dist/pagination/PaginationEngine.mjs +1 -1
  24. package/dist/pagination/utils/limits.d.mts +1 -1
  25. package/dist/{pagination-DAUg5zOY.d.mts → pagination-BAM9cA4N.d.mts} +5 -7
  26. package/dist/{plugin-options-Bpcuz9kS.d.mts → plugin-options-DrNGWV5b.d.mts} +2 -2
  27. package/dist/plugins/index.d.mts +4 -4
  28. package/dist/plugins/index.mjs +4 -4
  29. package/dist/{query-defaults-aUoKlEbh.mjs → query-defaults-DwvWUjZU.mjs} +16 -51
  30. package/dist/read-model/index.d.mts +1 -1
  31. package/dist/{repository-DzVIwhU2.d.mts → repository-B0aFVgP2.d.mts} +1 -1
  32. package/dist/{retention-purge-DqcYvRnG.d.mts → retention-purge-CCd_VXUf.d.mts} +4 -1
  33. package/dist/sync/index.d.mts +25 -9
  34. package/dist/sync/index.mjs +122 -12
  35. package/dist/testkit/index.d.mts +2 -2
  36. package/dist/testkit/index.mjs +1 -1
  37. package/dist/{timestamp.plugin-Bz_zL9PX.mjs → timestamp.plugin-B-IJrlYb.mjs} +1 -1
  38. package/dist/{to-plain-PO5r1Qoy.d.mts → to-plain-Do-8jTKC.d.mts} +2 -2
  39. package/dist/{transaction-C2PFxfIv.mjs → transaction-C6Oj941P.mjs} +14 -12
  40. package/dist/{update-DcfmNbJe.mjs → update-CYaJMu6j.mjs} +2 -4
  41. package/dist/utils/index.d.mts +1 -1
  42. package/dist/{validation-chain.plugin-B3gZCZj4.d.mts → validation-chain.plugin-COYrUdd8.d.mts} +19 -4
  43. package/dist/{validation-chain.plugin-DNY6MIF6.mjs → validation-chain.plugin-DJplVJ12.mjs} +10 -7
  44. package/package.json +11 -11
  45. package/skills/mongokit/references/plugins-tenancy.md +1 -0
  46. package/skills/mongokit/references/transactions-outbox.md +3 -1
@@ -1,5 +1,5 @@
1
- import { A as ObjectId } from "./pagination-DAUg5zOY.mjs";
2
- import { l as Plugin, p as RepositoryContext } from "./repository-DzVIwhU2.mjs";
1
+ import { A as ObjectId } from "./pagination-BAM9cA4N.mjs";
2
+ import { l as Plugin, p as RepositoryContext } from "./repository-B0aFVgP2.mjs";
3
3
  import mongoose, { Connection } from "mongoose";
4
4
  import { CacheAdapter as CacheAdapter$1, CacheOptions, RepositoryCacheHandle as RepositoryCacheHandle$1, RepositoryCachePluginOptions, cachePlugin } from "@classytic/repo-core/cache";
5
5
  import { TenantConfig } from "@classytic/repo-core/tenant";
@@ -1,4 +1,4 @@
1
- import { A as ObjectId, F as SortSpec, M as ReadPreferenceType, N as SelectSpec, b as WriteConcernSpec, i as CountStrategy, j as PopulateSpec, r as CollationOptions, v as ReadConcernLevel } from "./pagination-DAUg5zOY.mjs";
1
+ import { A as ObjectId, F as SortSpec, M as ReadPreferenceType, N as SelectSpec, b as WriteConcernSpec, i as CountStrategy, j as PopulateSpec, r as CollationOptions, v as ReadConcernLevel } from "./pagination-BAM9cA4N.mjs";
2
2
  import { DeleteManyResult, DeleteResult, LookupPopulateResult, UpdateManyResult } from "@classytic/repo-core/repository";
3
3
  import { PipelineStage, PopulateOptions } from "mongoose";
4
4
  import { Filter } from "@classytic/repo-core/filter";
@@ -378,12 +378,11 @@ interface CacheOperationOptions {
378
378
  cache?: import('@classytic/repo-core/cache').CacheOptions;
379
379
  }
380
380
  /**
381
- * `session.withTransaction()`'s own options param — `TransactionOptions` (readConcern,
382
- * writeConcern, readPreference, maxCommitTimeMS) PLUS the driver's `timeoutMS`, which is
383
- * NOT a field on `TransactionOptions` itself (verified against the installed mongodb
384
- * driver's `ClientSession.withTransaction` overload, mongodb.d.ts). `timeoutMS` bounds the
385
- * WHOLE `withTransaction` call, including its built-in retry loop (default 120s) — distinct
386
- * from `maxCommitTimeMS`, which only bounds a single `commitTransaction` server call.
381
+ * `TransactionOptions` (readConcern, writeConcern, readPreference, maxCommitTimeMS) PLUS
382
+ * `timeoutMS`: the budget for the WHOLE transaction, including its retry loop (default 120 s).
383
+ * It is enforced by mongokit, NOT the driver (the driver's `timeoutMS` is a no-op since 7.8):
384
+ * every mongokit operation inside is sent with `maxTimeMS` = what remains, and the commit with
385
+ * `maxCommitTimeMS` = what remained when the attempt started.
387
386
  */
388
387
  type ConvenientTransactionOptions = import('mongoose').mongo.TransactionOptions & {
389
388
  timeoutMS?: number;
@@ -1,5 +1,5 @@
1
- import { r as Repository } from "../Repository-B6oL6OMv.mjs";
2
- import { u as PaginationConfig } from "../pagination-DAUg5zOY.mjs";
1
+ import { r as Repository } from "../Repository-DJuRmBNx.mjs";
2
+ import { u as PaginationConfig } from "../pagination-BAM9cA4N.mjs";
3
3
  import { Connection, Model } from "mongoose";
4
4
  /** Delivery states. `dead_letter` is terminal and operator-visible. */
5
5
  export declare const OUTBOX_STATUSES: readonly ['pending', 'delivered', 'dead_letter'];
@@ -1,5 +1,5 @@
1
- import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-CGAepsBU.mjs";
2
- import { n as Repository } from "../Repository-eMRYZ_2Y.mjs";
1
+ import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-BeJfQBJV.mjs";
2
+ import { n as Repository } from "../Repository-DheA7oW-.mjs";
3
3
  import { Schema } from "mongoose";
4
4
  // src/outbox/index.ts
5
5
  /**
@@ -1,2 +1,2 @@
1
- import { n as PaginationEngine, t as CountMemoKey } from "../PaginationEngine-D17AthKw.mjs";
1
+ import { n as PaginationEngine, t as CountMemoKey } from "../PaginationEngine-BWb-c47a.mjs";
2
2
  export { CountMemoKey, PaginationEngine };
@@ -1 +1 @@
1
- export { t as PaginationEngine } from "../PaginationEngine-C9iS0U5l.mjs";
1
+ export { t as PaginationEngine } from "../PaginationEngine-BRlLTQQz.mjs";
@@ -1,4 +1,4 @@
1
- import { u as PaginationConfig } from "../../pagination-DAUg5zOY.mjs";
1
+ import { u as PaginationConfig } from "../../pagination-BAM9cA4N.mjs";
2
2
  /**
3
3
  * Validates and sanitizes limit value
4
4
  * Parses strings to numbers and prevents NaN bugs
@@ -1,4 +1,4 @@
1
- import { Connection, Document, Model, PipelineStage, PopulateOptions, Types } from "mongoose";
1
+ import { Document, Model, PipelineStage, PopulateOptions, Types } from "mongoose";
2
2
  /**
3
3
  * Core query/document primitives shared across every mongokit module —
4
4
  * sort/select/populate/filter specs and the mongoose-facing aliases.
@@ -99,17 +99,15 @@ declare function getQueryDefaults(): {
99
99
  /** Back to no deployment defaults. For tests. */
100
100
  declare function resetQueryDefaults(): void;
101
101
  /**
102
- * Boot check (M7): throws unless the deployment sets a query `maxTimeMS`, and — when a connection
103
- * is passed — unless its client carries a CSOT `timeoutMS`. Returns what it verified.
102
+ * Boot check (M7): throws unless the deployment sets a query `maxTimeMS`. Returns what it verified.
103
+ * A client `timeoutMS` is not checked: the driver (7.8+) drops it from the client options and only
104
+ * logs a process warning, so there is nothing to read; it bounds nothing either way.
104
105
  */
105
- declare function assertQueryDefaultsConfigured(connection?: Connection): {
106
+ declare function assertQueryDefaultsConfigured(): {
106
107
  maxTimeMS: number;
107
- timeoutMS?: number;
108
108
  };
109
109
  declare const QUERY_DEFAULTS_ERROR_CODES: {
110
110
  readonly NOT_CONFIGURED: 'mongokit.query_defaults.not_configured';
111
- /** A per-call bound inside a transaction that carries its own CSOT budget (the driver forbids both). */
112
- readonly TIMEOUT_IN_TRANSACTION: 'mongokit.query_defaults.timeout_in_transaction';
113
111
  };
114
112
  /** Resolve the options one command runs with. `repo` is the repository's own defaults. */
115
113
  declare function resolveQueryOptions(kind: CommandKind, perCall: PerCallQueryOptions, repo?: {
@@ -1,5 +1,5 @@
1
- import { A as ObjectId, F as SortSpec, N as SelectSpec, j as PopulateSpec } from "./pagination-DAUg5zOY.mjs";
2
- import { h as RepositoryInstance, p as RepositoryContext } from "./repository-DzVIwhU2.mjs";
1
+ import { A as ObjectId, F as SortSpec, N as SelectSpec, j as PopulateSpec } from "./pagination-BAM9cA4N.mjs";
2
+ import { h as RepositoryInstance, p as RepositoryContext } from "./repository-B0aFVgP2.mjs";
3
3
  import { OffsetPaginationResult } from "@classytic/repo-core/pagination";
4
4
  /** Field preset configuration (field-filter plugin) */
5
5
  interface FieldPreset {
@@ -1,7 +1,7 @@
1
- import { A as ImmutablePurgeEvent, B as elasticSearchPlugin, C as MethodRegistryRepository, D as LeasePluginOptions, E as LeasePendingFilter, F as IdempotencyStoreMethods, G as batchOperationsPlugin, H as BatchOperationsMethods, I as IdempotencyStorePluginOptions, J as AppendOnlyPurgeEvent, K as auditLogPlugin, L as idempotencyStorePlugin, M as ImmutableStatesPluginOptions, N as immutableStatesPlugin, O as leasePlugin, P as IdempotencyClaim, R as fieldFilterPlugin, S as mongoOperationsPlugin, T as LeaseMethods, V as cascadePlugin, X as AggregateHelpersMethods, Y as appendOnlyPlugin, Z as aggregateHelpersPlugin, _ as searchKeysPlugin, a as requireField, b as observabilityPlugin, c as timestampPlugin, d as createTenantContext, f as SubdocumentMethods, g as SearchKeysPluginOptions, h as softDeletePlugin, i as immutableField, j as ImmutablePurgeRequest, k as ImmutableClaimView, l as TenantContext, m as SoftDeleteMethods, n as autoInject, o as uniqueField, p as subdocumentPlugin, q as AppendOnlyPluginOptions, r as blockIf, s as validationChainPlugin, t as OperationType, u as TenantStore, v as ObservabilityOptions, w as methodRegistryPlugin, x as MongoOperationsMethods, y as OperationMetric, z as ElasticSearchOptions } from "../validation-chain.plugin-B3gZCZj4.mjs";
2
- import { A as cachePlugin, B as EnsureAuditTrailReadyOptions, C as prefixedId, D as CacheOptions, E as CacheAdapter, F as AuditTrailMethods, H as ensureAuditTrailReady, I as AuditTrailMode, L as AuditTrailOptions, M as AuditOperation, N as AuditQueryOptions, O as RepositoryCacheHandle, P as AuditQueryResult, R as AuditTrailQuery, T as sequentialId, V as auditTrailPlugin, _ as customIdPlugin, a as DateSequentialIdOptions, b as getNextPeriodSequence, i as CustomIdOptions, j as AuditEntry, k as RepositoryCachePluginOptions, m as SequentialIdOptions, o as IdGenerator, r as multiTenantPlugin, t as MultiTenantOptions, u as PrefixedIdOptions, v as dateSequentialId, x as getNextSequence, z as AuditTrailQueryOptions } from "../multi-tenant.plugin-DfeDMiI8.mjs";
3
- import { C as RepositoryPluginName, S as MongoKitPluginName, l as Plugin, x as MONGOKIT_PLUGIN_NAMES } from "../repository-DzVIwhU2.mjs";
4
- import { n as RetentionPurgeEvent, r as RetentionPurgeRequest, t as RETENTION_PURGE_ERROR_CODES } from "../retention-purge-DqcYvRnG.mjs";
1
+ import { A as ImmutablePurgeEvent, B as elasticSearchPlugin, C as MethodRegistryRepository, D as LeasePluginOptions, E as LeasePendingFilter, F as IdempotencyStoreMethods, G as batchOperationsPlugin, H as BatchOperationsMethods, I as IdempotencyStorePluginOptions, J as AppendOnlyPurgeEvent, K as auditLogPlugin, L as idempotencyStorePlugin, M as ImmutableStatesPluginOptions, N as immutableStatesPlugin, O as leasePlugin, P as IdempotencyClaim, R as fieldFilterPlugin, S as mongoOperationsPlugin, T as LeaseMethods, V as cascadePlugin, X as AggregateHelpersMethods, Y as appendOnlyPlugin, Z as aggregateHelpersPlugin, _ as searchKeysPlugin, a as requireField, b as observabilityPlugin, c as timestampPlugin, d as createTenantContext, f as SubdocumentMethods, g as SearchKeysPluginOptions, h as softDeletePlugin, i as immutableField, j as ImmutablePurgeRequest, k as ImmutableClaimView, l as TenantContext, m as SoftDeleteMethods, n as autoInject, o as uniqueField, p as subdocumentPlugin, q as AppendOnlyPluginOptions, r as blockIf, s as validationChainPlugin, t as OperationType, u as TenantStore, v as ObservabilityOptions, w as methodRegistryPlugin, x as MongoOperationsMethods, y as OperationMetric, z as ElasticSearchOptions } from "../validation-chain.plugin-COYrUdd8.mjs";
2
+ import { A as cachePlugin, B as EnsureAuditTrailReadyOptions, C as prefixedId, D as CacheOptions, E as CacheAdapter, F as AuditTrailMethods, H as ensureAuditTrailReady, I as AuditTrailMode, L as AuditTrailOptions, M as AuditOperation, N as AuditQueryOptions, O as RepositoryCacheHandle, P as AuditQueryResult, R as AuditTrailQuery, T as sequentialId, V as auditTrailPlugin, _ as customIdPlugin, a as DateSequentialIdOptions, b as getNextPeriodSequence, i as CustomIdOptions, j as AuditEntry, k as RepositoryCachePluginOptions, m as SequentialIdOptions, o as IdGenerator, r as multiTenantPlugin, t as MultiTenantOptions, u as PrefixedIdOptions, v as dateSequentialId, x as getNextSequence, z as AuditTrailQueryOptions } from "../multi-tenant.plugin-BTPtCLNK.mjs";
3
+ import { C as RepositoryPluginName, S as MongoKitPluginName, l as Plugin, x as MONGOKIT_PLUGIN_NAMES } from "../repository-B0aFVgP2.mjs";
4
+ import { n as RetentionPurgeEvent, r as RetentionPurgeRequest, t as RETENTION_PURGE_ERROR_CODES } from "../retention-purge-CCd_VXUf.mjs";
5
5
  import { ChangeLogStore } from "@classytic/repo-core/sync";
6
6
  interface ChangeLogPluginOptions {
7
7
  /** Durable feed implementing the repo-core/sync contract. */
@@ -1,7 +1,7 @@
1
- import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "../validation-chain.plugin-DNY6MIF6.mjs";
2
- import { C as ensureAuditTrailReady, S as auditTrailPlugin, _ as prefixedId, b as cachePlugin, d as customIdPlugin, f as dateSequentialId, h as getNextSequence, i as multiTenantPlugin, m as getNextPeriodSequence, n as softDeletePlugin, t as timestampPlugin, w as auditLogPlugin, x as AuditTrailQuery, y as sequentialId } from "../timestamp.plugin-Bz_zL9PX.mjs";
3
- import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-CGAepsBU.mjs";
4
- import { d as RETENTION_PURGE_ERROR_CODES, i as searchKeysPlugin } from "../Repository-eMRYZ_2Y.mjs";
1
+ import { _ as cascadePlugin, a as uniqueField, c as subdocumentPlugin, d as mongoOperationsPlugin, f as leasePlugin, g as elasticSearchPlugin, h as fieldFilterPlugin, i as requireField, l as observabilityPlugin, m as idempotencyStorePlugin, n as blockIf, o as validationChainPlugin, p as immutableStatesPlugin, r as immutableField, s as createTenantContext, t as autoInject, u as MONGOKIT_PLUGIN_NAMES, v as appendOnlyPlugin, y as aggregateHelpersPlugin } from "../validation-chain.plugin-DJplVJ12.mjs";
2
+ import { C as ensureAuditTrailReady, S as auditTrailPlugin, _ as prefixedId, b as cachePlugin, d as customIdPlugin, f as dateSequentialId, h as getNextSequence, i as multiTenantPlugin, m as getNextPeriodSequence, n as softDeletePlugin, t as timestampPlugin, w as auditLogPlugin, x as AuditTrailQuery, y as sequentialId } from "../timestamp.plugin-B-IJrlYb.mjs";
3
+ import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "../method-registry.plugin-BeJfQBJV.mjs";
4
+ import { d as RETENTION_PURGE_ERROR_CODES, i as searchKeysPlugin } from "../Repository-DheA7oW-.mjs";
5
5
  // src/plugins/change-log.plugin.ts
6
6
  const PLUGIN_NAME = "changeLog";
7
7
  /** Marks a repository already captured: the store and scope it writes to. */
@@ -9,13 +9,13 @@ import mongoose from "mongoose";
9
9
  * An explicit value always wins (FL1). For an aggregation the aggregate chain is consulted before
10
10
  * the generic query chain, because it is the more specific instruction.
11
11
  *
12
- * The bound travels as per-op CSOT `timeoutMS` (the driver derives the wire `maxTimeMS` from
13
- * it, and a client-level `timeoutMS` cannot override it). It and `comment` go to the commands Mongoose itself bounds with its global `maxTimeMS`
14
- * (find, findOne, countDocuments, distinct, findOneAndUpdate, aggregate:
15
- * lib/query.js:2465,2812,2905,3074,3610; lib/aggregate.js:1106). Writes get `writeConcern`. The
16
- * connection's CSOT `timeoutMS` bounds everything else; {@link assertQueryDefaultsConfigured}
17
- * checks both at boot. Inside a transaction a DEFAULT read/write concern or read preference is
18
- * dropped (the transaction owns them); a per-call one is passed and the driver judges it.
12
+ * The bound travels as the server-side `maxTimeMS` on the command (driver 7.8 made the client-side
13
+ * `timeoutMS` / CSOT a no-op, CVE-2026-119179, so there is no per-op client budget any more). It
14
+ * reaches find, findOne, countDocuments, distinct, findOneAndUpdate, aggregate, insertMany,
15
+ * update/delete and bulkWrite. A single-document `create()` goes through Mongoose `save()`, which
16
+ * forwards no `maxTimeMS`, so it is NOT server-bounded. Writes also get `writeConcern`.
17
+ * {@link assertQueryDefaultsConfigured} checks the deployment bound at boot. Inside a transaction a DEFAULT read/write concern or read
18
+ * preference is dropped (the transaction owns them); a per-call one is passed and the driver judges it.
19
19
  */
20
20
  /** On `globalThis`, for the reason `pagination/defaults.ts` gives: a duplicated install must share it. */
21
21
  const POLICY_SLOT = Symbol.for("classytic.mongokit.queryDefaults");
@@ -60,31 +60,19 @@ function resetQueryDefaults() {
60
60
  };
61
61
  }
62
62
  /**
63
- * Boot check (M7): throws unless the deployment sets a query `maxTimeMS`, and — when a connection
64
- * is passed — unless its client carries a CSOT `timeoutMS`. Returns what it verified.
63
+ * Boot check (M7): throws unless the deployment sets a query `maxTimeMS`. Returns what it verified.
64
+ * A client `timeoutMS` is not checked: the driver (7.8+) drops it from the client options and only
65
+ * logs a process warning, so there is nothing to read; it bounds nothing either way.
65
66
  */
66
- function assertQueryDefaultsConfigured(connection) {
67
+ function assertQueryDefaultsConfigured() {
67
68
  const maxTimeMS = policy().query.maxTimeMS;
68
69
  if (maxTimeMS === void 0) throw createError(500, "[mongokit] no deployment query time bound: call configureQueryDefaults({ maxTimeMS }) at boot", {
69
70
  code: QUERY_DEFAULTS_ERROR_CODES.NOT_CONFIGURED,
70
71
  meta: { missing: "maxTimeMS" }
71
72
  });
72
- if (!connection) return { maxTimeMS };
73
- const timeoutMS = connection.getClient().options.timeoutMS;
74
- if (!(typeof timeoutMS === "number" && timeoutMS > 0)) throw createError(500, "[mongokit] the connection has no CSOT timeoutMS: pass { timeoutMS } to mongoose.connect()", {
75
- code: QUERY_DEFAULTS_ERROR_CODES.NOT_CONFIGURED,
76
- meta: { missing: "timeoutMS" }
77
- });
78
- return {
79
- maxTimeMS,
80
- timeoutMS
81
- };
73
+ return { maxTimeMS };
82
74
  }
83
- const QUERY_DEFAULTS_ERROR_CODES = {
84
- NOT_CONFIGURED: "mongokit.query_defaults.not_configured",
85
- /** A per-call bound inside a transaction that carries its own CSOT budget (the driver forbids both). */
86
- TIMEOUT_IN_TRANSACTION: "mongokit.query_defaults.timeout_in_transaction"
87
- };
75
+ const QUERY_DEFAULTS_ERROR_CODES = { NOT_CONFIGURED: "mongokit.query_defaults.not_configured" };
88
76
  const TX_DEADLINE = Symbol.for("@classytic/mongokit/transaction-deadline");
89
77
  /**
90
78
  * Set (or clear) the transaction budget's deadline (`performance.now()` ms) on a session.
@@ -108,14 +96,6 @@ function inTransaction(session) {
108
96
  const s = session;
109
97
  return typeof s?.inTransaction === "function" && s.inTransaction();
110
98
  }
111
- /**
112
- * A `withTransaction` running under a CSOT budget: the driver refuses any per-op `timeoutMS`
113
- * there (mongodb lib/utils.js:451, `session.explicit && session.timeoutContext != null`).
114
- */
115
- function csotTransaction(session) {
116
- const s = session;
117
- return inTransaction(session) && s?.explicit === true && s.timeoutContext != null;
118
- }
119
99
  /** Resolve the options one command runs with. `repo` is the repository's own defaults. */
120
100
  function resolveQueryOptions(kind, perCall, repo = {}) {
121
101
  const deployment = policy();
@@ -132,7 +112,6 @@ function resolveQueryOptions(kind, perCall, repo = {}) {
132
112
  const budget = tx ? transactionRemaining(perCall.session) : void 0;
133
113
  if (budget !== void 0 && budget <= 0) throw new mongoose.mongo.MongoOperationTimeoutError("Transaction budget exhausted before this operation");
134
114
  if (kind !== "write") {
135
- if (csotTransaction(perCall.session) && perCall.maxTimeMS !== void 0) throw createError(400, "[mongokit] a per-call maxTimeMS cannot be honoured inside a transaction with its own timeoutMS budget; bound the transaction instead", { code: QUERY_DEFAULTS_ERROR_CODES.TIMEOUT_IN_TRANSACTION });
136
115
  const bound = tx ? perCall.maxTimeMS : perCall.maxTimeMS ?? (kind === "aggregate" ? repo.aggregate?.maxTimeMs ?? deployment.aggregate.maxTimeMs : void 0) ?? q.maxTimeMS;
137
116
  const capped = budget === void 0 ? bound : Math.min(bound ?? Number.POSITIVE_INFINITY, budget);
138
117
  if (capped !== void 0) out.maxTimeMS = capped;
@@ -174,34 +153,20 @@ function readPreferenceMode(value) {
174
153
  }
175
154
  /** Apply resolved options to a mongoose Query. */
176
155
  function applyToQuery(query, o) {
177
- if (o.maxTimeMS !== void 0) {
178
- const csot = { timeoutMS: o.maxTimeMS };
179
- query.setOptions(csot);
180
- }
156
+ if (o.maxTimeMS !== void 0) query.maxTimeMS(o.maxTimeMS);
181
157
  if (o.comment !== void 0) query.setOptions({ comment: o.comment });
182
158
  if (o.readPreference !== void 0) query.read(readPreferenceMode(o.readPreference));
183
159
  if (o.readConcern !== void 0) query.readConcern(o.readConcern);
184
160
  if (o.writeConcern !== void 0) query.setOptions({ writeConcern: o.writeConcern });
185
161
  if (o.hint !== void 0) query.hint(o.hint);
186
162
  }
187
- /**
188
- * Make a find cursor's bound apply PER BATCH (CSOT `timeoutMode: 'iteration'`). Only when some
189
- * `timeoutMS` exists (this op's, or the client's): the driver refuses a timeoutMode without one
190
- * (mongodb lib/cursor/abstract_cursor.js:114). In iteration mode the driver enforces the budget
191
- * client-side per batch and sends no wire maxTimeMS.
192
- */
193
- function applyCursorTimeoutMode(query, o, connection) {
194
- const clientTimeout = connection.getClient().options.timeoutMS;
195
- if (o.maxTimeMS === void 0 && clientTimeout === void 0) return;
196
- query.setOptions({ timeoutMode: "iteration" });
197
- }
198
163
  /** Apply resolved options to a mongoose Aggregate. */
199
164
  function applyToAggregate(aggregation, o) {
200
- if (o.maxTimeMS !== void 0) aggregation.option({ timeoutMS: o.maxTimeMS });
165
+ if (o.maxTimeMS !== void 0) aggregation.option({ maxTimeMS: o.maxTimeMS });
201
166
  if (o.comment !== void 0) aggregation.option({ comment: o.comment });
202
167
  if (o.readPreference !== void 0) aggregation.read(readPreferenceMode(o.readPreference));
203
168
  if (o.readConcern !== void 0) aggregation.readConcern(o.readConcern);
204
169
  if (o.allowDiskUse !== void 0) aggregation.allowDiskUse(o.allowDiskUse);
205
170
  if (o.hint !== void 0) aggregation.hint(o.hint);
206
171
  }
207
- export { assertQueryDefaultsConfigured as a, getQueryDefaults as c, setTransactionDeadline as d, applyToQuery as i, resetQueryDefaults as l, applyCursorTimeoutMode as n, configureAggregateDefaults as o, applyToAggregate as r, configureQueryDefaults as s, QUERY_DEFAULTS_ERROR_CODES as t, resolveQueryOptions as u };
172
+ export { configureAggregateDefaults as a, resetQueryDefaults as c, assertQueryDefaultsConfigured as i, resolveQueryOptions as l, applyToAggregate as n, configureQueryDefaults as o, applyToQuery as r, getQueryDefaults as s, QUERY_DEFAULTS_ERROR_CODES as t, setTransactionDeadline as u };
@@ -1,4 +1,4 @@
1
- import { r as Repository } from "../Repository-B6oL6OMv.mjs";
1
+ import { r as Repository } from "../Repository-DJuRmBNx.mjs";
2
2
  import { BulkUpsertResult } from "@classytic/repo-core/repository";
3
3
  export declare const READ_MODEL_ERROR_CODES: {
4
4
  readonly MERGE_IN_TRANSACTION: 'mongokit.read_model.merge_in_transaction';
@@ -1,4 +1,4 @@
1
- import { A as ObjectId, D as AnyDocument, F as SortSpec, M as ReadPreferenceType, N as SelectSpec, _ as QueryDefaults, b as WriteConcernSpec, i as CountStrategy, j as PopulateSpec, k as FilterQuery, p as AggregateDefaults, v as ReadConcernLevel } from "./pagination-DAUg5zOY.mjs";
1
+ import { A as ObjectId, D as AnyDocument, F as SortSpec, M as ReadPreferenceType, N as SelectSpec, _ as QueryDefaults, b as WriteConcernSpec, i as CountStrategy, j as PopulateSpec, k as FilterQuery, p as AggregateDefaults, v as ReadConcernLevel } from "./pagination-BAM9cA4N.mjs";
2
2
  import { r as DocField } from "./type-utils-CKvuewAL.mjs";
3
3
  import { DistributionConfig, DistributionMissHandler, RepoCapabilities, RepositoryBase } from "@classytic/repo-core/repository";
4
4
  import { Model, PipelineStage } from "mongoose";
@@ -1,4 +1,4 @@
1
- import { p as RepositoryContext } from "./repository-DzVIwhU2.mjs";
1
+ import { p as RepositoryContext } from "./repository-B0aFVgP2.mjs";
2
2
  /** Per-call retention purge request (`options.retentionPurge`). */
3
3
  interface RetentionPurgeRequest {
4
4
  /** Why the rows are removed (e.g. `'retention: 7 years'`). Required, non-blank. */
@@ -17,6 +17,8 @@ interface RetentionPurgeEvent {
17
17
  filter: Record<string, unknown> | undefined;
18
18
  /** Rows removed (`delete`: 1 or 0). */
19
19
  deletedCount: number;
20
+ /** True when the delete is an `archiveByFilter` chunk the sink acknowledged first. */
21
+ archived: boolean;
20
22
  /**
21
23
  * The delete's session, when the call ran in one. Write the audit row with it
22
24
  * (`{ session: event.session }`) so the delete and its audit commit or abort
@@ -31,5 +33,6 @@ declare const RETENTION_PURGE_ERROR_CODES: {
31
33
  readonly REASON_REQUIRED: 'RETENTION_PURGE_REASON_REQUIRED';
32
34
  readonly INVALID_ACTOR: 'RETENTION_PURGE_INVALID_ACTOR';
33
35
  readonly HARD_ONLY: 'RETENTION_PURGE_HARD_ONLY';
36
+ readonly ARCHIVE_UNATTESTED: 'RETENTION_PURGE_ARCHIVE_UNATTESTED';
34
37
  };
35
38
  export { RetentionPurgeEvent as n, RetentionPurgeRequest as r, RETENTION_PURGE_ERROR_CODES as t };
@@ -1,5 +1,5 @@
1
1
  import { ClientSession, Connection, Model } from "mongoose";
2
- import { ChangeEntry, ChangeLogAppendOptions, ChangeLogStore, ChangesSinceOptions, CommandStreamStore, CommandStreamTx } from "@classytic/repo-core/sync";
2
+ import { ChangeEntry, ChangeLogAppendOptions, ChangeLogStore, ChangesPage, ChangesSinceOptions, CommandStreamStore, CommandStreamTx } from "@classytic/repo-core/sync";
3
3
  export interface ChangeLogModelOptions {
4
4
  /** Entries collection. Default `sync_changes`; its counter lives in `<collection>_counter`. */
5
5
  collection?: string;
@@ -43,12 +43,21 @@ export interface ChangeLogModels {
43
43
  feed: string;
44
44
  }
45
45
  export declare function createChangeLogModels(connection: Connection, options?: ChangeLogModelOptions): ChangeLogModels;
46
+ /** {@link MongoChangeLogStore.snapshot}'s options: the reader's view, plus the bound after the first page. */
47
+ export interface ChangesSnapshotOptions extends ChangesSinceOptions {
48
+ /** The `upTo` the first page returned; omit it on the first page. */
49
+ readonly upTo?: string;
50
+ }
51
+ /** One snapshot page. `cursor` pages the snapshot; `upTo` is where `since()` resumes once `hasMore` is false. */
52
+ export interface ChangesSnapshotPage extends ChangesPage {
53
+ readonly upTo: string;
54
+ }
46
55
  /** The mongo change feed: the `ChangeLogStore` contract plus superseded-entry compaction. */
47
56
  export interface MongoChangeLogStore extends ChangeLogStore {
48
57
  /**
49
- * Delete every entry a later COMMITTED entry of the same `(tenantId, scope, docId)` replaced. Safe for any client
50
- * cursor: entries carry whole documents, so a reader only ever needs the latest one; tombstones and
51
- * each document's latest entry stay. Idempotent; run it on a schedule.
58
+ * Delete every entry of a `(tenantId, scope, docId)` but the one a reader ends on (the last
59
+ * tombstone, or the highest-version upsert after it). Safe for any client cursor: entries carry
60
+ * whole documents, and a reader would have skipped every deleted one. Idempotent; run it on a schedule.
52
61
  */
53
62
  compactSuperseded(options?: {
54
63
  batchSize?: number;
@@ -57,9 +66,8 @@ export interface MongoChangeLogStore extends ChangeLogStore {
57
66
  }>;
58
67
  /**
59
68
  * Retention with a horizon (the `ChangeLogStore.prune` contract). Deletes every entry older than
60
- * `before` (`at < before`) that a later entry of the same `(tenantId, scope, docId)` replaced:
61
- * each document's latest entry stays, tombstone or not, so a full reload still sees every
62
- * document. Records a HORIZON (the highest deleted cursor, never lowered) per `(tenantId, scope)`
69
+ * `before` (`at < before`) except, per `(tenantId, scope, docId)`, the one a reader ends on (as
70
+ * `compactSuperseded` keeps), so a snapshot still sees every document. Records a HORIZON (the highest deleted cursor, never lowered) per `(tenantId, scope)`
63
71
  * and feed-wide, BEFORE deleting, and returns the feed-wide one, `''` while nothing was ever
64
72
  * pruned. `since(cursor, options)` with a cursor below the horizon of what that reader can see
65
73
  * (its tenant, shared scopes and `scopes`; the feed-wide horizon when unfiltered; `''` included)
@@ -73,6 +81,14 @@ export interface MongoChangeLogStore extends ChangeLogStore {
73
81
  prune(before: Date, options?: {
74
82
  batchSize?: number;
75
83
  }): Promise<string>;
84
+ /**
85
+ * The resync bootstrap for a reader `since()` expired: each document's latest entry at or below
86
+ * `upTo` (the reader's horizon, fixed by the first page), tombstones left out, in feed order. Then
87
+ * `since(upTo)` carries on, delivering every change above it, deletes included. Complete because
88
+ * `prune` and `compactSuperseded` never delete a document's latest entry. If that `since(upTo)`
89
+ * expires again, a prune raised the horizon in between: bootstrap again.
90
+ */
91
+ snapshot(cursor: string, options?: ChangesSnapshotOptions): Promise<ChangesSnapshotPage>;
76
92
  /**
77
93
  * The compaction horizon cursor a reader with these options is held to (`''` while nothing it
78
94
  * can see was pruned); feed-wide without options.
@@ -94,8 +110,8 @@ export interface MongoChangeLogStore extends ChangeLogStore {
94
110
  * entry; `null` when the feed holds none. Reads only (it never runs the sequencer), so it is cheap on a hot
95
111
  * path, e.g. a writer that needs the last recorded state of a record before it appends the next change.
96
112
  * A document is `(tenantId, scope, docId)`: `tenantId` omitted or `null` reads the untenanted document.
97
- * Pass `session` to see what the caller staged or appended in its own transaction. A pruned or compacted
98
- * document still has its latest entry (both keep it).
113
+ * Pass `session` to see what the caller staged or appended in its own transaction. Pruning and
114
+ * compaction keep the entry a reader ends on, which may not be the last one recorded.
99
115
  */
100
116
  latestOf(scope: string, docId: string, options?: {
101
117
  tenantId?: string | null;
@@ -1,4 +1,4 @@
1
- import { o as withTransaction } from "../transaction-C2PFxfIv.mjs";
1
+ import { o as withTransaction } from "../transaction-C6Oj941P.mjs";
2
2
  import { Schema, Types } from "mongoose";
3
3
  import { CursorExpiredError } from "@classytic/repo-core/sync";
4
4
  // src/sync/index.ts
@@ -394,6 +394,45 @@ function createChangeLogStore(models, storeOptions = {}) {
394
394
  scope: id.scope,
395
395
  docId: id.docId
396
396
  });
397
+ /**
398
+ * The entry a reader of one document ends on, which retention keeps. A tombstone applies
399
+ * unconditionally (`changeLogPlugin` writes version 0); an upsert only above the version held. So:
400
+ * the last tombstone, unless upserts follow it, then the highest of those (the first on a tie).
401
+ * Not simply the last cursor: branch stock records a lower version after a higher one.
402
+ */
403
+ const winnerOf = (rows) => {
404
+ let held = null;
405
+ for (const row of [...rows].sort((a, b) => a.seq - b.seq)) if (row.op === "delete") held = {
406
+ seq: row.seq,
407
+ version: Number.NEGATIVE_INFINITY
408
+ };
409
+ else if (!held || row.version > held.version) held = {
410
+ seq: row.seq,
411
+ version: row.version
412
+ };
413
+ return held?.seq ?? 0;
414
+ };
415
+ const winnerSeq = async (filter) => {
416
+ const tomb = await entries.findOne({
417
+ ...filter,
418
+ op: "delete"
419
+ }).sort({ seq: -1 }).select({ seq: 1 }).lean().exec();
420
+ const bound = filter.seq ?? {};
421
+ return (await entries.findOne(tomb?.seq === void 0 ? {
422
+ ...filter,
423
+ op: "upsert"
424
+ } : {
425
+ ...filter,
426
+ op: "upsert",
427
+ seq: {
428
+ ...bound,
429
+ $gt: tomb.seq
430
+ }
431
+ }).sort({
432
+ version: -1,
433
+ seq: 1
434
+ }).select({ seq: 1 }).lean().exec())?.seq ?? tomb?.seq ?? 0;
435
+ };
397
436
  const toEntry = (row) => ({
398
437
  scope: row.scope,
399
438
  docId: row.docId,
@@ -506,11 +545,11 @@ function createChangeLogStore(models, storeOptions = {}) {
506
545
  const groups = entries.aggregate([
507
546
  { $group: {
508
547
  _id: docKey,
509
- latest: { $max: "$seq" },
548
+ top: { $max: "$seq" },
510
549
  n: { $sum: 1 }
511
550
  } },
512
551
  { $match: { n: { $gt: 1 } } },
513
- { $project: { latest: 1 } }
552
+ { $project: { top: 1 } }
514
553
  ]).allowDiskUse(true).cursor({ batchSize });
515
554
  let removed = 0;
516
555
  let ops = [];
@@ -520,9 +559,17 @@ function createChangeLogStore(models, storeOptions = {}) {
520
559
  ops = [];
521
560
  };
522
561
  for await (const g of groups) {
562
+ const doc = docOf(g._id);
563
+ const keep = await winnerSeq({
564
+ ...doc,
565
+ seq: { $lte: g.top }
566
+ });
523
567
  ops.push({ deleteMany: { filter: {
524
- ...docOf(g._id),
525
- seq: { $lt: g.latest }
568
+ ...doc,
569
+ seq: {
570
+ $lte: g.top,
571
+ $ne: keep
572
+ }
526
573
  } } });
527
574
  if (ops.length >= batchSize) await flush();
528
575
  }
@@ -533,6 +580,66 @@ function createChangeLogStore(models, storeOptions = {}) {
533
580
  const seq = await horizonFor(options);
534
581
  return seq > 0 ? toCursor(seq) : "";
535
582
  },
583
+ async snapshot(cursor, options = {}) {
584
+ const { upTo: bound, ...view } = options;
585
+ const limit = view.limit ?? DEFAULT_LIMIT;
586
+ const upTo = bound === void 0 ? await horizonFor(view) : fromCursor(bound);
587
+ const after = fromCursor(cursor);
588
+ const upToCursor = upTo > 0 ? toCursor(upTo) : "";
589
+ if (after >= upTo) return {
590
+ changes: [],
591
+ cursor,
592
+ hasMore: false,
593
+ upTo: upToCursor
594
+ };
595
+ const rows = await entries.find({
596
+ seq: {
597
+ $gt: after,
598
+ $lte: upTo
599
+ },
600
+ ...viewOf(view)
601
+ }).sort({ seq: 1 }).limit(limit + 1).lean().exec();
602
+ const page = rows.slice(0, limit);
603
+ const latest = /* @__PURE__ */ new Map();
604
+ if (page.length > 0) {
605
+ const keyOf = (r) => JSON.stringify([
606
+ typeof r.tenantId === "string" ? r.tenantId : null,
607
+ r.scope,
608
+ r.docId
609
+ ]);
610
+ const docs = new Map(page.map((r) => [keyOf(r), r]));
611
+ const groups = await entries.aggregate([{ $match: {
612
+ seq: { $lte: upTo },
613
+ $or: [...docs.values()].map((r) => docOf({
614
+ tenantId: typeof r.tenantId === "string" ? r.tenantId : null,
615
+ scope: r.scope,
616
+ docId: r.docId
617
+ }))
618
+ } }, { $group: {
619
+ _id: docKey,
620
+ rows: { $push: {
621
+ seq: "$seq",
622
+ version: "$version",
623
+ op: "$op"
624
+ } }
625
+ } }]).allowDiskUse(true).exec();
626
+ for (const g of groups) latest.set(keyOf(g._id), winnerOf(g.rows));
627
+ const changes = page.filter((r) => latest.get(keyOf(r)) === r.seq && r.op === "upsert").map(toEntry);
628
+ const last = page[page.length - 1];
629
+ return {
630
+ changes,
631
+ cursor: toCursor(last.seq),
632
+ hasMore: rows.length > limit,
633
+ upTo: upToCursor
634
+ };
635
+ }
636
+ return {
637
+ changes: [],
638
+ cursor,
639
+ hasMore: false,
640
+ upTo: upToCursor
641
+ };
642
+ },
536
643
  async prune(before, options = {}) {
537
644
  if (!(before instanceof Date) || Number.isNaN(before.getTime())) throw new Error("[mongokit:sync] prune(before) needs a valid Date");
538
645
  const batchSize = options.batchSize ?? 1e3;
@@ -569,20 +676,23 @@ function createChangeLogStore(models, storeOptions = {}) {
569
676
  };
570
677
  for await (const g of groups) {
571
678
  const doc = docOf(g._id);
572
- const latest = await topSeq(doc);
573
- let deletedMax;
574
- if (latest > g.oldMax) deletedMax = g.oldMax;
575
- else if (g.n > 1) deletedMax = await topSeq({
679
+ const keep = await winnerSeq(doc);
680
+ const deletedMax = await topSeq({
576
681
  ...doc,
577
682
  at: old,
578
- seq: { $lt: latest }
683
+ seq: {
684
+ $lte: g.oldMax,
685
+ $ne: keep
686
+ }
579
687
  });
580
- else continue;
581
688
  if (deletedMax <= 0) continue;
582
689
  ops.push({ deleteMany: { filter: {
583
690
  ...doc,
584
691
  at: old,
585
- seq: { $lte: deletedMax }
692
+ seq: {
693
+ $lte: deletedMax,
694
+ $ne: keep
695
+ }
586
696
  } } });
587
697
  batchMax = Math.max(batchMax, deletedMax);
588
698
  const key = JSON.stringify([doc.tenantId, doc.scope]);
@@ -1,5 +1,5 @@
1
- import { n as CreateRepositoryConfig } from "../create-repository-BOFUYj1l.mjs";
2
- import { r as Repository } from "../Repository-B6oL6OMv.mjs";
1
+ import { n as CreateRepositoryConfig } from "../create-repository-DG-gwOf0.mjs";
2
+ import { r as Repository } from "../Repository-DJuRmBNx.mjs";
3
3
  import { Connection, Model, Schema } from "mongoose";
4
4
  import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { t as createRepository } from "../create-repository-D7MQpIZo.mjs";
1
+ import { t as createRepository } from "../create-repository-C9DHHcl7.mjs";
2
2
  import { createConnection } from "mongoose";
3
3
  import { rmSync } from "node:fs";
4
4
  import { mkdir, mkdtemp, readFile, readdir, rm, stat } from "node:fs/promises";
@@ -1,4 +1,4 @@
1
- import { a as ALL_OPERATIONS, s as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-eMRYZ_2Y.mjs";
1
+ import { a as ALL_OPERATIONS, s as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-DheA7oW-.mjs";
2
2
  import { n as warn } from "./logger-hbWOlj_X.mjs";
3
3
  import { n as declareCollectionScope } from "./join-scope-C1gOtZ59.mjs";
4
4
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
@@ -1,5 +1,5 @@
1
- import { y as UserContext } from "./repository-DzVIwhU2.mjs";
2
- import { r as FieldPreset } from "./plugin-options-Bpcuz9kS.mjs";
1
+ import { y as UserContext } from "./repository-B0aFVgP2.mjs";
2
+ import { r as FieldPreset } from "./plugin-options-DrNGWV5b.mjs";
3
3
  import mongoose, { Schema } from "mongoose";
4
4
  import { CacheAdapter } from "@classytic/repo-core/cache";
5
5
  import { HttpError } from "@classytic/repo-core/errors";