@classytic/mongokit 3.38.1 → 3.40.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 (51) hide show
  1. package/dist/{PaginationEngine-CBS1XGzp.d.mts → PaginationEngine-D-P19Y9m.d.mts} +34 -9
  2. package/dist/PaginationEngine-Dvl8jcRY.mjs +1198 -0
  3. package/dist/{Repository-Ck5x43wF.mjs → Repository-BMgPSkqZ.mjs} +672 -655
  4. package/dist/{Repository-BdSkQ8vc.d.mts → Repository-CR8Y9g2E.d.mts} +106 -34
  5. package/dist/actions/index.d.mts +1 -1
  6. package/dist/actions/index.mjs +1 -1
  7. package/dist/adapter/index.d.mts +21 -0
  8. package/dist/adapter/index.mjs +1 -1
  9. package/dist/{adapter-Bfqz8yHE.mjs → adapter-BrVfPFSW.mjs} +45 -1
  10. package/dist/ai/index.d.mts +1 -1
  11. package/dist/better-auth/index.mjs +2 -2
  12. package/dist/{create-repository-B5EulQ31.mjs → create-repository-BzBUMvd1.mjs} +3 -3
  13. package/dist/{create-repository-BU4x9Kb0.d.mts → create-repository-CAle1MSU.d.mts} +5 -5
  14. package/dist/idempotency/index.d.mts +23 -0
  15. package/dist/idempotency/index.mjs +134 -0
  16. package/dist/{index-DKEql4cL.d.mts → index-CmjNyulu.d.mts} +67 -8
  17. package/dist/index.d.mts +125 -35
  18. package/dist/index.mjs +286 -676
  19. package/dist/indexes/verify-indexes.d.mts +14 -1
  20. package/dist/indexes/verify-indexes.mjs +54 -7
  21. package/dist/{method-registry.plugin-DblM7V6j.mjs → method-registry.plugin-C63pGn1d.mjs} +54 -4
  22. package/dist/{multi-tenant.plugin-BwSvTirB.d.mts → multi-tenant.plugin-CBgCo9im.d.mts} +77 -5
  23. package/dist/{operations-DZodvjC-.d.mts → operations-CrRIoc8Z.d.mts} +22 -7
  24. package/dist/outbox/index.d.mts +1 -1
  25. package/dist/outbox/index.mjs +7 -5
  26. package/dist/pagination/PaginationEngine.d.mts +1 -1
  27. package/dist/pagination/PaginationEngine.mjs +1 -1
  28. package/dist/pagination/utils/limits.d.mts +1 -1
  29. package/dist/pagination/utils/limits.mjs +22 -1
  30. package/dist/pagination-BpIv2bdS.d.mts +364 -0
  31. package/dist/{plugin-options-DXnlKM3B.d.mts → plugin-options-Zk46lD6F.d.mts} +16 -2
  32. package/dist/plugins/index.d.mts +8 -3
  33. package/dist/plugins/index.mjs +99 -26
  34. package/dist/{repository-H_ffGgpE.d.mts → repository-odl03582.d.mts} +50 -6
  35. package/dist/sync/index.d.mts +40 -0
  36. package/dist/sync/index.mjs +334 -0
  37. package/dist/tenant/index.d.mts +89 -0
  38. package/dist/tenant/index.mjs +96 -0
  39. package/dist/testkit/index.d.mts +2 -2
  40. package/dist/testkit/index.mjs +1 -1
  41. package/dist/{timestamp.plugin-CBQukQzV.mjs → timestamp.plugin-DF0gJB6p.mjs} +116 -9
  42. package/dist/{to-plain-Dyz_qvuo.d.mts → to-plain-k7SjNlSS.d.mts} +2 -2
  43. package/dist/transaction-B5L9glO0.mjs +533 -0
  44. package/dist/{update-jSXp3nu3.mjs → update-DG0fCwRJ.mjs} +293 -83
  45. package/dist/utils/index.d.mts +1 -1
  46. package/dist/{validation-chain.plugin-DhNyxNCw.mjs → validation-chain.plugin-BLY2gLEk.mjs} +192 -279
  47. package/dist/{validation-chain.plugin-yQRob3Av.d.mts → validation-chain.plugin-DYp9E7K2.d.mts} +26 -20
  48. package/package.json +17 -4
  49. package/dist/PaginationEngine-BX9o_-4R.mjs +0 -735
  50. package/dist/core-62oo3N1C.d.mts +0 -50
  51. package/dist/pagination-jSUdrACK.d.mts +0 -158
