@classytic/mongokit 3.43.1 → 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 (92) hide show
  1. package/README.md +18 -3
  2. package/dist/{PaginationEngine-DZnkbSrr.mjs → PaginationEngine-BRlLTQQz.mjs} +13 -27
  3. package/dist/{PaginationEngine-CRHyuUtw.d.mts → PaginationEngine-BWb-c47a.d.mts} +1 -3
  4. package/dist/{Repository-DnL4xk5w.d.mts → Repository-DJuRmBNx.d.mts} +44 -13
  5. package/dist/{Repository-Dj6xqPxJ.mjs → Repository-DheA7oW-.mjs} +330 -112
  6. package/dist/actions/index.d.mts +1 -1
  7. package/dist/actions/index.mjs +2 -2
  8. package/dist/{actions-ChYfkPYP.mjs → actions-DKQx9ToJ.mjs} +3 -4
  9. package/dist/adapter/index.d.mts +6 -10
  10. package/dist/adapter/index.mjs +1 -1
  11. package/dist/{adapter-CkMoMF83.mjs → adapter-Qzgc3ZD_.mjs} +4 -6
  12. package/dist/ai/index.d.mts +21 -8
  13. package/dist/ai/index.mjs +15 -9
  14. package/dist/better-auth/index.d.mts +21 -20
  15. package/dist/better-auth/index.mjs +12 -23
  16. package/dist/{coercion-BjdPX3YF.d.mts → coercion-COp9DRNw.d.mts} +0 -2
  17. package/dist/{create-repository-UjwPWR1z.mjs → create-repository-C9DHHcl7.mjs} +4 -5
  18. package/dist/{create-repository-CJtVdwig.d.mts → create-repository-DG-gwOf0.d.mts} +5 -7
  19. package/dist/{derive-DRBiegRH.mjs → derive-Dxsc31fV.mjs} +3 -6
  20. package/dist/{error-BuByZwKU.mjs → error-DjdmPLWO.mjs} +1 -2
  21. package/dist/{field-selection-FSQFLpsU.mjs → field-selection-DPFaFw9Y.mjs} +1 -2
  22. package/dist/{id-chunks-BmAwsqNk.mjs → id-chunks-DH-RAZWG.mjs} +13 -8
  23. package/dist/{id-resolution-BqDrk0S2.mjs → id-resolution-Cqh49d9J.mjs} +1 -2
  24. package/dist/idempotency/index.d.mts +4 -7
  25. package/dist/idempotency/index.mjs +1 -2
  26. package/dist/{index-SYF8N28D.d.mts → index-5mhQ63_B.d.mts} +3 -4
  27. package/dist/{index-C0AjVqX3.d.mts → index-ehGZLbrd.d.mts} +2 -8
  28. package/dist/index.d.mts +70 -102
  29. package/dist/index.mjs +36 -52
  30. package/dist/indexes/verify-indexes.d.mts +8 -11
  31. package/dist/indexes/verify-indexes.mjs +1 -2
  32. package/dist/{indexes-DIjMpYES.d.mts → indexes-FfyOuhDK.d.mts} +0 -2
  33. package/dist/{join-scope-DSAznE8E.mjs → join-scope-C1gOtZ59.mjs} +2 -3
  34. package/dist/kernel-conformance/index.d.mts +39 -13
  35. package/dist/kernel-conformance/index.mjs +4 -7
  36. package/dist/lock/index.d.mts +134 -7
  37. package/dist/lock/index.mjs +349 -14
  38. package/dist/{logger-K7dPmu2U.mjs → logger-hbWOlj_X.mjs} +1 -2
  39. package/dist/{method-registry.plugin-C63pGn1d.mjs → method-registry.plugin-BeJfQBJV.mjs} +5 -7
  40. package/dist/{model-registry-ClKtWiHa.mjs → model-registry-DrVfJ3Nt.mjs} +1 -2
  41. package/dist/{mongooseToJsonSchema-C3k4ide0.mjs → mongooseToJsonSchema-DMDotz4_.mjs} +2 -3
  42. package/dist/{multi-tenant.plugin-B_q9NUHc.d.mts → multi-tenant.plugin-BTPtCLNK.d.mts} +143 -11
  43. package/dist/{operations-XiFgG5US.d.mts → operations-D6XsHx4e.d.mts} +19 -11
  44. package/dist/outbox/index.d.mts +24 -9
  45. package/dist/outbox/index.mjs +17 -6
  46. package/dist/pagination/PaginationEngine.d.mts +1 -1
  47. package/dist/pagination/PaginationEngine.mjs +1 -1
  48. package/dist/pagination/utils/limits.d.mts +6 -9
  49. package/dist/pagination/utils/limits.mjs +2 -3
  50. package/dist/{pagination-D6psses0.d.mts → pagination-BAM9cA4N.d.mts} +6 -16
  51. package/dist/{plugin-options-Cirmb4Kp.d.mts → plugin-options-DrNGWV5b.d.mts} +2 -4
  52. package/dist/plugins/index.d.mts +6 -7
  53. package/dist/plugins/index.mjs +6 -7
  54. package/dist/query/primitives/coercion.d.mts +1 -1
  55. package/dist/query/primitives/coercion.mjs +2 -3
  56. package/dist/query/primitives/geo.d.mts +11 -14
  57. package/dist/query/primitives/geo.mjs +1 -2
  58. package/dist/query/primitives/indexes.d.mts +1 -1
  59. package/dist/query/primitives/indexes.mjs +1 -2
  60. package/dist/{query-defaults-BxUcSOoF.mjs → query-defaults-DwvWUjZU.mjs} +18 -54
  61. package/dist/read-model/index.d.mts +18 -21
  62. package/dist/read-model/index.mjs +3 -4
  63. package/dist/{repository-MqAP409e.d.mts → repository-B0aFVgP2.d.mts} +25 -29
  64. package/dist/retention-purge-CCd_VXUf.d.mts +38 -0
  65. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-D8jQpDG7.mjs} +1 -2
  66. package/dist/search/index.d.mts +1 -1
  67. package/dist/search/index.mjs +1 -1
  68. package/dist/sync/index.d.mts +113 -17
  69. package/dist/sync/index.mjs +421 -65
  70. package/dist/tenant/index.d.mts +8 -11
  71. package/dist/tenant/index.mjs +1 -2
  72. package/dist/testkit/index.d.mts +179 -23
  73. package/dist/testkit/index.mjs +421 -23
  74. package/dist/{timestamp.plugin-DpOWavVI.mjs → timestamp.plugin-B-IJrlYb.mjs} +192 -49
  75. package/dist/{to-plain-vksRrLtc.mjs → to-plain-CEKv_ylg.mjs} +2 -4
  76. package/dist/{to-plain-o5tUY2nV.d.mts → to-plain-Do-8jTKC.d.mts} +2 -16
  77. package/dist/{transaction-DmiB4k2P.mjs → transaction-C6Oj941P.mjs} +18 -19
  78. package/dist/{type-utils-CLzRFtIA.d.mts → type-utils-CKvuewAL.d.mts} +0 -2
  79. package/dist/{update-WgeAUJ_I.mjs → update-CYaJMu6j.mjs} +13 -23
  80. package/dist/usage/index.d.mts +2 -5
  81. package/dist/usage/index.mjs +8 -6
  82. package/dist/utils/index.d.mts +8 -10
  83. package/dist/utils/index.mjs +7 -7
  84. package/dist/{validation-chain.plugin-BAeC5eVT.d.mts → validation-chain.plugin-COYrUdd8.d.mts} +150 -48
  85. package/dist/{validation-chain.plugin-DRApTmkN.mjs → validation-chain.plugin-DJplVJ12.mjs} +210 -55
  86. package/llms.txt +33 -0
  87. package/package.json +29 -25
  88. package/skills/mongokit/SKILL.md +112 -0
  89. package/skills/mongokit/references/pagination-search.md +87 -0
  90. package/skills/mongokit/references/plugins-tenancy.md +103 -0
  91. package/skills/mongokit/references/repository-api.md +83 -0
  92. package/skills/mongokit/references/transactions-outbox.md +103 -0
