@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
package/README.md CHANGED
@@ -206,11 +206,12 @@ To serialise writers on a row, the write must change it. An update that sets a f
206
206
  ### Time Bounds And Concerns (`queryDefaults`)
207
207
 
208
208
  ```ts
209
- // Boot: every command is bounded; the boot check fails without it (and without CSOT timeoutMS).
210
- await mongoose.connect(uri, { timeoutMS: 30_000 });
209
+ // Boot: every command is bounded by a server-side maxTimeMS; the boot check fails without it.
210
+ // Do not pass `timeoutMS` to mongoose.connect(): the MongoDB driver ignores it since 7.8 (CVE-2026-119179).
211
+ await mongoose.connect(uri);
211
212
  configureQueryDefaults({ maxTimeMS: 5_000, writeConcern: { w: 'majority' } });
212
213
  configureAggregateDefaults({ maxTimeMs: 15_000 });
213
- assertQueryDefaultsConfigured(mongoose.connection);
214
+ assertQueryDefaultsConfigured();
214
215
 
215
216
  // Per repository, and per call (per call always wins):
216
217
  const repo = new Repository(Order, plugins, {}, { queryDefaults: { comment: 'orders' } });
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.mjs";
2
- import { i as applyToQuery, r as applyToAggregate, u as resolveQueryOptions } from "./query-defaults-aUoKlEbh.mjs";
2
+ import { l as resolveQueryOptions, n as applyToAggregate, r as applyToQuery } from "./query-defaults-DwvWUjZU.mjs";
3
3
  import { t as createError } from "./error-DjdmPLWO.mjs";
4
4
  import { n as warn } from "./logger-hbWOlj_X.mjs";
5
5
  import { calculateSkip, calculateTotalPages, shouldWarnDeepPagination, validateLimit, validatePage } from "./pagination/utils/limits.mjs";
@@ -1,4 +1,4 @@
1
- import { D as AnyDocument, c as MongokitPageExtras, f as CursorSecret, i as CountStrategy, l as OffsetPaginationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-DAUg5zOY.mjs";
1
+ import { D as AnyDocument, c as MongokitPageExtras, f as CursorSecret, i as CountStrategy, l as OffsetPaginationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-BAM9cA4N.mjs";
2
2
  import { Model } from "mongoose";
3
3
  import { TtlMemo } from "@classytic/repo-core/cache";
4
4
  import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
@@ -1,8 +1,8 @@
1
- import { A as ObjectId, F as SortSpec$1, M as ReadPreferenceType, N as SelectSpec, c as MongokitPageExtras, i as CountStrategy, j as PopulateSpec, r as CollationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-DAUg5zOY.mjs";
2
- import { r as RetentionPurgeRequest } from "./retention-purge-DqcYvRnG.mjs";
3
- import { n as PaginationEngine } from "./PaginationEngine-D17AthKw.mjs";
4
- import { S as LookupOptions, _ as SessionOptions, b as WithTransactionOptions, c as DeleteResult, f as LookupPopulateOptions, g as ReadOptions, h as OperationOptions, i as CacheableOptions, l as FindOneAndUpdateOptions, n as AggregateOptions, o as CreateOptions, p as LookupPopulateResult, s as DeleteManyResult, t as AggregateCallOptions, u as GetByIdsOptions, v as UpdateManyResult, x as LookupBuilder, y as UpdateOptions } from "./operations-Dw9OFV0V.mjs";
5
- import { _ as RepositoryOptions, d as PluginType, f as PrioritizedHook, o as Middleware, p as RepositoryContext } from "./repository-DzVIwhU2.mjs";
1
+ import { A as ObjectId, F as SortSpec$1, M as ReadPreferenceType, N as SelectSpec, c as MongokitPageExtras, i as CountStrategy, j as PopulateSpec, r as CollationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-BAM9cA4N.mjs";
2
+ import { r as RetentionPurgeRequest } from "./retention-purge-CCd_VXUf.mjs";
3
+ import { n as PaginationEngine } from "./PaginationEngine-BWb-c47a.mjs";
4
+ import { S as LookupOptions, _ as SessionOptions, b as WithTransactionOptions, c as DeleteResult, f as LookupPopulateOptions, g as ReadOptions, h as OperationOptions, i as CacheableOptions, l as FindOneAndUpdateOptions, n as AggregateOptions, o as CreateOptions, p as LookupPopulateResult, s as DeleteManyResult, t as AggregateCallOptions, u as GetByIdsOptions, v as UpdateManyResult, x as LookupBuilder, y as UpdateOptions } from "./operations-D6XsHx4e.mjs";
5
+ import { _ as RepositoryOptions, d as PluginType, f as PrioritizedHook, o as Middleware, p as RepositoryContext } from "./repository-B0aFVgP2.mjs";
6
6
  import { AggPaginationRequest, AggRequest, AggResult, ArchiveOptions, ArchiveResult, ArchiveSink, BulkUpsertOptions, BulkUpsertResult, ChangeEvent, FilterInput, KeysetAggPaginationResult, QueryOptions, RepoCapabilities, RepositoryBase, TenantPurgeOptions, TenantPurgeResult, TenantPurgeStrategy, WatchOptions } from "@classytic/repo-core/repository";
7
7
  import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
8
8
  import { Filter } from "@classytic/repo-core/filter";
@@ -870,7 +870,7 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
870
870
  }): Promise<string>;
871
871
  /**
872
872
  * Resumable keyset batches over `_id`: each batch is ONE bounded `find` (no long-lived server
873
- * cursor, so no cursor timeout and no CSOT lifetime budget), memory is one batch, and every
873
+ * cursor, so no cursor timeout and a bound on every batch), memory is one batch, and every
874
874
  * batch carries a `checkpoint`. Pass a checkpoint back as `after` to resume: rows written
875
875
  * behind it are not revisited, rows ahead of it are reached. A checkpoint is bound to the
876
876
  * collection, the post-policy filter (tenant included) and is refused elsewhere.
@@ -1609,15 +1609,24 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
1609
1609
  * Chunked cold-storage extraction — see `StandardRepo.archiveByFilter`
1610
1610
  * for the cross-kit contract (write-before-delete, at-least-once,
1611
1611
  * duplicate-tolerant sinks). Mongokit composes the kit-agnostic
1612
- * `runChunkedArchive` orchestrator with `createMongoArchivePort`
1612
+ * `runChunkedArchive` orchestrator with `createMongoArchiveRun`
1613
1613
  * (`_id`-ordered lean reads + plugin-routed deleteMany removal). See
1614
1614
  * [actions/archive.ts](./actions/archive.ts) for the port.
1615
1615
  *
1616
+ * `retentionPurge` rides every chunk delete with an attestation of the ids the
1617
+ * sink acknowledged, so a fence built with `allowArchive` (or `allowPurge`)
1618
+ * admits it; validated before any chunk is read. Each row is deleted only if its
1619
+ * `updatedAt` (+ `versionField`) still equals the exported value; an edited row stays
1620
+ * live for the next run.
1621
+ *
1616
1622
  * Accepts Filter IR or a Mongo-shaped filter record — compiled once via
1617
1623
  * `compileFilterToMongo`, same dual-dialect rule as every other verb.
1618
1624
  */
1619
1625
  archiveByFilter(filter: Record<string, unknown> | Filter, sink: ArchiveSink<TDoc>, options?: ArchiveOptions & {
1620
1626
  session?: ClientSession;
1627
+ retentionPurge?: RetentionPurgeRequest | undefined;
1628
+ /** Rows never change: skip the unchanged-since-export pin (refused without it when the schema has no timestamps). */
1629
+ immutable?: boolean | undefined;
1621
1630
  }): Promise<ArchiveResult>;
1622
1631
  /**
1623
1632
  * Many keyed upserts in ONE command with an exact per-row outcome (repo-core `bulkUpsert` IR).
@@ -1,11 +1,11 @@
1
1
  import { i as isValidIdForType, t as getSchemaIdType } from "./id-resolution-Cqh49d9J.mjs";
2
2
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-DjdmPLWO.mjs";
3
3
  import { a as narrowToIds, i as keysetFilter, o as selectKeysetChunk, r as idChunks, t as DEFAULT_ID_CHUNK } from "./id-chunks-DH-RAZWG.mjs";
4
- import { _ as SHORTHAND_OPS, a as count, b as create, c as getByQuery, d as deleteById, f as deleteByQuery, g as LookupBuilder, h as distinct, l as getOrCreate, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, v as compileFilterToMongo, x as createMany, y as castFilterToSchema } from "./update-DcfmNbJe.mjs";
4
+ import { _ as SHORTHAND_OPS, a as count, b as create, c as getByQuery, d as deleteById, f as deleteByQuery, g as LookupBuilder, h as distinct, l as getOrCreate, n as update, o as exists, r as updateByQuery, s as getById, t as findOneAndUpdate, v as compileFilterToMongo, x as createMany, y as castFilterToSchema } from "./update-CYaJMu6j.mjs";
5
5
  import { n as warn } from "./logger-hbWOlj_X.mjs";
6
- import { i as applyToQuery, n as applyCursorTimeoutMode, r as applyToAggregate, u as resolveQueryOptions } from "./query-defaults-aUoKlEbh.mjs";
7
- import { o as withTransaction, p as resolveMongoCapabilities, s as createTxBoundRepo } from "./transaction-C2PFxfIv.mjs";
8
- import { _ as resolveCursorSecrets, d as withGroupTiebreak, f as withIdTiebreak, g as attachSignature, i as validateKeysetSort, l as assertOffsetWithinCap, n as getPrimaryField, o as encodeCursor, s as resolveCursorFilter, t as PaginationEngine, u as cursorScope, v as verifySignature } from "./PaginationEngine-C9iS0U5l.mjs";
6
+ import { l as resolveQueryOptions, n as applyToAggregate, r as applyToQuery } from "./query-defaults-DwvWUjZU.mjs";
7
+ import { o as withTransaction, p as resolveMongoCapabilities, s as createTxBoundRepo } from "./transaction-C6Oj941P.mjs";
8
+ import { _ as resolveCursorSecrets, d as withGroupTiebreak, f as withIdTiebreak, g as attachSignature, i as validateKeysetSort, l as assertOffsetWithinCap, n as getPrimaryField, o as encodeCursor, s as resolveCursorFilter, t as PaginationEngine, u as cursorScope, v as verifySignature } from "./PaginationEngine-BRlLTQQz.mjs";
9
9
  import { calculateTotalPages } from "./pagination/utils/limits.mjs";
10
10
  import { a as insertValues, c as writeKeys, d as buildSearchKeys, f as prefixSearchFilter, i as fieldValuesOf, n as SearchKeysWriteError, o as keysFromValues, r as analyzeUpdate, s as unresolvedForUpdate, t as SEARCH_KEYS_ERROR_CODES } from "./derive-Dxsc31fV.mjs";
11
11
  import { hasNearOperator, rewriteNearForCount } from "./query/primitives/geo.mjs";
@@ -23,8 +23,43 @@ const RETENTION_PURGE_ERROR_CODES = {
23
23
  DISABLED: "RETENTION_PURGE_DISABLED",
24
24
  REASON_REQUIRED: "RETENTION_PURGE_REASON_REQUIRED",
25
25
  INVALID_ACTOR: "RETENTION_PURGE_INVALID_ACTOR",
26
- HARD_ONLY: "RETENTION_PURGE_HARD_ONLY"
26
+ HARD_ONLY: "RETENTION_PURGE_HARD_ONLY",
27
+ ARCHIVE_UNATTESTED: "RETENTION_PURGE_ARCHIVE_UNATTESTED"
27
28
  };
29
+ /** Attested id sets, keyed by an object only `attestArchivedChunk` can mint. */
30
+ const ATTESTED_CHUNKS = /* @__PURE__ */ new WeakMap();
31
+ /**
32
+ * Mint the attestation for one archive chunk the sink has ACKNOWLEDGED. Internal to
33
+ * mongokit (not exported from the package entry): the archive port is its only caller.
34
+ */
35
+ function attestArchivedChunk(ids) {
36
+ const token = Object.freeze({});
37
+ ATTESTED_CHUNKS.set(token, new Set(ids.map(String)));
38
+ return token;
39
+ }
40
+ /** Every `_id: { $in: [...] }` list in the query, through nested `$and`. */
41
+ function idListsIn(query) {
42
+ if (!query || typeof query !== "object") return [];
43
+ const q = query;
44
+ const lists = [];
45
+ const idPredicate = q._id;
46
+ if (idPredicate && typeof idPredicate === "object" && Array.isArray(idPredicate.$in)) lists.push(idPredicate.$in);
47
+ if (Array.isArray(q.$and)) for (const arm of q.$and) lists.push(...idListsIn(arm));
48
+ return lists;
49
+ }
50
+ /**
51
+ * Whether the context carries an attestation, and whether it covers the delete. A delete
52
+ * is covered only when some `_id.$in` it ANDs in is non-empty and wholly attested — an
53
+ * extra predicate can only narrow it further.
54
+ */
55
+ function archiveAttestationOf(ctx) {
56
+ const token = ctx.archiveAttestation;
57
+ if (token === void 0) return "none";
58
+ const attested = typeof token === "object" && token ? ATTESTED_CHUNKS.get(token) : void 0;
59
+ if (!attested) return "invalid";
60
+ const query = ctx.query;
61
+ return idListsIn(query).some((ids) => ids.length > 0 && ids.every((id) => attested.has(String(id)))) ? "valid" : "invalid";
62
+ }
28
63
  /** Validates the request's shape (reason, actor). Throws 400 on a malformed one. */
29
64
  function assertRetentionPurgeShape(raw, op) {
30
65
  const req = raw;
@@ -50,7 +85,15 @@ function assertRetentionPurgeShape(raw, op) {
50
85
  function readRetentionPurge(ctx, op, guard) {
51
86
  const raw = ctx.retentionPurge;
52
87
  if (raw === void 0 || raw === null || raw === false) return void 0;
53
- if (!guard.allowPurge) throw createError(403, `'${op}' with retentionPurge is not enabled on this repository: construct ${guard.plugin} with allowPurge: true for the retention path.`, {
88
+ const attestation = archiveAttestationOf(ctx);
89
+ if (attestation === "invalid") throw createError(403, `'${op}' carries an archive attestation that does not cover the rows it deletes: an archive delete may remove only the chunk its sink acknowledged.`, {
90
+ code: RETENTION_PURGE_ERROR_CODES.ARCHIVE_UNATTESTED,
91
+ meta: {
92
+ operation: op,
93
+ plugin: guard.plugin
94
+ }
95
+ });
96
+ if (!guard.allowPurge && !(guard.allowArchive && attestation === "valid")) throw createError(403, `'${op}' with retentionPurge is not enabled on this repository: construct ${guard.plugin} with allowPurge: true for the retention path` + (attestation === "valid" ? ", or allowArchive: true for archiveByFilter." : "."), {
54
97
  code: RETENTION_PURGE_ERROR_CODES.DISABLED,
55
98
  meta: {
56
99
  operation: op,
@@ -76,6 +119,7 @@ function retentionPurgeEvent(operation, context, result) {
76
119
  id: operation === "delete" ? ctx.id : void 0,
77
120
  filter: operation === "deleteMany" ? ctx.query : void 0,
78
121
  deletedCount: operation === "delete" ? result ? 1 : 0 : result?.deletedCount ?? 0,
122
+ archived: archiveAttestationOf(context) === "valid",
79
123
  session: context.session,
80
124
  context
81
125
  };
@@ -117,7 +161,7 @@ function createMongoPurgePort(repo, field, value, session, portOptions = {}) {
117
161
  * uses, so a row that leaves the matching set between the id select and the
118
162
  * write is never touched.
119
163
  *
120
- * Mirrors `createMongoArchivePort`, which likewise binds a pre-compiled
164
+ * Mirrors `createMongoArchiveRun`, which likewise binds a pre-compiled
121
165
  * filter rather than a `field/value` pair.
122
166
  */
123
167
  function createMongoPurgePortFromFilter(repo, filter, session, portOptions = {}) {
@@ -1459,22 +1503,78 @@ function buildKeysetPredicate(sort, cursor) {
1459
1503
  return { $match: branches.length === 1 ? branches[0] : { $or: branches } };
1460
1504
  }
1461
1505
  // src/actions/archive.ts
1506
+ const idOf = (doc) => doc._id;
1507
+ const valueAt = (doc, path) => path.split(".").reduce((acc, key) => acc?.[key], doc);
1462
1508
  /**
1463
- * Build an `ArchivePort` bound to a repository + a pre-compiled Mongo
1464
- * filter (the repository compiles Filter IR before calling this).
1509
+ * The fields that prove a row is unchanged since export: the `updatedAt` mongoose
1510
+ * timestamps maintain on every write path, plus the repository's `versionField` when
1511
+ * the schema has it. Empty only for a collection declared `immutable`. A collection with
1512
+ * no maintained `updatedAt` is refused before the run reads anything.
1465
1513
  */
1466
- function createMongoArchivePort(repo, filter, session) {
1514
+ function archiveChangeTokens(model, versionField, immutable) {
1515
+ if (immutable) return [];
1516
+ const ts = model.timestamps;
1517
+ const updatedAt = ts === true ? "updatedAt" : ts && typeof ts === "object" && ts.updatedAt !== false ? typeof ts.updatedAt === "string" ? ts.updatedAt : "updatedAt" : void 0;
1518
+ if (!updatedAt) throw createError(400, `archiveByFilter on '${model.modelName}': the schema maintains no updatedAt (timestamps), so a row edited between export and delete could not be told apart and would be deleted with its edit. Enable timestamps, or pass immutable: true for a collection whose rows never change.`, {
1519
+ code: "ARCHIVE_NO_CHANGE_TOKEN",
1520
+ meta: { model: model.modelName }
1521
+ });
1522
+ return model.hasVersionPath ? [updatedAt, versionField] : [updatedAt];
1523
+ }
1524
+ /** Each exported row pinned to its change tokens: an edited row no longer matches. */
1525
+ function unchangedSince(docs, tokens) {
1526
+ return { $or: docs.map((doc) => {
1527
+ const pin = { _id: idOf(doc) };
1528
+ for (const token of tokens) {
1529
+ const value = valueAt(doc, token);
1530
+ pin[token] = value === void 0 ? { $exists: false } : value;
1531
+ }
1532
+ return pin;
1533
+ }) };
1534
+ }
1535
+ /**
1536
+ * Build the sink + port pair for ONE `archiveByFilter` call. The returned sink
1537
+ * forwards to the caller's and records what it acknowledged; the port's delete
1538
+ * removes only acknowledged rows still unchanged since export, and throws on any other.
1539
+ * Progression is a keyset over `_id`, so a row left live is not revisited by this run.
1540
+ */
1541
+ function createMongoArchiveRun(repo, filter, sink, session, retentionPurge, immutable) {
1542
+ const tokens = archiveChangeTokens({
1543
+ modelName: repo.Model.modelName,
1544
+ timestamps: repo.Model.schema.get("timestamps"),
1545
+ hasVersionPath: Boolean(repo.Model.schema.path(repo.versionField))
1546
+ }, repo.versionField, immutable);
1547
+ let acknowledged = /* @__PURE__ */ new Set();
1548
+ let cursor = null;
1467
1549
  return {
1468
- async readChunk(limit) {
1469
- return selectKeysetChunk(repo.Model, filter, limit, { session });
1550
+ sink: {
1551
+ async write(docs) {
1552
+ await sink.write(docs);
1553
+ acknowledged = new Set(docs.map((doc) => String(idOf(doc))));
1554
+ },
1555
+ ...sink.flush ? { flush: () => sink.flush?.() ?? Promise.resolve() } : {}
1470
1556
  },
1471
- async deleteChunk(docs) {
1472
- const ids = docs.map((doc) => doc._id);
1473
- return (await repo.deleteMany(narrowToIds(filter, ids), {
1474
- session,
1475
- bypassTenant: true,
1476
- mode: "hard"
1477
- })).deletedCount;
1557
+ port: {
1558
+ async readChunk(limit) {
1559
+ return selectKeysetChunk(repo.Model, keysetFilter(filter, cursor), limit, { session });
1560
+ },
1561
+ async deleteChunk(docs) {
1562
+ const ids = docs.map(idOf);
1563
+ const unacknowledged = ids.filter((id) => !acknowledged.has(String(id)));
1564
+ if (unacknowledged.length > 0) throw new Error(`archiveByFilter: refusing to delete ${unacknowledged.length} row(s) the sink did not acknowledge`);
1565
+ const narrowed = narrowToIds(filter, ids);
1566
+ const result = await repo.deleteMany(tokens.length > 0 ? { $and: [narrowed, unchangedSince(docs, tokens)] } : narrowed, {
1567
+ session,
1568
+ bypassTenant: true,
1569
+ mode: "hard",
1570
+ ...retentionPurge ? {
1571
+ retentionPurge,
1572
+ archiveAttestation: attestArchivedChunk(ids)
1573
+ } : {}
1574
+ });
1575
+ if (ids.length > 0) cursor = ids[ids.length - 1];
1576
+ return result.deletedCount;
1577
+ }
1478
1578
  }
1479
1579
  };
1480
1580
  }
@@ -1619,7 +1719,7 @@ async function executeUpserts(model, ops, options) {
1619
1719
  verboseResults: true,
1620
1720
  ...options.session ? { session: options.session } : {},
1621
1721
  ...options.writeConcern ? { writeConcern: options.writeConcern } : {},
1622
- ...options.timeoutMS !== void 0 ? { timeoutMS: options.timeoutMS } : {}
1722
+ ...options.maxTimeMS !== void 0 ? { maxTimeMS: options.maxTimeMS } : {}
1623
1723
  });
1624
1724
  } catch (err) {
1625
1725
  throwIfUnsupported(err);
@@ -3085,7 +3185,7 @@ var Repository = class extends RepositoryBase {
3085
3185
  }
3086
3186
  /**
3087
3187
  * Resumable keyset batches over `_id`: each batch is ONE bounded `find` (no long-lived server
3088
- * cursor, so no cursor timeout and no CSOT lifetime budget), memory is one batch, and every
3188
+ * cursor, so no cursor timeout and a bound on every batch), memory is one batch, and every
3089
3189
  * batch carries a `checkpoint`. Pass a checkpoint back as `after` to resume: rows written
3090
3190
  * behind it are not revisited, rows ahead of it are reached. A checkpoint is bound to the
3091
3191
  * collection, the post-policy filter (tenant included) and is refused elsewhere.
@@ -3164,7 +3264,6 @@ var Repository = class extends RepositoryBase {
3164
3264
  if (options.session) query.session(options.session);
3165
3265
  const cursorOpts = this._opts("read", context);
3166
3266
  applyToQuery(query, cursorOpts);
3167
- applyCursorTimeoutMode(query, cursorOpts, this.Model.db);
3168
3267
  const stream = query.cursor();
3169
3268
  let yieldedCount = 0;
3170
3269
  try {
@@ -4509,17 +4608,24 @@ var Repository = class extends RepositoryBase {
4509
4608
  * Chunked cold-storage extraction — see `StandardRepo.archiveByFilter`
4510
4609
  * for the cross-kit contract (write-before-delete, at-least-once,
4511
4610
  * duplicate-tolerant sinks). Mongokit composes the kit-agnostic
4512
- * `runChunkedArchive` orchestrator with `createMongoArchivePort`
4611
+ * `runChunkedArchive` orchestrator with `createMongoArchiveRun`
4513
4612
  * (`_id`-ordered lean reads + plugin-routed deleteMany removal). See
4514
4613
  * [actions/archive.ts](./actions/archive.ts) for the port.
4515
4614
  *
4615
+ * `retentionPurge` rides every chunk delete with an attestation of the ids the
4616
+ * sink acknowledged, so a fence built with `allowArchive` (or `allowPurge`)
4617
+ * admits it; validated before any chunk is read. Each row is deleted only if its
4618
+ * `updatedAt` (+ `versionField`) still equals the exported value; an edited row stays
4619
+ * live for the next run.
4620
+ *
4516
4621
  * Accepts Filter IR or a Mongo-shaped filter record — compiled once via
4517
4622
  * `compileFilterToMongo`, same dual-dialect rule as every other verb.
4518
4623
  */
4519
4624
  async archiveByFilter(filter, sink, options = {}) {
4520
4625
  const compiled = compileFilterToMongo(filter);
4521
- const port = createMongoArchivePort(this, compiled, options.session);
4522
- return runChunkedArchive(options, sink, port);
4626
+ const retentionPurge = options.retentionPurge === void 0 ? void 0 : assertRetentionPurgeShape(options.retentionPurge, "archiveByFilter");
4627
+ const run = createMongoArchiveRun(this, compiled, sink, options.session, retentionPurge, options.immutable === true);
4628
+ return runChunkedArchive(options, run.sink, run.port);
4523
4629
  }
4524
4630
  /**
4525
4631
  * Many keyed upserts in ONE command with an exact per-row outcome (repo-core `bulkUpsert` IR).
@@ -4580,7 +4686,7 @@ var Repository = class extends RepositoryBase {
4580
4686
  ordered,
4581
4687
  session: context.session,
4582
4688
  writeConcern: writeOpts.writeConcern,
4583
- timeoutMS: writeOpts.maxTimeMS
4689
+ maxTimeMS: writeOpts.maxTimeMS
4584
4690
  });
4585
4691
  return summarizeBulkUpsert([...refused, ...sent].sort((a, b) => a.index - b.index));
4586
4692
  });
@@ -4940,9 +5046,9 @@ var Repository = class extends RepositoryBase {
4940
5046
  const appendLookupStages$2 = (pipeline) => appendLookupStages(pipeline, lookups, projection, isKeyset && sort ? this._parseSort(sort) : void 0);
4941
5047
  if (isKeyset && sort) {
4942
5048
  const parsedSort = this._parseSort(sort);
4943
- const { validateKeysetSort } = await import("./PaginationEngine-C9iS0U5l.mjs").then((n) => n.r);
4944
- const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-C9iS0U5l.mjs").then((n) => n.a);
4945
- const { getPrimaryField } = await import("./PaginationEngine-C9iS0U5l.mjs").then((n) => n.r);
5049
+ const { validateKeysetSort } = await import("./PaginationEngine-BRlLTQQz.mjs").then((n) => n.r);
5050
+ const { encodeCursor, resolveCursorFilter } = await import("./PaginationEngine-BRlLTQQz.mjs").then((n) => n.a);
5051
+ const { getPrimaryField } = await import("./PaginationEngine-BRlLTQQz.mjs").then((n) => n.r);
4946
5052
  const normalizedSort = validateKeysetSort(parsedSort, this._pagination.config.strictKeysetSortFields);
4947
5053
  const cursorVersion = this._pagination.config.cursorVersion ?? 1;
4948
5054
  const minCursorVersion = this._pagination.config.minCursorVersion ?? 1;
@@ -1,2 +1,2 @@
1
- import { c as create_d_exports, i as delete_d_exports, n as update_d_exports, r as read_d_exports, u as aggregate_d_exports } from "../index-8QUEsOFI.mjs";
1
+ import { c as create_d_exports, i as delete_d_exports, n as update_d_exports, r as read_d_exports, u as aggregate_d_exports } from "../index-5mhQ63_B.mjs";
2
2
  export { aggregate_d_exports as aggregate, create_d_exports as create, delete_d_exports as deleteActions, read_d_exports as read, update_d_exports as update };
@@ -1,2 +1,2 @@
1
- export { m as aggregate, S as create, p as deleteActions, u as read, i as update } from "../update-DcfmNbJe.mjs";
2
- import "../actions-DExyXMTy.mjs";
1
+ export { m as aggregate, S as create, p as deleteActions, u as read, i as update } from "../update-CYaJMu6j.mjs";
2
+ import "../actions-DKQx9ToJ.mjs";
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.mjs";
2
- import { S as create_exports, i as update_exports, m as aggregate_exports, p as delete_exports, u as read_exports } from "./update-DcfmNbJe.mjs";
2
+ import { S as create_exports, i as update_exports, m as aggregate_exports, p as delete_exports, u as read_exports } from "./update-CYaJMu6j.mjs";
3
3
  // src/actions/index.ts
4
4
  var actions_exports = /* @__PURE__ */ __exportAll({
5
5
  aggregate: () => aggregate_exports,
@@ -1,4 +1,4 @@
1
- import { l as Plugin } from "../repository-DzVIwhU2.mjs";
1
+ import { l as Plugin } from "../repository-B0aFVgP2.mjs";
2
2
  import { PipelineStage } from "mongoose";
3
3
  /** Supported similarity metrics for vector search */
4
4
  type SimilarityMetric = 'cosine' | 'euclidean' | 'dotProduct';
@@ -53,10 +53,13 @@ interface BATableConfig {
53
53
  * log. Measured on a real deployment: `GET /me`, `GET /users/:id` and
54
54
  * `GET /branches/code/:code` all 404'd for rows that existed.
55
55
  *
56
- * There is no safe default that covers both, which is why this is an explicit
56
+ * - `'uuid'` - the host set `advanced.database.generateId: 'uuid'`. BA then stores real BSON UUIDs
57
+ * (not strings), and the schema needs the UUID SchemaType so a string id is cast to one.
58
+ *
59
+ * There is no safe default that covers all three, which is why this is an explicit
57
60
  * option rather than a guess: read `typeof doc._id` from a live row.
58
61
  */
59
- export type BetterAuthIdType = 'objectid' | 'string';
62
+ export type BetterAuthIdType = 'objectid' | 'string' | 'uuid';
60
63
  export interface RegisterBetterAuthStubsOptions {
61
64
  /** Plugin sets to include. `core` is always implied. */
62
65
  plugins?: BetterAuthPluginKey$1[];
@@ -1,5 +1,5 @@
1
1
  import { n as createMongooseAdapter } from "../adapter-Qzgc3ZD_.mjs";
2
- import { n as Repository } from "../Repository-eMRYZ_2Y.mjs";
2
+ import { n as Repository } from "../Repository-DheA7oW-.mjs";
3
3
  import { asReadOnlyRepo } from "@classytic/repo-core/repository";
4
4
  import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
5
5
  // src/better-auth/index.ts
@@ -42,7 +42,9 @@ function missingIndexesOn(existing, declared) {
42
42
  }
43
43
  /** The `_id` path for a stub/overlay schema, or `{}` to inherit mongoose's ObjectId default. */
44
44
  function idPath(idType) {
45
- return idType === "string" ? { _id: { type: String } } : {};
45
+ if (idType === "string") return { _id: { type: String } };
46
+ if (idType === "uuid") return { _id: { type: "UUID" } };
47
+ return {};
46
48
  }
47
49
  /**
48
50
  * Register stub Mongoose models for Better Auth's collections so that
@@ -331,24 +333,12 @@ const BA_INDEXES_BY_PLUGIN = {
331
333
  keys: { userId: 1 },
332
334
  name: "ba_twoFactor_userId"
333
335
  }],
334
- deviceAuthorization: [
335
- {
336
- collection: "deviceCode",
337
- keys: { deviceCode: 1 },
338
- name: "ba_deviceCode_code"
339
- },
340
- {
341
- collection: "deviceCode",
342
- keys: { userCode: 1 },
343
- name: "ba_deviceCode_userCode"
344
- },
345
- {
346
- collection: "deviceCode",
347
- keys: { expiresAt: 1 },
348
- name: "ttl_deviceCode_expiresAt",
349
- ttl: true
350
- }
351
- ]
336
+ deviceAuthorization: [{
337
+ collection: "deviceCode",
338
+ keys: { expiresAt: 1 },
339
+ name: "ttl_deviceCode_expiresAt",
340
+ ttl: true
341
+ }]
352
342
  };
353
343
  /**
354
344
  * Create every index Better Auth's queries need. Idempotent, never throws.
@@ -1,6 +1,6 @@
1
- import { S as auditTrailPlugin, b as cachePlugin, d as customIdPlugin, i as multiTenantPlugin, n as softDeletePlugin, t as timestampPlugin, w as auditLogPlugin } from "./timestamp.plugin-Bz_zL9PX.mjs";
2
- import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-CGAepsBU.mjs";
3
- import { n as Repository } from "./Repository-eMRYZ_2Y.mjs";
1
+ import { S as auditTrailPlugin, b as cachePlugin, d as customIdPlugin, i as multiTenantPlugin, n as softDeletePlugin, t as timestampPlugin, w as auditLogPlugin } from "./timestamp.plugin-B-IJrlYb.mjs";
2
+ import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-BeJfQBJV.mjs";
3
+ import { n as Repository } from "./Repository-DheA7oW-.mjs";
4
4
  // src/create-repository.ts
5
5
  function isLoggerAudit(value) {
6
6
  const v = value;
@@ -1,8 +1,8 @@
1
- import { L as AuditTrailOptions, i as CustomIdOptions, k as RepositoryCachePluginOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-DfeDMiI8.mjs";
2
- import { r as Repository } from "./Repository-B6oL6OMv.mjs";
3
- import { u as PaginationConfig } from "./pagination-DAUg5zOY.mjs";
4
- import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-Bpcuz9kS.mjs";
5
- import { _ as RepositoryOptions, d as PluginType } from "./repository-DzVIwhU2.mjs";
1
+ import { L as AuditTrailOptions, i as CustomIdOptions, k as RepositoryCachePluginOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-BTPtCLNK.mjs";
2
+ import { r as Repository } from "./Repository-DJuRmBNx.mjs";
3
+ import { u as PaginationConfig } from "./pagination-BAM9cA4N.mjs";
4
+ import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-DrNGWV5b.mjs";
5
+ import { _ as RepositoryOptions, d as PluginType } from "./repository-B0aFVgP2.mjs";
6
6
  import { Model } from "mongoose";
7
7
  /**
8
8
  * Audit config — either a `Logger` (routes to `auditLogPlugin`, which
@@ -1,5 +1,5 @@
1
- import { S as LookupOptions, c as DeleteResult, d as GroupResult, h as OperationOptions, l as FindOneAndUpdateOptions, m as MinMaxResult, o as CreateOptions, v as UpdateManyResult, y as UpdateOptions } from "./operations-Dw9OFV0V.mjs";
2
- import { A as ObjectId, D as AnyDocument, F as SortSpec, N as SelectSpec, j as PopulateSpec, y as ResolvedQueryOptions } from "./pagination-DAUg5zOY.mjs";
1
+ import { S as LookupOptions, c as DeleteResult, d as GroupResult, h as OperationOptions, l as FindOneAndUpdateOptions, m as MinMaxResult, o as CreateOptions, v as UpdateManyResult, y as UpdateOptions } from "./operations-D6XsHx4e.mjs";
2
+ import { A as ObjectId, D as AnyDocument, F as SortSpec, N as SelectSpec, j as PopulateSpec, y as ResolvedQueryOptions } from "./pagination-BAM9cA4N.mjs";
3
3
  import { p as UpdateWithValidationResult } from "./type-utils-CKvuewAL.mjs";
4
4
  import { Model, PipelineStage } from "mongoose";
5
5
  declare namespace aggregate_d_exports {
package/dist/index.d.mts CHANGED
@@ -1,19 +1,19 @@
1
- import { a as CreateManyFailure, l as isCreateManyPartialError, o as CreateManyPartial, s as CreateManyPartialError, t as index_d_exports } from "./index-8QUEsOFI.mjs";
2
- import { a as AggregationBuilder, c as ProjectionSpec, d as VectorSearchOptions, f as RetentionTenantPurgeOptions, i as TransitionMachine, l as SortOrder, n as HOOK_PRIORITY, o as AggregationPlan, r as Repository, s as GroupSpec, t as GetAllParams, u as SortSpec } from "./Repository-B6oL6OMv.mjs";
3
- import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-BOFUYj1l.mjs";
4
- import { C as RepositoryPluginName, S as MongoKitPluginName, _ as RepositoryOptions, a as HookMode, b as WithPlugins, c as MinimalRepoView, d as PluginType, f as PrioritizedHook, g as RepositoryOperation, h as RepositoryInstance, i as EventPhase, l as Plugin, m as RepositoryEvent, n as EventHandlers, o as Middleware, p as RepositoryContext, r as EventPayload, s as MiddlewareContext, t as AllPluginMethods, u as PluginFunction, v as RepositorySearchMode, x as MONGOKIT_PLUGIN_NAMES, y as UserContext } from "./repository-DzVIwhU2.mjs";
5
- import { A as ObjectId, C as configureQueryDefaults, D as AnyDocument, E as resolveQueryOptions, F as SortSpec$1, M as ReadPreferenceType, N as SelectSpec, O as AnyModel, P as SortDirection, S as configureAggregateDefaults, T as resetQueryDefaults, _ as QueryDefaults, a as CursorPayload, b as WriteConcernSpec, c as MongokitPageExtras, d as ValueType, f as CursorSecret, g as QUERY_DEFAULTS_ERROR_CODES, h as PerCallQueryOptions, i as CountStrategy, j as PopulateSpec, l as OffsetPaginationOptions, m as CommandKind, n as BasePaginationOptions, o as DecodedCursor, p as AggregateDefaults, r as CollationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig, v as ReadConcernLevel, w as getQueryDefaults, x as assertQueryDefaultsConfigured, y as ResolvedQueryOptions } from "./pagination-DAUg5zOY.mjs";
6
- import { S as LookupOptions, _ as SessionOptions, a as ConvenientTransactionOptions, b as WithTransactionOptions, c as DeleteResult, d as GroupResult, f as LookupPopulateOptions, g as ReadOptions, h as OperationOptions, i as CacheableOptions, l as FindOneAndUpdateOptions, m as MinMaxResult, n as AggregateOptions, o as CreateOptions, p as LookupPopulateResult, r as CacheOperationOptions, t as AggregateCallOptions, u as GetByIdsOptions, v as UpdateManyResult, x as LookupBuilder, y as UpdateOptions } from "./operations-Dw9OFV0V.mjs";
7
- import { n as PaginationEngine, t as CountMemoKey } from "./PaginationEngine-D17AthKw.mjs";
8
- import { A as ImmutablePurgeEvent, B as elasticSearchPlugin, 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, U as BulkWriteOptions, V as cascadePlugin, W as BulkWriteResult, 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";
9
- 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, S as periodCounterKey, T as sequentialId, V as auditTrailPlugin, _ as customIdPlugin, a as DateSequentialIdOptions, b as getNextPeriodSequence, c as PeriodSequenceOptions, d as SequenceCounter, f as SequenceCounterOptions, g as createSequenceCounter, h as civilPeriodKey, i as CustomIdOptions, j as AuditEntry, k as RepositoryCachePluginOptions, l as PeriodSequenceResult, m as SequentialIdOptions, n as adminBypass, o as IdGenerator, p as SequencePeriod, r as multiTenantPlugin, s as PeriodSequenceKeyOptions, t as MultiTenantOptions, u as PrefixedIdOptions, v as dateSequentialId, w as readSequence, x as getNextSequence, y as ensureSequenceAtLeast, z as AuditTrailQueryOptions } from "./multi-tenant.plugin-DfeDMiI8.mjs";
10
- import { n as RetentionPurgeEvent, r as RetentionPurgeRequest, t as RETENTION_PURGE_ERROR_CODES } from "./retention-purge-DqcYvRnG.mjs";
1
+ import { a as CreateManyFailure, l as isCreateManyPartialError, o as CreateManyPartial, s as CreateManyPartialError, t as index_d_exports } from "./index-5mhQ63_B.mjs";
2
+ import { a as AggregationBuilder, c as ProjectionSpec, d as VectorSearchOptions, f as RetentionTenantPurgeOptions, i as TransitionMachine, l as SortOrder, n as HOOK_PRIORITY, o as AggregationPlan, r as Repository, s as GroupSpec, t as GetAllParams, u as SortSpec } from "./Repository-DJuRmBNx.mjs";
3
+ import { n as CreateRepositoryConfig, r as createRepository, t as AuditConfig } from "./create-repository-DG-gwOf0.mjs";
4
+ import { C as RepositoryPluginName, S as MongoKitPluginName, _ as RepositoryOptions, a as HookMode, b as WithPlugins, c as MinimalRepoView, d as PluginType, f as PrioritizedHook, g as RepositoryOperation, h as RepositoryInstance, i as EventPhase, l as Plugin, m as RepositoryEvent, n as EventHandlers, o as Middleware, p as RepositoryContext, r as EventPayload, s as MiddlewareContext, t as AllPluginMethods, u as PluginFunction, v as RepositorySearchMode, x as MONGOKIT_PLUGIN_NAMES, y as UserContext } from "./repository-B0aFVgP2.mjs";
5
+ import { A as ObjectId, C as configureQueryDefaults, D as AnyDocument, E as resolveQueryOptions, F as SortSpec$1, M as ReadPreferenceType, N as SelectSpec, O as AnyModel, P as SortDirection, S as configureAggregateDefaults, T as resetQueryDefaults, _ as QueryDefaults, a as CursorPayload, b as WriteConcernSpec, c as MongokitPageExtras, d as ValueType, f as CursorSecret, g as QUERY_DEFAULTS_ERROR_CODES, h as PerCallQueryOptions, i as CountStrategy, j as PopulateSpec, l as OffsetPaginationOptions, m as CommandKind, n as BasePaginationOptions, o as DecodedCursor, p as AggregateDefaults, r as CollationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig, v as ReadConcernLevel, w as getQueryDefaults, x as assertQueryDefaultsConfigured, y as ResolvedQueryOptions } from "./pagination-BAM9cA4N.mjs";
6
+ import { S as LookupOptions, _ as SessionOptions, a as ConvenientTransactionOptions, b as WithTransactionOptions, c as DeleteResult, d as GroupResult, f as LookupPopulateOptions, g as ReadOptions, h as OperationOptions, i as CacheableOptions, l as FindOneAndUpdateOptions, m as MinMaxResult, n as AggregateOptions, o as CreateOptions, p as LookupPopulateResult, r as CacheOperationOptions, t as AggregateCallOptions, u as GetByIdsOptions, v as UpdateManyResult, x as LookupBuilder, y as UpdateOptions } from "./operations-D6XsHx4e.mjs";
7
+ import { n as PaginationEngine, t as CountMemoKey } from "./PaginationEngine-BWb-c47a.mjs";
8
+ import { A as ImmutablePurgeEvent, B as elasticSearchPlugin, 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, U as BulkWriteOptions, V as cascadePlugin, W as BulkWriteResult, 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";
9
+ 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, S as periodCounterKey, T as sequentialId, V as auditTrailPlugin, _ as customIdPlugin, a as DateSequentialIdOptions, b as getNextPeriodSequence, c as PeriodSequenceOptions, d as SequenceCounter, f as SequenceCounterOptions, g as createSequenceCounter, h as civilPeriodKey, i as CustomIdOptions, j as AuditEntry, k as RepositoryCachePluginOptions, l as PeriodSequenceResult, m as SequentialIdOptions, n as adminBypass, o as IdGenerator, p as SequencePeriod, r as multiTenantPlugin, s as PeriodSequenceKeyOptions, t as MultiTenantOptions, u as PrefixedIdOptions, v as dateSequentialId, w as readSequence, x as getNextSequence, y as ensureSequenceAtLeast, z as AuditTrailQueryOptions } from "./multi-tenant.plugin-BTPtCLNK.mjs";
10
+ import { n as RetentionPurgeEvent, r as RetentionPurgeRequest, t as RETENTION_PURGE_ERROR_CODES } from "./retention-purge-CCd_VXUf.mjs";
11
11
  import { n as SchemaPathsLike, t as FieldType$1 } from "./coercion-COp9DRNw.mjs";
12
12
  import { n as SchemaIndexes } from "./indexes-FfyOuhDK.mjs";
13
- import { a as SoftDeleteFilterMode, c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, n as CascadeRelation, o as SoftDeleteOptions, r as FieldPreset, s as SoftDeleteRepository, t as CascadeOptions } from "./plugin-options-Bpcuz9kS.mjs";
13
+ import { a as SoftDeleteFilterMode, c as ValidationChainOptions, i as Logger, l as ValidatorDefinition, n as CascadeRelation, o as SoftDeleteOptions, r as FieldPreset, s as SoftDeleteRepository, t as CascadeOptions } from "./plugin-options-DrNGWV5b.mjs";
14
14
  import { a as InferRawDoc, c as NonNullableFields, d as Strict, f as UpdatePatch, i as InferDocument, l as PartialBy, n as DeepPartial, o as KeysOfType, p as UpdateWithValidationResult, r as DocField, s as MongoOperatorUpdate, t as CreateInput, u as RequiredBy } from "./type-utils-CKvuewAL.mjs";
15
15
  import { a as prefixSearchFilter, c as SearchMatchRange, d as tokenizeSearchText, f as SEARCH_KEYS_ERROR_CODES, i as buildSearchKeys, l as findSearchMatches, n as MAX_PREFIX_SEARCH_TERMS, o as searchKeysIndex, p as SearchKeysWriteError, r as PrefixSearchFilterOptions, s as searchKeysPath, t as DEFAULT_SEARCH_KEYS_FIELD, u as normalizeSearchText } from "./index-ehGZLbrd.mjs";
16
- import { S as parseDuplicateKeyError, _ as getMongooseProjection, a as createMemoryCache, b as createError, g as getFieldsForUser, h as filterResponseData, i as buildCrudSchemasFromMongooseSchema, m as createFieldPreset, n as CrudSchemasFramework, o as LoggerConfig, r as buildCrudSchemasFromModel, s as configureLogger, t as toPlain, u as idVariants, v as CreateErrorOptions, x as isDuplicateKeyError, y as ParseDuplicateKeyOptions } from "./to-plain-PO5r1Qoy.mjs";
16
+ import { S as parseDuplicateKeyError, _ as getMongooseProjection, a as createMemoryCache, b as createError, g as getFieldsForUser, h as filterResponseData, i as buildCrudSchemasFromMongooseSchema, m as createFieldPreset, n as CrudSchemasFramework, o as LoggerConfig, r as buildCrudSchemasFromModel, s as configureLogger, t as toPlain, u as idVariants, v as CreateErrorOptions, x as isDuplicateKeyError, y as ParseDuplicateKeyOptions } from "./to-plain-Do-8jTKC.mjs";
17
17
  import { RepoCapabilities } from "@classytic/repo-core/repository";
18
18
  import { ClientSession, Connection, Model, PipelineStage, Schema } from "mongoose";
19
19
  import { HttpError } from "@classytic/repo-core/errors";
@@ -789,12 +789,13 @@ export declare const TRANSACTION_ERROR_CODES: {
789
789
  };
790
790
  /**
791
791
  * The driver's convenient-transaction algorithm (start → callback → commit, label-based retry,
792
- * jittered backoff, a deadline), run here so the driver never attaches a session `timeoutContext`:
793
- * with one, every legacy bulk write (`insertMany`, `bulkWrite`) in the transaction is refused once
794
- * the client carries `timeoutMS` (driver <= 7.7: the bulk path re-resolves the inherited client
795
- * `timeoutMS` as a per-op one). Each operation keeps its own per-op CSOT budget instead.
796
- * Deadline: `transactionOptions.timeoutMS`, else the client's `timeoutMS`, else 120 s (as the driver).
797
- * Pinned by `tests/integration/csot-transaction-matrix.test.ts`.
792
+ * jittered backoff, a deadline), run here and nowhere else: the driver's own `timeoutMS` / CSOT is a
793
+ * no-op since 7.8, so the budget is mongokit's. `transactionOptions.timeoutMS` is that budget, enforced
794
+ * by mongokit alone: every mongokit operation in the transaction is sent with `maxTimeMS` = what
795
+ * remains of it, the commit is bounded by `maxCommitTimeMS`, and the retry loop stops at the deadline
796
+ * (a spent budget before the commit aborts, so the outcome is known). Without it the deadline is 120 s
797
+ * (as the driver) and operations carry only their own bound. The client's `timeoutMS` is not read.
798
+ * Pinned by `tests/integration/transaction-budget-matrix.test.ts`.
798
799
  */
799
800
  export declare function runTransaction<T>(session: ClientSession, callback: (session: ClientSession) => Promise<T>, transactionOptions?: ConvenientTransactionOptions): Promise<T>;
800
801
  /**
package/dist/index.mjs CHANGED
@@ -1,21 +1,21 @@
1
1
  import { n as idVariants } from "./id-resolution-Cqh49d9J.mjs";
2
- import { a as ALL_OPERATIONS, c as READ_OPERATIONS, d as RETENTION_PURGE_ERROR_CODES, i as searchKeysPlugin, l as operationsByPolicyKey, n as Repository, o as MUTATING_OPERATIONS, r as AggregationBuilder, s as OP_REGISTRY, t as HOOK_PRIORITY, u as createMongoPurgePort } from "./Repository-eMRYZ_2Y.mjs";
3
- import { C as isCreateManyPartialError, g as LookupBuilder, v as compileFilterToMongo } from "./update-DcfmNbJe.mjs";
4
- import { t as actions_exports } from "./actions-DExyXMTy.mjs";
5
- import { a as supportsTransactions, c as MONGOKIT_CAPABILITIES, d as probeMongoCapabilities, f as resetCapabilityProbeCache, h as transactionResolutionOf, i as runTransaction, l as capabilitiesForSupport, m as resolveTransactionSupport, n as batchTransaction, o as withTransaction, p as resolveMongoCapabilities, r as isTransactionUnsupported, t as TRANSACTION_ERROR_CODES, u as classifyHelloReply } from "./transaction-C2PFxfIv.mjs";
6
- import { t as createRepository } from "./create-repository-D7MQpIZo.mjs";
2
+ import { a as ALL_OPERATIONS, c as READ_OPERATIONS, d as RETENTION_PURGE_ERROR_CODES, i as searchKeysPlugin, l as operationsByPolicyKey, n as Repository, o as MUTATING_OPERATIONS, r as AggregationBuilder, s as OP_REGISTRY, t as HOOK_PRIORITY, u as createMongoPurgePort } from "./Repository-DheA7oW-.mjs";
3
+ import { C as isCreateManyPartialError, g as LookupBuilder, v as compileFilterToMongo } from "./update-CYaJMu6j.mjs";
4
+ import { t as actions_exports } from "./actions-DKQx9ToJ.mjs";
5
+ import { a as supportsTransactions, c as MONGOKIT_CAPABILITIES, d as probeMongoCapabilities, f as resetCapabilityProbeCache, h as transactionResolutionOf, i as runTransaction, l as capabilitiesForSupport, m as resolveTransactionSupport, n as batchTransaction, o as withTransaction, p as resolveMongoCapabilities, r as isTransactionUnsupported, t as TRANSACTION_ERROR_CODES, u as classifyHelloReply } from "./transaction-C6Oj941P.mjs";
6
+ import { t as createRepository } from "./create-repository-C9DHHcl7.mjs";
7
7
  import { n as defineModel, r as isModelRegistered, t as ModelCollisionError } from "./model-registry-DrVfJ3Nt.mjs";
8
- import { c as PAGINATION_ERROR_CODES, h as resetPaginationDefaults, m as getPaginationDefaults, p as configurePaginationDefaults, t as PaginationEngine } from "./PaginationEngine-C9iS0U5l.mjs";
9
- import { C as ensureAuditTrailReady, S as auditTrailPlugin, _ as prefixedId, a as declareTenantContextKey, b as cachePlugin, c as tenantContextKeysOf, d as customIdPlugin, f as dateSequentialId, g as periodCounterKey, h as getNextSequence, i as multiTenantPlugin, l as civilPeriodKey, m as getNextPeriodSequence, n as softDeletePlugin, o as forwardScope, p as ensureSequenceAtLeast, r as adminBypass, s as isScopeKey, t as timestampPlugin, u as createSequenceCounter, v as readSequence, w as auditLogPlugin, x as AuditTrailQuery, y as sequentialId } from "./timestamp.plugin-Bz_zL9PX.mjs";
10
- 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";
11
- import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-CGAepsBU.mjs";
8
+ import { c as PAGINATION_ERROR_CODES, h as resetPaginationDefaults, m as getPaginationDefaults, p as configurePaginationDefaults, t as PaginationEngine } from "./PaginationEngine-BRlLTQQz.mjs";
9
+ import { C as ensureAuditTrailReady, S as auditTrailPlugin, _ as prefixedId, a as declareTenantContextKey, b as cachePlugin, c as tenantContextKeysOf, d as customIdPlugin, f as dateSequentialId, g as periodCounterKey, h as getNextSequence, i as multiTenantPlugin, l as civilPeriodKey, m as getNextPeriodSequence, n as softDeletePlugin, o as forwardScope, p as ensureSequenceAtLeast, r as adminBypass, s as isScopeKey, t as timestampPlugin, u as createSequenceCounter, v as readSequence, w as auditLogPlugin, x as AuditTrailQuery, y as sequentialId } from "./timestamp.plugin-B-IJrlYb.mjs";
10
+ 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";
11
+ import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-BeJfQBJV.mjs";
12
12
  import { n as warn, t as configureLogger } from "./logger-hbWOlj_X.mjs";
13
13
  import { GEO_OPERATORS, parseGeoFilter } from "./query/primitives/geo.mjs";
14
14
  import { buildFieldTypeMap, coerceHeuristic } from "./query/primitives/coercion.mjs";
15
15
  import { n as isDuplicateKeyError, r as parseDuplicateKeyError, t as createError } from "./error-DjdmPLWO.mjs";
16
16
  import { extractSchemaIndexes } from "./query/primitives/indexes.mjs";
17
17
  import { n as declareCollectionScope, t as JOIN_ERROR_CODES } from "./join-scope-C1gOtZ59.mjs";
18
- import { a as assertQueryDefaultsConfigured, c as getQueryDefaults, l as resetQueryDefaults, o as configureAggregateDefaults, s as configureQueryDefaults, t as QUERY_DEFAULTS_ERROR_CODES, u as resolveQueryOptions } from "./query-defaults-aUoKlEbh.mjs";
18
+ import { a as configureAggregateDefaults, c as resetQueryDefaults, i as assertQueryDefaultsConfigured, l as resolveQueryOptions, o as configureQueryDefaults, s as getQueryDefaults, t as QUERY_DEFAULTS_ERROR_CODES } from "./query-defaults-DwvWUjZU.mjs";
19
19
  import { _ as tokenizeSearchText, d as buildSearchKeys, f as prefixSearchFilter, g as normalizeSearchText, h as findSearchMatches, l as DEFAULT_SEARCH_KEYS_FIELD, m as searchKeysPath, n as SearchKeysWriteError, p as searchKeysIndex, t as SEARCH_KEYS_ERROR_CODES, u as MAX_PREFIX_SEARCH_TERMS } from "./derive-Dxsc31fV.mjs";
20
20
  import { i as getMongooseProjection, n as filterResponseData, r as getFieldsForUser, t as createFieldPreset } from "./field-selection-DPFaFw9Y.mjs";
21
21
  import "./search/index.mjs";
@@ -31,7 +31,8 @@ const BULK_WRITE_OPTIONS = [
31
31
  "bypassDocumentValidation",
32
32
  "comment",
33
33
  "let",
34
- "ignoreUndefined"
34
+ "ignoreUndefined",
35
+ "maxTimeMS"
35
36
  ];
36
37
  /**
37
38
  * Driver options that EXIST but this method does not forward.
@@ -51,7 +52,6 @@ const BULK_WRITE_OPTIONS = [
51
52
  const UNSUPPORTED_DRIVER_OPTIONS = /* @__PURE__ */ new Set([
52
53
  "forceServerObjectId",
53
54
  "timeoutMS",
54
- "maxTimeMS",
55
55
  "retryWrites",
56
56
  "checkKeys",
57
57
  "serializeFunctions",