@@ -1,4 +1,4 @@
1
- import { c as ALL_OPERATIONS, t as HOOK_PRIORITY, u as OP_REGISTRY } from "./Repository-Ck5x43wF.mjs";
1
+ import { i as ALL_OPERATIONS, o as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-BMgPSkqZ.mjs";
2
2
  import { n as warn } from "./logger-K7dPmu2U.mjs";
3
3
  import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
4
4
  import mongoose from "mongoose";
@@ -754,6 +754,33 @@ async function getNextSequence(counterKey, increment = 1, connection, session) {
754
754
  return result.seq;
755
755
  }
756
756
  /**
757
+ * Current value of a counter, or `null` when it has never been created.
758
+ * `null` and `0` differ: only a missing counter needs seeding.
759
+ */
760
+ async function readSequence(counterKey, connection) {
761
+ const doc = await getCounterModel(connection).findById(counterKey).lean();
762
+ return doc ? doc.seq : null;
763
+ }
764
+ /**
765
+ * Raise a counter to at least `floor` — never lowers it. Atomic (`$max` +
766
+ * upsert), so concurrent seeders converge on the highest floor.
767
+ *
768
+ * Use before a counter's first increment when ids were already issued under
769
+ * another key (e.g. moving from a global to a per-tenant sequence); otherwise
770
+ * the new counter restarts at 1 and re-issues taken ids.
771
+ *
772
+ * @returns The counter's value after the raise.
773
+ */
774
+ async function ensureSequenceAtLeast(counterKey, floor, connection) {
775
+ if (!Number.isSafeInteger(floor) || floor < 0) throw new Error(`ensureSequenceAtLeast: floor must be a non-negative integer, got ${floor}`);
776
+ const result = await getCounterModel(connection).findOneAndUpdate({ _id: counterKey }, { $max: { seq: floor } }, {
777
+ upsert: true,
778
+ returnDocument: "after"
779
+ });
780
+ if (!result) throw new Error(`Failed to seed counter '${counterKey}'`);
781
+ return result.seq;
782
+ }
783
+ /**
757
784
  * Generator: Simple sequential counter.
758
785
  * Produces IDs like `INV-0001`, `INV-0002`, etc.
759
786
  *
@@ -795,30 +822,53 @@ function sequentialId(options) {
795
822
  * }),
796
823
  * })
797
824
  * ```
825
+ *
826
+ * @example Per-tenant numbering in a shared database
827
+ * ```typescript
828
+ * customIdPlugin({
829
+ * field: 'orderNumber',
830
+ * generator: dateSequentialId({ prefix: 'ORD', model: OrderModel, scope: 'tenant' }),
831
+ * })
832
+ * // org-a: ORD-2026-02-0001, ORD-2026-02-0002 … org-b: ORD-2026-02-0001 …
833
+ * ```
798
834
  */
799
835
  function dateSequentialId(options) {
800
- const { prefix, model, partition = "monthly", padding = 4, separator = "-" } = options;
836
+ const { prefix, model, partition = "monthly", padding = 4, separator = "-", scope = "global", tenantKey = "organizationId", timezone = "local" } = options;
837
+ /** Per-generator, not module-level — two generators warn independently. */
838
+ const overflowWarned = /* @__PURE__ */ new Set();
839
+ const paddingCeiling = 10 ** padding;
801
840
  return async (context) => {
802
841
  const now = /* @__PURE__ */ new Date();
803
- const year = String(now.getFullYear());
804
- const month = String(now.getMonth() + 1).padStart(2, "0");
805
- const day = String(now.getDate()).padStart(2, "0");
842
+ const utc = timezone === "utc";
843
+ const year = String(utc ? now.getUTCFullYear() : now.getFullYear());
844
+ const month = String((utc ? now.getUTCMonth() : now.getMonth()) + 1).padStart(2, "0");
845
+ const day = String(utc ? now.getUTCDate() : now.getDate()).padStart(2, "0");
846
+ let keyBase = model.modelName;
847
+ if (scope === "tenant") {
848
+ const tenantId = context[tenantKey];
849
+ 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.`);
850
+ keyBase = `${model.modelName}:${String(tenantId)}`;
851
+ }
806
852
  let datePart;
807
853
  let counterKey;
808
854
  switch (partition) {
809
855
  case "yearly":
810
856
  datePart = year;
811
- counterKey = `${model.modelName}:${year}`;
857
+ counterKey = `${keyBase}:${year}`;
812
858
  break;
813
859
  case "daily":
814
860
  datePart = `${year}${separator}${month}${separator}${day}`;
815
- counterKey = `${model.modelName}:${year}-${month}-${day}`;
861
+ counterKey = `${keyBase}:${year}-${month}-${day}`;
816
862
  break;
817
863
  default:
818
864
  datePart = `${year}${separator}${month}`;
819
- counterKey = `${model.modelName}:${year}-${month}`;
865
+ counterKey = `${keyBase}:${year}-${month}`;
820
866
  }
821
867
  const seq = await getNextSequence(counterKey, 1, context._counterConnection, context.session);
868
+ if (seq >= paddingCeiling && !overflowWarned.has(counterKey)) {
869
+ overflowWarned.add(counterKey);
870
+ 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\`.`);
871
+ }
822
872
  return `${prefix}${separator}${datePart}${separator}${String(seq).padStart(padding, "0")}`;
823
873
  };
824
874
  }