@@ -1,11 +1,11 @@
1
- import { a as ALL_OPERATIONS, s as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-Dj6xqPxJ.mjs";
2
- import { n as warn } from "./logger-K7dPmu2U.mjs";
3
- import { n as declareCollectionScope } from "./join-scope-DSAznE8E.mjs";
1
+ import { a as ALL_OPERATIONS, s as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-DheA7oW-.mjs";
2
+ import { n as warn } from "./logger-hbWOlj_X.mjs";
3
+ import { n as declareCollectionScope } from "./join-scope-C1gOtZ59.mjs";
4
4
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
5
5
  import mongoose from "mongoose";
6
6
  import { cachePlugin } from "@classytic/repo-core/cache";
7
7
  import { adminBypass, payloadHasTenantField } from "@classytic/repo-core/plugins";
8
- //#region src/plugins/audit-log.plugin.ts
8
+ // src/plugins/audit-log.plugin.ts
9
9
  /**
10
10
  * Audit log plugin that logs all repository operations
11
11
  *
@@ -102,8 +102,7 @@ function auditLogPlugin(logger) {
102
102
  }
103
103
  };
104
104
  }
105
- //#endregion
106
- //#region src/plugins/audit-trail.plugin.ts
105
+ // src/plugins/audit-trail.plugin.ts
107
106
  /**
108
107
  * Audit Trail Plugin
109
108
  *
@@ -631,8 +630,7 @@ var AuditTrailQuery = class {
631
630
  });
632
631
  }
633
632
  };
634
- //#endregion
635
- //#region src/plugins/custom-id.plugin.ts
633
+ // src/plugins/custom-id.plugin.ts
636
634
  /**
637
635
  * Custom ID Plugin
638
636
  *
@@ -711,10 +709,54 @@ const counterSchema = new mongoose.Schema({
711
709
  * Falls back to the default mongoose connection if none is provided.
712
710
  * Lazy-init to avoid model registration errors if mongoose isn't connected yet.
713
711
  */
714
- function getCounterModel(connection) {
712
+ function getCounterModel(connection, collection) {
715
713
  const conn = connection ?? mongoose.connection;
716
- if (conn.models._MongoKitCounter) return conn.models._MongoKitCounter;
717
- return conn.model("_MongoKitCounter", counterSchema);
714
+ const modelName = collection === void 0 || collection === "_mongokit_counters" ? "_MongoKitCounter" : `_MongoKitCounter:${collection}`;
715
+ if (conn.models[modelName]) return conn.models[modelName];
716
+ return conn.model(modelName, counterSchema, collection ?? "_mongokit_counters");
717
+ }
718
+ /**
719
+ * Bind the atomic counter API to a connection and, optionally, a
720
+ * collection other than `_mongokit_counters`.
721
+ *
722
+ * @example
723
+ * const counters = createSequenceCounter({ connection, collection: 'app_counters' });
724
+ * await withTransaction(connection, async (session) => {
725
+ * const seq = await counters.next('alert.stream', 1, session);
726
+ * });
727
+ */
728
+ function createSequenceCounter(options = {}) {
729
+ const { connection, collection } = options;
730
+ assertCounterCollection("createSequenceCounter", collection);
731
+ const model = () => getCounterModel(connection, collection);
732
+ return {
733
+ next: (counterKey, increment = 1, session) => incrementCounter(model(), counterKey, increment, session),
734
+ read: (counterKey, session) => readCounter(model(), counterKey, session),
735
+ ensureAtLeast: (counterKey, floor, session) => raiseCounter(model(), counterKey, floor, session),
736
+ nextInPeriod: (baseKey, periodOptions) => nextInPeriod(model(), baseKey, periodOptions)
737
+ };
738
+ }
739
+ function assertCounterCollection(fn, collection) {
740
+ if (collection !== void 0 && (typeof collection !== "string" || collection.length === 0)) throw new Error(`${fn}: collection must be a non-empty string`);
741
+ }
742
+ async function incrementCounter(Counter, counterKey, increment, session) {
743
+ const result = await Counter.findOneAndUpdate({ _id: counterKey }, { $inc: { seq: increment } }, {
744
+ upsert: true,
745
+ returnDocument: "after",
746
+ session
747
+ });
748
+ if (!result) throw new Error(`Failed to increment counter '${counterKey}'`);
749
+ return result.seq;
750
+ }
751
+ async function raiseCounter(Counter, counterKey, floor, session) {
752
+ if (!Number.isSafeInteger(floor) || floor < 0) throw new Error(`ensureSequenceAtLeast: floor must be a non-negative integer, got ${floor}`);
753
+ const result = await Counter.findOneAndUpdate({ _id: counterKey }, { $max: { seq: floor } }, {
754
+ upsert: true,
755
+ returnDocument: "after",
756
+ session
757
+ });
758
+ if (!result) throw new Error(`Failed to seed counter '${counterKey}'`);
759
+ return result.seq;
718
760
  }
719
761
  /**
720
762
  * Atomically increment and return the next sequence value for a given key.
@@ -746,20 +788,17 @@ function getCounterModel(connection) {
746
788
  * });
747
789
  */
748
790
  async function getNextSequence(counterKey, increment = 1, connection, session) {
749
- const result = await getCounterModel(connection).findOneAndUpdate({ _id: counterKey }, { $inc: { seq: increment } }, {
750
- upsert: true,
751
- returnDocument: "after",
752
- session
753
- });
754
- if (!result) throw new Error(`Failed to increment counter '${counterKey}'`);
755
- return result.seq;
791
+ return incrementCounter(getCounterModel(connection), counterKey, increment, session);
756
792
  }
757
793
  /**
758
794
  * Current value of a counter, or `null` when it has never been created.
759
795
  * `null` and `0` differ: only a missing counter needs seeding.
760
796
  */
761
- async function readSequence(counterKey, connection) {
762
- const doc = await getCounterModel(connection).findById(counterKey).lean();
797
+ async function readSequence(counterKey, connection, session) {
798
+ return readCounter(getCounterModel(connection), counterKey, session);
799
+ }
800
+ async function readCounter(Counter, counterKey, session) {
801
+ const doc = await Counter.findById(counterKey, null, session ? { session } : {}).lean();
763
802
  return doc ? doc.seq : null;
764
803
  }
765
804
  /**
@@ -772,14 +811,109 @@ async function readSequence(counterKey, connection) {
772
811
  *
773
812
  * @returns The counter's value after the raise.
774
813
  */
775
- async function ensureSequenceAtLeast(counterKey, floor, connection) {
776
- if (!Number.isSafeInteger(floor) || floor < 0) throw new Error(`ensureSequenceAtLeast: floor must be a non-negative integer, got ${floor}`);
777
- const result = await getCounterModel(connection).findOneAndUpdate({ _id: counterKey }, { $max: { seq: floor } }, {
778
- upsert: true,
779
- returnDocument: "after"
780
- });
781
- if (!result) throw new Error(`Failed to seed counter '${counterKey}'`);
782
- return result.seq;
814
+ async function ensureSequenceAtLeast(counterKey, floor, connection, session) {
815
+ return raiseCounter(getCounterModel(connection), counterKey, floor, session);
816
+ }
817
+ const SEQUENCE_PERIODS = /* @__PURE__ */ new Set([
818
+ "year",
819
+ "month",
820
+ "day"
821
+ ]);
822
+ /** One cached formatter per zone; construction is the expensive part of Intl. */
823
+ const civilFormatters = /* @__PURE__ */ new Map();
824
+ function civilFormatter(fn, timeZone) {
825
+ if (typeof timeZone !== "string" || timeZone.length === 0) throw new Error(`${fn}: timeZone must be a non-empty IANA time zone (e.g. 'Asia/Riyadh')`);
826
+ let formatter = civilFormatters.get(timeZone);
827
+ if (!formatter) {
828
+ try {
829
+ formatter = new Intl.DateTimeFormat("en-US", {
830
+ timeZone,
831
+ calendar: "gregory",
832
+ numberingSystem: "latn",
833
+ year: "numeric",
834
+ month: "2-digit",
835
+ day: "2-digit"
836
+ });
837
+ } catch {
838
+ throw new Error(`${fn}: unknown time zone '${timeZone}'`);
839
+ }
840
+ civilFormatters.set(timeZone, formatter);
841
+ }
842
+ return formatter;
843
+ }
844
+ /**
845
+ * The civil date of an instant in a zone. Read from the zone's own rules
846
+ * (Intl), never by adding a fixed offset, so DST shifts and zone-rule changes
847
+ * (a skipped or repeated local day) land on the date a wall clock there shows.
848
+ */
849
+ function civilParts(fn, at, timeZone) {
850
+ const formatter = civilFormatter(fn, timeZone);
851
+ let year = "";
852
+ let month = "";
853
+ let day = "";
854
+ for (const part of formatter.formatToParts(at)) if (part.type === "year") year = part.value;
855
+ else if (part.type === "month") month = part.value;
856
+ else if (part.type === "day") day = part.value;
857
+ return {
858
+ year: year.padStart(4, "0"),
859
+ month,
860
+ day
861
+ };
862
+ }
863
+ function assertInstant(fn, at) {
864
+ if (!(at instanceof Date) || Number.isNaN(at.getTime())) throw new Error(`${fn}: at must be a valid Date`);
865
+ }
866
+ /**
867
+ * The civil period of an instant in an IANA time zone: `'2026'` (year),
868
+ * `'2026-03'` (month) or `'2026-03-14'` (day). The period suffix every
869
+ * period-scoped counter key ends with.
870
+ *
871
+ * @example
872
+ * civilPeriodKey(new Date('2026-12-31T21:00:00Z'), 'year', 'Asia/Riyadh'); // '2027'
873
+ * civilPeriodKey(new Date('2026-12-31T21:00:00Z'), 'year', 'UTC'); // '2026'
874
+ */
875
+ function civilPeriodKey(at, period, timeZone) {
876
+ assertInstant("civilPeriodKey", at);
877
+ if (!SEQUENCE_PERIODS.has(period)) throw new Error(`civilPeriodKey: period must be 'year', 'month' or 'day', got '${period}'`);
878
+ const { year, month, day } = civilParts("civilPeriodKey", at, timeZone);
879
+ if (period === "year") return year;
880
+ if (period === "month") return `${year}-${month}`;
881
+ return `${year}-${month}-${day}`;
882
+ }
883
+ /**
884
+ * The counter key and civil period for a base key at an instant. Use it to
885
+ * `read` / `ensureAtLeast` the same counter `getNextPeriodSequence` bumps.
886
+ */
887
+ function periodCounterKey(baseKey, options) {
888
+ if (typeof baseKey !== "string" || baseKey.length === 0) throw new Error("periodCounterKey: baseKey must be a non-empty string");
889
+ const period = civilPeriodKey(options.at ?? /* @__PURE__ */ new Date(), options.period, options.timeZone);
890
+ return {
891
+ counterKey: `${baseKey}${options.keySeparator ?? ":"}${period}`,
892
+ period
893
+ };
894
+ }
895
+ /**
896
+ * Atomically issue the next value of a counter that resets every civil
897
+ * year / month / day in an IANA time zone. The period is derived from `at`
898
+ * (default now) in `timeZone`, so every replica agrees on it whatever its
899
+ * `TZ`, and DST never moves a boundary.
900
+ *
901
+ * @example Yearly reference `OC-2026-000123` in Riyadh
902
+ * const { seq, period } = await getNextPeriodSequence('occ.occurrence', {
903
+ * period: 'year', timeZone: 'Asia/Riyadh', at: reportedAt, connection, session,
904
+ * });
905
+ * const ref = `OC-${period}-${String(seq).padStart(6, '0')}`;
906
+ */
907
+ async function getNextPeriodSequence(baseKey, options) {
908
+ return nextInPeriod(getCounterModel(options.connection), baseKey, options);
909
+ }
910
+ async function nextInPeriod(Counter, baseKey, options) {
911
+ const { counterKey, period } = periodCounterKey(baseKey, options);
912
+ return {
913
+ seq: await incrementCounter(Counter, counterKey, options.increment ?? 1, options.session),
914
+ period,
915
+ counterKey
916
+ };
783
917
  }
784
918
  /**
785
919
  * Generator: Simple sequential counter.
@@ -796,10 +930,11 @@ async function ensureSequenceAtLeast(counterKey, floor, connection) {
796
930
  * ```
797
931
  */
798
932
  function sequentialId(options) {
799
- const { prefix, model, padding = 4, separator = "-", counterKey } = options;
933
+ const { prefix, model, padding = 4, separator = "-", counterKey, counterCollection } = options;
800
934
  const key = counterKey || model.modelName;
935
+ assertCounterCollection("sequentialId", counterCollection);
801
936
  return async (context) => {
802
- const seq = await getNextSequence(key, 1, context._counterConnection, context.session);
937
+ const seq = await incrementCounter(getCounterModel(context._counterConnection, counterCollection), key, 1, context.session);
803
938
  return `${prefix}${separator}${String(seq).padStart(padding, "0")}`;
804
939
  };
805
940
  }
@@ -834,21 +969,31 @@ function sequentialId(options) {
834
969
  * ```
835
970
  */
836
971
  function dateSequentialId(options) {
837
- const { prefix, model, partition = "monthly", padding = 4, separator = "-", scope = "global", tenantKey = "organizationId", timezone = "local" } = options;
972
+ const { prefix, model, partition = "monthly", padding = 4, separator = "-", scope = "global", tenantKey = "organizationId", timezone = "local", timeZone, at, counterKey: baseCounterKey, counterCollection } = options;
973
+ assertCounterCollection("dateSequentialId", counterCollection);
974
+ if (timeZone !== void 0) civilFormatter("dateSequentialId", timeZone);
975
+ if (baseCounterKey !== void 0 && (typeof baseCounterKey !== "string" || baseCounterKey.length === 0)) throw new Error("dateSequentialId: counterKey must be a non-empty string");
838
976
  /** Per-generator, not module-level — two generators warn independently. */
839
977
  const overflowWarned = /* @__PURE__ */ new Set();
840
978
  const paddingCeiling = 10 ** padding;
841
979
  return async (context) => {
842
- const now = /* @__PURE__ */ new Date();
843
- const utc = timezone === "utc";
844
- const year = String(utc ? now.getUTCFullYear() : now.getFullYear());
845
- const month = String((utc ? now.getUTCMonth() : now.getMonth()) + 1).padStart(2, "0");
846
- const day = String(utc ? now.getUTCDate() : now.getDate()).padStart(2, "0");
847
- let keyBase = model.modelName;
980
+ const now = at?.(context) ?? /* @__PURE__ */ new Date();
981
+ assertInstant("dateSequentialId", now);
982
+ let year;
983
+ let month;
984
+ let day;
985
+ if (timeZone !== void 0) ({year, month, day} = civilParts("dateSequentialId", now, timeZone));
986
+ else {
987
+ const utc = timezone === "utc";
988
+ year = String(utc ? now.getUTCFullYear() : now.getFullYear());
989
+ month = String((utc ? now.getUTCMonth() : now.getMonth()) + 1).padStart(2, "0");
990
+ day = String(utc ? now.getUTCDate() : now.getDate()).padStart(2, "0");
991
+ }
992
+ let keyBase = baseCounterKey ?? model.modelName;
848
993
  if (scope === "tenant") {
849
994
  const tenantId = context[tenantKey];
850
995
  if (tenantId === void 0 || tenantId === null || tenantId === "") throw new Error(`[mongokit] dateSequentialId: scope 'tenant' requires '${tenantKey}' in context for '${model.modelName}' create, but none was provided. Pass it per call (repo.create(data, { ${tenantKey}: '<id>' })) or resolve it ambiently; the generator never falls back to the global counter.`);
851
- keyBase = `${model.modelName}:${String(tenantId)}`;
996
+ keyBase = `${keyBase}:${String(tenantId)}`;
852
997
  }
853
998
  let datePart;
854
999
  let counterKey;
@@ -865,7 +1010,7 @@ function dateSequentialId(options) {
865
1010
  datePart = `${year}${separator}${month}`;
866
1011
  counterKey = `${keyBase}:${year}-${month}`;
867
1012
  }
868
- const seq = await getNextSequence(counterKey, 1, context._counterConnection, context.session);
1013
+ const seq = await incrementCounter(getCounterModel(context._counterConnection, counterCollection), counterKey, 1, context.session);
869
1014
  if (seq >= paddingCeiling && !overflowWarned.has(counterKey)) {
870
1015
  overflowWarned.add(counterKey);
871
1016
  warn(`[mongokit] dateSequentialId: '${model.modelName}' sequence ${seq} exceeds padding ${padding} (max ${paddingCeiling - 1}) for period '${counterKey}'. Ids remain unique but no longer sort lexically — raise \`padding\` for new periods, or use a finer \`partition\`.`);
@@ -957,8 +1102,7 @@ function customIdPlugin(options) {
957
1102
  }
958
1103
  };
959
1104
  }
960
- //#endregion
961
- //#region src/utils/scope.ts
1105
+ // src/utils/scope.ts
962
1106
  /** Tenant context keys forwarded even when no tenant plugin declared one. */
963
1107
  const CONVENTIONAL_TENANT_KEYS = ["organizationId", "tenantId"];
964
1108
  const ATTRIBUTION_KEYS = [
@@ -1013,8 +1157,7 @@ function forwardScope(source, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
1013
1157
  function isScopeKey(key, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
1014
1158
  return key === "bypassTenant" || tenantKeys.includes(key) || ATTRIBUTION_KEYS.includes(key);
1015
1159
  }
1016
- //#endregion
1017
- //#region src/plugins/multi-tenant.plugin.ts
1160
+ // src/plugins/multi-tenant.plugin.ts
1018
1161
  /**
1019
1162
  * Multi-Tenant Plugin
1020
1163
  *
@@ -1063,6 +1206,9 @@ function multiTenantPlugin(options = {}) {
1063
1206
  const extraOps = [{
1064
1207
  op: "getDeleted",
1065
1208
  policyKey: "filters"
1209
+ }, {
1210
+ op: "searchSimilar",
1211
+ policyKey: "filters"
1066
1212
  }];
1067
1213
  return {
1068
1214
  name: "multi-tenant",
@@ -1256,8 +1402,7 @@ function multiTenantPlugin(options = {}) {
1256
1402
  function adminBypass$1(options) {
1257
1403
  return adminBypass(options);
1258
1404
  }
1259
- //#endregion
1260
- //#region src/plugins/soft-delete.plugin.ts
1405
+ // src/plugins/soft-delete.plugin.ts
1261
1406
  /**
1262
1407
  * Build the deletion-state filter for either side of the soft-delete
1263
1408
  * boundary. One parameterized helper covers both directions so the two
@@ -1563,8 +1708,7 @@ function softDeletePlugin(options = {}) {
1563
1708
  }
1564
1709
  };
1565
1710
  }
1566
- //#endregion
1567
- //#region src/plugins/timestamp.plugin.ts
1711
+ // src/plugins/timestamp.plugin.ts
1568
1712
  /**
1569
1713
  * Timestamp plugin that auto-injects timestamps
1570
1714
  *
@@ -1602,5 +1746,4 @@ function timestampPlugin() {
1602
1746
  }
1603
1747
  };
1604
1748
  }
1605
- //#endregion
1606
- export { AuditTrailQuery as _, declareTenantContextKey as a, auditLogPlugin as b, tenantContextKeysOf as c, ensureSequenceAtLeast as d, getNextSequence as f, cachePlugin as g, sequentialId as h, multiTenantPlugin as i, customIdPlugin as l, readSequence as m, softDeletePlugin as n, forwardScope as o, prefixedId as p, adminBypass$1 as r, isScopeKey as s, timestampPlugin as t, dateSequentialId as u, auditTrailPlugin as v, ensureAuditTrailReady as y };
1749
+ export { ensureAuditTrailReady as C, auditTrailPlugin as S, prefixedId as _, declareTenantContextKey as a, cachePlugin as b, tenantContextKeysOf as c, customIdPlugin as d, dateSequentialId as f, periodCounterKey as g, getNextSequence as h, multiTenantPlugin as i, civilPeriodKey as l, getNextPeriodSequence as m, softDeletePlugin as n, forwardScope as o, ensureSequenceAtLeast as p, adminBypass$1 as r, isScopeKey as s, timestampPlugin as t, createSequenceCounter as u, readSequence as v, auditLogPlugin as w, AuditTrailQuery as x, sequentialId as y };
@@ -1,4 +1,4 @@
1
- //#region src/utils/memory-cache.ts
1
+ // src/utils/memory-cache.ts
2
2
  /**
3
3
  * Creates an in-memory cache adapter
4
4
  *
@@ -63,8 +63,7 @@ function createMemoryCache(maxEntries = 1e3) {
63
63
  }
64
64
  };
65
65
  }
66
- //#endregion
67
- //#region src/utils/to-plain.ts
66
+ // src/utils/to-plain.ts
68
67
  /**
69
68
  * Convert a hydrated Mongoose (sub)document to a plain object — the
70
69
  * spread-safety primitive for domain verbs that rebuild embedded lines.
@@ -94,5 +93,4 @@ function createMemoryCache(maxEntries = 1e3) {
94
93
  function toPlain(value) {
95
94
  return value !== null && value !== void 0 && typeof value.toObject === "function" ? value.toObject() : value;
96
95
  }
97
- //#endregion
98
96
  export { createMemoryCache as n, toPlain as t };
@@ -1,10 +1,9 @@
1
- import { y as UserContext } from "./repository-MqAP409e.mjs";
2
- import { r as FieldPreset } from "./plugin-options-Cirmb4Kp.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";
6
6
  import { CrudSchemas, SchemaBuilderOptions } from "@classytic/repo-core/schema";
7
- //#region src/utils/error.d.ts
8
7
  /** Optional structured fields for createError. */
9
8
  interface CreateErrorOptions {
10
9
  /** Stable machine-readable code (e.g. `SEARCH_NOT_CONFIGURED`). */
@@ -70,8 +69,6 @@ interface ParseDuplicateKeyOptions {
70
69
  * Returns `null` when the error is not a duplicate-key error.
71
70
  */
72
71
  declare function parseDuplicateKeyError(error: unknown, options?: ParseDuplicateKeyOptions): HttpError | null;
73
- //#endregion
74
- //#region src/utils/field-selection.d.ts
75
72
  /**
76
73
  * Get allowed fields for a user based on their context
77
74
  *
@@ -138,8 +135,6 @@ declare function filterResponseData<T extends Record<string, unknown>>(data: T |
138
135
  * });
139
136
  */
140
137
  declare function createFieldPreset(config: Partial<FieldPreset>): FieldPreset;
141
- //#endregion
142
- //#region src/utils/id-resolution.d.ts
143
138
  /** Normalized _id type — covers the common Mongoose _id declarations */
144
139
  type IdType = 'objectid' | 'string' | 'number' | 'uuid' | 'unknown';
145
140
  /**
@@ -212,8 +207,6 @@ declare function isValidIdForType(id: unknown, idType: IdType): boolean;
212
207
  * await col.deleteMany({ customerId: { $in: idVariants(userId) } });
213
208
  */
214
209
  declare function idVariants(id: unknown): unknown[];
215
- //#endregion
216
- //#region src/utils/logger.d.ts
217
210
  /**
218
211
  * Internal Logger
219
212
  *
@@ -244,8 +237,6 @@ interface LoggerConfig {
244
237
  * Pass `false` to silence all output.
245
238
  */
246
239
  declare function configureLogger(config: Partial<LoggerConfig> | false): void;
247
- //#endregion
248
- //#region src/utils/memory-cache.d.ts
249
240
  /**
250
241
  * Creates an in-memory cache adapter
251
242
  *
@@ -257,8 +248,6 @@ declare function configureLogger(config: Partial<LoggerConfig> | false): void;
257
248
  * @param maxEntries - Maximum cache entries before oldest are evicted (default: 1000)
258
249
  */
259
250
  declare function createMemoryCache(maxEntries?: number): CacheAdapter;
260
- //#endregion
261
- //#region src/utils/mongooseToJsonSchema.d.ts
262
251
  /**
263
252
  * Return shape for the schema builders. Structurally a `CrudSchemas` plus
264
253
  * an `[key: string]: unknown` index signature so the function reference
@@ -298,8 +287,6 @@ declare function buildCrudSchemasFromMongooseSchema(mongooseSchema: Schema, opti
298
287
  * `SchemaGenerator<Model<unknown>>`, so the contract claim above is unchanged.
299
288
  */
300
289
  declare function buildCrudSchemasFromModel<TDoc = unknown>(mongooseModel: mongoose.Model<TDoc>, options?: SchemaBuilderOptions): CrudSchemasFramework;
301
- //#endregion
302
- //#region src/utils/to-plain.d.ts
303
290
  /**
304
291
  * Convert a hydrated Mongoose (sub)document to a plain object — the
305
292
  * spread-safety primitive for domain verbs that rebuild embedded lines.
@@ -327,5 +314,4 @@ declare function buildCrudSchemasFromModel<TDoc = unknown>(mongooseModel: mongoo
327
314
  * defensively over values that may or may not be hydrated.
328
315
  */
329
316
  declare function toPlain<T>(value: T): T;
330
- //#endregion
331
317
  export { parseDuplicateKeyError as S, getMongooseProjection as _, createMemoryCache as a, createError as b, IdType as c, isObjectIdInstance as d, isValidIdForType as f, getFieldsForUser as g, filterResponseData as h, buildCrudSchemasFromMongooseSchema as i, getSchemaIdType as l, createFieldPreset as m, CrudSchemasFramework as n, LoggerConfig as o, toObjectId as p, buildCrudSchemasFromModel as r, configureLogger as s, toPlain as t, idVariants as u, CreateErrorOptions as v, isDuplicateKeyError as x, ParseDuplicateKeyOptions as y };
@@ -1,8 +1,8 @@
1
- import { t as createError } from "./error-BuByZwKU.mjs";
2
- import { d as setTransactionDeadline } from "./query-defaults-BxUcSOoF.mjs";
1
+ import { u as setTransactionDeadline } from "./query-defaults-DwvWUjZU.mjs";
2
+ import { t as createError } from "./error-DjdmPLWO.mjs";
3
3
  import mongoose from "mongoose";
4
4
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
5
- //#region src/capabilities.ts
5
+ // src/capabilities.ts
6
6
  /**
7
7
  * Read the provenance of a capability descriptor's `transactions` flag.
8
8
  * A descriptor without the field is a static declaration (`'declared'`) —
@@ -252,8 +252,7 @@ async function probeMongoCapabilities(connection, options = {}) {
252
252
  PROBE_INFLIGHT.set(key, run);
253
253
  return DERIVED[await run];
254
254
  }
255
- //#endregion
256
- //#region src/tx-bound.ts
255
+ // src/tx-bound.ts
257
256
  /**
258
257
  * Map of session-aware method name → positional index of the `{ session }`
259
258
  * options bag. Keep this list in sync with the method signatures in
@@ -391,8 +390,7 @@ function createTxBoundRepo(outer, session) {
391
390
  };
392
391
  } });
393
392
  }
394
- //#endregion
395
- //#region src/transaction.ts
393
+ // src/transaction.ts
396
394
  /**
397
395
  * Cross-repo transaction helper.
398
396
  *
@@ -496,18 +494,17 @@ function timeoutError(cause, csot) {
496
494
  }
497
495
  /**
498
496
  * The driver's convenient-transaction algorithm (start → callback → commit, label-based retry,
499
- * jittered backoff, a deadline), run here so the driver never attaches a session `timeoutContext`:
500
- * with one, every legacy bulk write (`insertMany`, `bulkWrite`) in the transaction is refused once
501
- * the client carries `timeoutMS` (driver <= 7.7: the bulk path re-resolves the inherited client
502
- * `timeoutMS` as a per-op one). Each operation keeps its own per-op CSOT budget instead.
503
- * Deadline: `transactionOptions.timeoutMS`, else the client's `timeoutMS`, else 120 s (as the driver).
504
- * Pinned by `tests/integration/csot-transaction-matrix.test.ts`.
497
+ * jittered backoff, a deadline), run here and nowhere else: the driver's own `timeoutMS` / CSOT is a
498
+ * no-op since 7.8, so the budget is mongokit's. `transactionOptions.timeoutMS` is that budget, enforced
499
+ * by mongokit alone: every mongokit operation in the transaction is sent with `maxTimeMS` = what
500
+ * remains of it, the commit is bounded by `maxCommitTimeMS`, and the retry loop stops at the deadline
501
+ * (a spent budget before the commit aborts, so the outcome is known). Without it the deadline is 120 s
502
+ * (as the driver) and operations carry only their own bound. The client's `timeoutMS` is not read.
503
+ * Pinned by `tests/integration/transaction-budget-matrix.test.ts`.
505
504
  */
506
505
  async function runTransaction(session, callback, transactionOptions = {}) {
507
506
  assertSessionSurface(session, DRIVEN);
508
- const { timeoutMS: explicitTimeout, ...startOptions } = transactionOptions;
509
- const clientTimeout = session.timeoutMS;
510
- const timeoutMS = explicitTimeout ?? clientTimeout;
507
+ const { timeoutMS, ...startOptions } = transactionOptions;
511
508
  const csot = timeoutMS != null;
512
509
  const deadline = performance.now() + (timeoutMS ?? MAX_TRANSACTION_TIMEOUT_MS);
513
510
  let lastError;
@@ -519,7 +516,10 @@ async function runTransaction(session, callback, transactionOptions = {}) {
519
516
  if (performance.now() + backoff >= deadline) throw timeoutError(lastError, csot);
520
517
  await setTimeout$1(backoff);
521
518
  }
522
- session.startTransaction(startOptions);
519
+ session.startTransaction(csot && startOptions.maxCommitTimeMS === void 0 ? {
520
+ ...startOptions,
521
+ maxCommitTimeMS: Math.max(1, Math.floor(deadline - performance.now()))
522
+ } : startOptions);
523
523
  let result;
524
524
  try {
525
525
  result = await callback(session);
@@ -541,7 +541,7 @@ async function runTransaction(session, callback, transactionOptions = {}) {
541
541
  throw late;
542
542
  }
543
543
  try {
544
- await session.commitTransaction(csot ? { timeoutMS: Math.max(1, Math.floor(left)) } : void 0);
544
+ await session.commitTransaction();
545
545
  return result;
546
546
  } catch (err) {
547
547
  lastError = err;
@@ -678,5 +678,4 @@ function isTransactionUnsupported(error) {
678
678
  function supportsTransactions(connection) {
679
679
  return resolveTransactionSupport(connection) !== "no";
680
680
  }
681
- //#endregion
682
681
  export { supportsTransactions as a, MONGOKIT_CAPABILITIES as c, probeMongoCapabilities as d, resetCapabilityProbeCache as f, transactionResolutionOf as h, runTransaction as i, capabilitiesForSupport as l, resolveTransactionSupport as m, batchTransaction as n, withTransaction as o, resolveMongoCapabilities as p, isTransactionUnsupported as r, createTxBoundRepo as s, TRANSACTION_ERROR_CODES as t, classifyHelloReply as u };
@@ -1,6 +1,5 @@
1
1
  import { Document, Model } from "mongoose";
2
2
  import { ValidationResult } from "@classytic/repo-core/schema";
3
- //#region src/types/type-utils.d.ts
4
3
  /**
5
4
  * Infer document type from a Mongoose Model
6
5
  * @example
@@ -159,5 +158,4 @@ type UpdateWithValidationResult<T> = {
159
158
  violations?: ValidationResult['violations'];
160
159
  };
161
160
  };
162
- //#endregion
163
161
  export { InferRawDoc as a, NonNullableFields as c, Strict as d, UpdatePatch as f, InferDocument as i, PartialBy as l, DeepPartial as n, KeysOfType as o, UpdateWithValidationResult as p, DocField as r, MongoOperatorUpdate as s, CreateInput as t, RequiredBy as u };
@@ -1,9 +1,9 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { n as warn } from "./logger-K7dPmu2U.mjs";
3
- import { t as createError } from "./error-BuByZwKU.mjs";
4
- import { i as applyToQuery } from "./query-defaults-BxUcSOoF.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.mjs";
2
+ import { t as createError } from "./error-DjdmPLWO.mjs";
3
+ import { n as warn } from "./logger-hbWOlj_X.mjs";
4
+ import { r as applyToQuery } from "./query-defaults-DwvWUjZU.mjs";
5
5
  import { coerceFilterDates, isFilter } from "@classytic/repo-core/filter";
6
- //#region src/actions/create.ts
6
+ // src/actions/create.ts
7
7
  var create_exports = /* @__PURE__ */ __exportAll({
8
8
  create: () => create,
9
9
  createDefault: () => createDefault,
@@ -17,8 +17,6 @@ var create_exports = /* @__PURE__ */ __exportAll({
17
17
  async function create(Model, data, options = {}) {
18
18
  const document = new Model(data);
19
19
  const saveOptions = { session: options.session };
20
- const bound = options.queryOptions?.maxTimeMS;
21
- if (bound !== void 0) Object.assign(saveOptions, { timeoutMS: bound });
22
20
  const wc = options.queryOptions?.writeConcern;
23
21
  if (wc) Object.assign(saveOptions, { writeConcern: {
24
22
  ...wc.w !== void 0 ? { w: wc.w } : {},
@@ -71,7 +69,7 @@ async function createMany(Model, dataArray, options = {}) {
71
69
  const wc = options.queryOptions?.writeConcern;
72
70
  if (wc) Object.assign(insertOptions, { writeConcern: wc });
73
71
  const bound = options.queryOptions?.maxTimeMS;
74
- if (bound !== void 0) Object.assign(insertOptions, { timeoutMS: bound });
72
+ if (bound !== void 0) Object.assign(insertOptions, { maxTimeMS: bound });
75
73
  return await Model.insertMany(dataArray, insertOptions);
76
74
  } catch (err) {
77
75
  const inserted = err.insertedDocs;
@@ -109,8 +107,7 @@ async function upsert(Model, query, data, options = {}) {
109
107
  ...options.updatePipeline !== void 0 ? { updatePipeline: options.updatePipeline } : {}
110
108
  });
111
109
  }
112
- //#endregion
113
- //#region src/filter/cast-schema.ts
110
+ // src/filter/cast-schema.ts
114
111
  /** Operators whose operand is a single scalar to cast. */
115
112
  const SCALAR_OPS = /* @__PURE__ */ new Set([
116
113
  "$eq",
@@ -225,8 +222,7 @@ function castFilterToSchema(filter, schema) {
225
222
  }
226
223
  return changed ? out : filter;
227
224
  }
228
- //#endregion
229
- //#region src/filter/compile.ts
225
+ // src/filter/compile.ts
230
226
  /**
231
227
  * Compile a Filter IR node to a MongoDB query object. Returns `{}`
232
228
  * when the node is `TRUE` (or an empty `and`) so callers can merge
@@ -464,8 +460,7 @@ function asOperators(condition) {
464
460
  function escapeRegexChar(ch) {
465
461
  return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
466
462
  }
467
- //#endregion
468
- //#region src/query/LookupBuilder.ts
463
+ // src/query/LookupBuilder.ts
469
464
  /** Stages that are never valid inside a $lookup pipeline */
470
465
  const BLOCKED_PIPELINE_STAGES = [
471
466
  "$out",
@@ -852,8 +847,7 @@ function compileSelectToProjection(select) {
852
847
  * - For read-heavy workloads: Consider caching or materialized views
853
848
  * - For real-time dashboards: Consider separate aggregation collections
854
849
  */
855
- //#endregion
856
- //#region src/actions/aggregate.ts
850
+ // src/actions/aggregate.ts
857
851
  var aggregate_exports = /* @__PURE__ */ __exportAll({
858
852
  aggregate: () => aggregate,
859
853
  aggregatePaginate: () => aggregatePaginate,
@@ -1023,8 +1017,7 @@ async function minMax(Model, field, query = {}, options = {}) {
1023
1017
  max: null
1024
1018
  };
1025
1019
  }
1026
- //#endregion
1027
- //#region src/actions/delete.ts
1020
+ // src/actions/delete.ts
1028
1021
  var delete_exports = /* @__PURE__ */ __exportAll({
1029
1022
  deleteById: () => deleteById,
1030
1023
  deleteByQuery: () => deleteByQuery,
@@ -1113,8 +1106,7 @@ async function restore(Model, id, options = {}) {
1113
1106
  id: String(id)
1114
1107
  };
1115
1108
  }
1116
- //#endregion
1117
- //#region src/actions/read.ts
1109
+ // src/actions/read.ts
1118
1110
  var read_exports = /* @__PURE__ */ __exportAll({
1119
1111
  count: () => count,
1120
1112
  exists: () => exists,
@@ -1251,8 +1243,7 @@ async function exists(Model, query, options = {}) {
1251
1243
  applyToQuery(q, options.queryOptions ?? {});
1252
1244
  return q;
1253
1245
  }
1254
- //#endregion
1255
- //#region src/actions/update.ts
1246
+ // src/actions/update.ts
1256
1247
  var update_exports = /* @__PURE__ */ __exportAll({
1257
1248
  findOneAndUpdate: () => findOneAndUpdate,
1258
1249
  increment: () => increment,
@@ -1435,5 +1426,4 @@ async function pushToArray(Model, id, field, value, options = {}) {
1435
1426
  async function pullFromArray(Model, id, field, value, options = {}) {
1436
1427
  return update(Model, id, { $pull: { [field]: value } }, options);
1437
1428
  }
1438
- //#endregion
1439
1429
  export { isCreateManyPartialError as C, create_exports as S, SHORTHAND_OPS as _, count as a, create as b, getByQuery as c, deleteById as d, deleteByQuery as f, LookupBuilder as g, distinct as h, update_exports as i, getOrCreate as l, aggregate_exports as m, update as n, exists as o, delete_exports as p, updateByQuery as r, getById as s, findOneAndUpdate as t, read_exports as u, compileFilterToMongo as v, upsert as w, createMany as x, castFilterToSchema as y };