@@ -907,6 +957,62 @@ function customIdPlugin(options) {
907
957
  };
908
958
  }
909
959
  //#endregion
960
+ //#region src/utils/scope.ts
961
+ /** Tenant context keys forwarded even when no tenant plugin declared one. */
962
+ const CONVENTIONAL_TENANT_KEYS = ["organizationId", "tenantId"];
963
+ const ATTRIBUTION_KEYS = [
964
+ "session",
965
+ "user",
966
+ "userId",
967
+ "requestId"
968
+ ];
969
+ /** Where a repository records the tenant context keys its plugins read. */
970
+ const TENANT_KEYS = Symbol.for("@classytic/mongokit/tenant-context-keys");
971
+ /**
972
+ * Record that `repo` resolves its tenant from `contextKey`. Called by
973
+ * `multiTenantPlugin` at bind, so forwarding knows a custom key (`branchId`)
974
+ * without the host restating it.
975
+ */
976
+ function declareTenantContextKey(repo, contextKey) {
977
+ const holder = repo;
978
+ if (!holder[TENANT_KEYS]) Object.defineProperty(holder, TENANT_KEYS, {
979
+ value: /* @__PURE__ */ new Set(),
980
+ enumerable: false
981
+ });
982
+ holder[TENANT_KEYS]?.add(contextKey);
983
+ }
984
+ /** Every tenant context key relevant to a call spanning these repositories. */
985
+ function tenantContextKeysOf(...repos) {
986
+ const keys = new Set(CONVENTIONAL_TENANT_KEYS);
987
+ for (const repo of repos) for (const key of repo?.[TENANT_KEYS] ?? []) keys.add(key);
988
+ return [...keys];
989
+ }
990
+ /**
991
+ * The scope to hand a second repository call made on behalf of `source`
992
+ * (a hook's `RepositoryContext`, or a host's request context).
993
+ *
994
+ * Absent values are omitted, never written as `undefined`, so spreading the
995
+ * result into an options bag cannot erase a value set there.
996
+ */
997
+ function forwardScope(source, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
998
+ if (!source) return {};
999
+ const out = {};
1000
+ let hasTenant = false;
1001
+ for (const key of tenantKeys) {
1002
+ const value = source[key];
1003
+ if (value === void 0) continue;
1004
+ out[key] = value;
1005
+ if (value !== null && value !== "") hasTenant = true;
1006
+ }
1007
+ if (!hasTenant && source.bypassTenant === true) out.bypassTenant = true;
1008
+ for (const key of ATTRIBUTION_KEYS) if (source[key] !== void 0) out[key] = source[key];
1009
+ return out;
1010
+ }
1011
+ /** Every key `forwardScope` governs. Other forwarding helpers must not copy these themselves. */
1012
+ function isScopeKey(key, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
1013
+ return key === "bypassTenant" || tenantKeys.includes(key) || ATTRIBUTION_KEYS.includes(key);
1014
+ }
1015
+ //#endregion
910
1016
  //#region src/plugins/multi-tenant.plugin.ts
911
1017
  /**
912
1018
  * Multi-Tenant Plugin
@@ -960,6 +1066,7 @@ function multiTenantPlugin(options = {}) {
960
1066
  return {
961
1067
  name: "multi-tenant",
962
1068
  apply(repo) {
1069
+ declareTenantContextKey(repo, contextKey);
963
1070
  const allOps = [...ALL_OPERATIONS.map((op) => ({
964
1071
  op,
965
1072
  policyKey: OP_REGISTRY[op].policyKey
@@ -1477,4 +1584,4 @@ function timestampPlugin() {
1477
1584
  };
1478
1585
  }
1479
1586
  //#endregion
1480
- export { customIdPlugin as a, prefixedId as c, AuditTrailQuery as d, auditTrailPlugin as f, multiTenantPlugin as i, sequentialId as l, auditLogPlugin as m, softDeletePlugin as n, dateSequentialId as o, ensureAuditTrailReady as p, adminBypass$1 as r, getNextSequence as s, timestampPlugin as t, cachePlugin as u };
1587
+ 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 };
@@ -1,5 +1,5 @@
1
- import { v as UserContext } from "./repository-H_ffGgpE.mjs";
2
- import { r as FieldPreset } from "./plugin-options-DXnlKM3B.mjs";
1
+ import { v as UserContext } from "./repository-odl03582.mjs";
2
+ import { r as FieldPreset } from "./plugin-options-Zk46lD6F.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";