@classytic/primitives 0.20.0 → 0.22.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/CHANGELOG.md +934 -906
  2. package/dist/approval.d.mts +3 -3
  3. package/dist/bank-transaction.d.mts +1 -2
  4. package/dist/{brand-LLXqoh5J.d.mts → brand-Lj_75Y_r.d.mts} +6 -6
  5. package/dist/brand.d.mts +1 -1
  6. package/dist/cadence.d.mts +1 -1
  7. package/dist/cadence.mjs +2 -2
  8. package/dist/calendar.d.mts +1 -1
  9. package/dist/calendar.mjs +2 -2
  10. package/dist/condition.mjs +2 -2
  11. package/dist/context.d.mts +0 -1
  12. package/dist/currency.d.mts +1 -1
  13. package/dist/environment.d.mts +90 -0
  14. package/dist/environment.mjs +53 -0
  15. package/dist/event-infra.d.mts +18 -1
  16. package/dist/idempotency.d.mts +11 -1
  17. package/dist/identity-verification.d.mts +0 -1
  18. package/dist/memory-outbox.d.mts +1 -2
  19. package/dist/memory-outbox.mjs +0 -8
  20. package/dist/mixin.d.mts +1 -1
  21. package/dist/monetization.d.mts +1 -2
  22. package/dist/{money-DPo9fLBi.d.mts → money-CfAvfNiM.d.mts} +1 -2
  23. package/dist/money.d.mts +1 -1
  24. package/dist/otp.mjs +1 -1
  25. package/dist/outbox.d.mts +0 -1
  26. package/dist/payment-allocation-status.d.mts +10 -4
  27. package/dist/payment-allocation-status.mjs +14 -0
  28. package/dist/payment-events.d.mts +2 -3
  29. package/dist/payment-events.mjs +12 -0
  30. package/dist/payment-gateway.d.mts +3 -3
  31. package/dist/payment-method-kind.d.mts +23 -11
  32. package/dist/payment-method-kind.mjs +17 -0
  33. package/dist/period.d.mts +39 -4
  34. package/dist/period.mjs +50 -1
  35. package/dist/person-parse.d.mts +0 -1
  36. package/dist/phone.d.mts +0 -1
  37. package/dist/phone.mjs +3 -3
  38. package/dist/proration.d.mts +1 -2
  39. package/dist/retention.mjs +3 -3
  40. package/dist/shelf-life.mjs +1 -1
  41. package/dist/sla-policy.d.mts +1 -2
  42. package/dist/state-diagram.d.mts +0 -1
  43. package/dist/state-diagram.mjs +1 -1
  44. package/dist/suspension.d.mts +6 -4
  45. package/dist/suspension.mjs +3 -2
  46. package/dist/{timezone-tlG7ygwx.d.mts → timezone-CxUjxmRU.d.mts} +3 -4
  47. package/dist/timezone.d.mts +1 -1
  48. package/dist/unit-cost-rate.d.mts +1 -2
  49. package/package.json +7 -2
  50. /package/dist/{cadence-BFQVC1Qp.d.mts → cadence-5X6TiD49.d.mts} +0 -0
  51. /package/dist/{calendar-B6fm-FbR.d.mts → calendar-DJWSbcgE.d.mts} +0 -0
@@ -83,17 +83,17 @@ interface CreateStepInput {
83
83
  readonly requiredApprovals?: number;
84
84
  readonly threshold?: ApprovalThreshold;
85
85
  }
86
- type ApprovalErrorCode = 'EMPTY_STEPS' | 'DUPLICATE_STEP_ID' | 'EMPTY_APPROVERS' | 'DUPLICATE_APPROVER_ID' | 'INVALID_QUORUM' | 'UNKNOWN_STEP' | 'UNAUTHORIZED_APPROVER' | 'STEP_NOT_ACTIVE' | 'STEP_ALREADY_DECIDED_BY_APPROVER'
86
+ type ApprovalErrorCode = 'EMPTY_STEPS' | 'DUPLICATE_STEP_ID' | 'EMPTY_APPROVERS' | 'DUPLICATE_APPROVER_ID' | 'INVALID_QUORUM' | 'UNKNOWN_STEP' | 'UNAUTHORIZED_APPROVER' | 'STEP_NOT_ACTIVE' | 'STEP_ALREADY_DECIDED_BY_APPROVER' |
87
87
  /** A finalize/commit was attempted while the chain has not reached
88
88
  * `approved` (still `pending`, or `rejected`). The single canonical
89
89
  * "not yet approved" gate — hosts route it through arc-approval's
90
90
  * `rethrowApprovalError` to the `approval.chain_incomplete` wire code. */
91
- | 'CHAIN_INCOMPLETE'
91
+ 'CHAIN_INCOMPLETE' |
92
92
  /** A finalize/commit was attempted with NO chain at all. Distinct from
93
93
  * `CHAIN_INCOMPLETE`: the chain is not unfinished, it is absent — the
94
94
  * document never carried one, or a schema that never declared the field
95
95
  * stripped it on write. See {@link assertApproved}. */
96
- | 'CHAIN_MISSING';
96
+ 'CHAIN_MISSING';
97
97
  declare class ApprovalError extends Error {
98
98
  readonly name = "ApprovalError";
99
99
  readonly code: ApprovalErrorCode;
@@ -1,5 +1,4 @@
1
- import { n as Money } from "./money-DPo9fLBi.mjs";
2
-
1
+ import { n as Money } from "./money-CfAvfNiM.mjs";
3
2
  //#region src/money/bank-transaction.d.ts
4
3
  /**
5
4
  * The other side of a bank transaction. All fields optional — different
@@ -18,19 +18,19 @@ type Brand<T, B extends string> = T & {
18
18
  readonly [__brand]: B;
19
19
  };
20
20
  /** Flattens intersection types for nicer IDE hover output. */
21
- type Prettify<T> = { [K in keyof T]: T[K] } & {};
21
+ type Prettify<T> = { [K in keyof T]: T[K]; } & {};
22
22
  /** Recursive partial — every leaf becomes optional. */
23
- type DeepPartial<T> = T extends ((...args: readonly unknown[]) => unknown) ? T : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
23
+ type DeepPartial<T> = T extends ((...args: readonly unknown[]) => unknown) ? T : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends object ? { [K in keyof T]?: DeepPartial<T[K]>; } : T;
24
24
  /** Recursive readonly — every leaf becomes immutable. */
25
- type DeepReadonly<T> = T extends ((...args: readonly unknown[]) => unknown) ? T : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
25
+ type DeepReadonly<T> = T extends ((...args: readonly unknown[]) => unknown) ? T : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;
26
26
  /** Make specific keys required while leaving the rest as-is. */
27
- type RequireKeys<T, K extends keyof T> = T & { [P in K]-?: T[P] };
27
+ type RequireKeys<T, K extends keyof T> = T & { [P in K]-?: T[P]; };
28
28
  /** Make specific keys optional while leaving the rest as-is. */
29
- type OptionalKeys<T, K extends keyof T> = Omit<T, K> & { [P in K]?: T[P] };
29
+ type OptionalKeys<T, K extends keyof T> = Omit<T, K> & { [P in K]?: T[P]; };
30
30
  /** A value that may be absent in three flavours — `T | null | undefined`. */
31
31
  type Nullable<T> = T | null | undefined;
32
32
  /** Keys of T whose value type is assignable to V. */
33
- type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
33
+ type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never; }[keyof T];
34
34
  /** Non-empty array guard type. */
35
35
  type NonEmptyArray<T> = readonly [T, ...T[]];
36
36
  //#endregion
package/dist/brand.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as NonEmptyArray, c as Prettify, i as KeysMatching, l as RequireKeys, n as DeepPartial, o as Nullable, r as DeepReadonly, s as OptionalKeys, t as Brand } from "./brand-LLXqoh5J.mjs";
1
+ import { a as NonEmptyArray, c as Prettify, i as KeysMatching, l as RequireKeys, n as DeepPartial, o as Nullable, r as DeepReadonly, s as OptionalKeys, t as Brand } from "./brand-Lj_75Y_r.mjs";
2
2
  export { Brand, DeepPartial, DeepReadonly, KeysMatching, NonEmptyArray, Nullable, OptionalKeys, Prettify, RequireKeys };
@@ -1,2 +1,2 @@
1
- import { a as CronCadence, c as MonthlyCadence, d as nextOccurrence, f as occurrencesBetween, i as CadenceKind, l as WeeklyCadence, n as CadenceError, o as DailyCadence, p as validateCadence, r as CadenceErrorCode, s as IsoWeekday, t as Cadence, u as YearlyCadence } from "./cadence-BFQVC1Qp.mjs";
1
+ import { a as CronCadence, c as MonthlyCadence, d as nextOccurrence, f as occurrencesBetween, i as CadenceKind, l as WeeklyCadence, n as CadenceError, o as DailyCadence, p as validateCadence, r as CadenceErrorCode, s as IsoWeekday, t as Cadence, u as YearlyCadence } from "./cadence-5X6TiD49.mjs";
2
2
  export { Cadence, CadenceError, CadenceErrorCode, CadenceKind, CronCadence, DailyCadence, IsoWeekday, MonthlyCadence, WeeklyCadence, YearlyCadence, nextOccurrence, occurrencesBetween, validateCadence };
package/dist/cadence.mjs CHANGED
@@ -200,7 +200,7 @@ function stepYearlyZoned(c, from, zone) {
200
200
  }
201
201
  function stepDaily(c, from) {
202
202
  const start = c.startAt;
203
- const diffDays = Math.floor((from.getTime() - start.getTime()) / (1440 * 60 * 1e3));
203
+ const diffDays = Math.floor((from.getTime() - start.getTime()) / 864e5);
204
204
  const nextNthDay = Math.floor(diffDays / c.interval) * c.interval + c.interval;
205
205
  const nextMs = start.getTime() + nextNthDay * 24 * 60 * 60 * 1e3;
206
206
  return new Date(nextMs);
@@ -208,7 +208,7 @@ function stepDaily(c, from) {
208
208
  function stepWeekly(c, from) {
209
209
  const anchor = c.startAt;
210
210
  const sortedDays = [...c.daysOfWeek ?? [isoWeekday(anchor)]].sort((a, b) => a - b);
211
- const oneDay = 1440 * 60 * 1e3;
211
+ const oneDay = 864e5;
212
212
  const msPerWeek = 7 * oneDay;
213
213
  let cursor = from.getTime() + oneDay;
214
214
  const maxSteps = Math.max(60, c.interval * 14);
@@ -1,2 +1,2 @@
1
- import { a as isoWeekday, i as addYears, n as addDays, o as startOfDay, r as addMonths, s as startOfMonth, t as UtcOffsetMinutes } from "./calendar-B6fm-FbR.mjs";
1
+ import { a as isoWeekday, i as addYears, n as addDays, o as startOfDay, r as addMonths, s as startOfMonth, t as UtcOffsetMinutes } from "./calendar-DJWSbcgE.mjs";
2
2
  export { UtcOffsetMinutes, addDays, addMonths, addYears, isoWeekday, startOfDay, startOfMonth };
package/dist/calendar.mjs CHANGED
@@ -9,13 +9,13 @@ const DAY_MS = 864e5;
9
9
  function startOfDay(instant, offsetMinutes = 0) {
10
10
  const shifted = new Date(instant.getTime() + offsetMinutes * MINUTE_MS);
11
11
  const localMidnightAsUtc = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate());
12
- return new Date(localMidnightAsUtc - offsetMinutes * MINUTE_MS);
12
+ return /* @__PURE__ */ new Date(localMidnightAsUtc - offsetMinutes * MINUTE_MS);
13
13
  }
14
14
  /** The absolute instant of the first-of-month midnight, in the given offset. */
15
15
  function startOfMonth(instant, offsetMinutes = 0) {
16
16
  const shifted = new Date(instant.getTime() + offsetMinutes * MINUTE_MS);
17
17
  const localFirstAsUtc = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), 1);
18
- return new Date(localFirstAsUtc - offsetMinutes * MINUTE_MS);
18
+ return /* @__PURE__ */ new Date(localFirstAsUtc - offsetMinutes * MINUTE_MS);
19
19
  }
20
20
  /** ISO weekday (1 = Monday … 7 = Sunday) of `instant` in the given offset. */
21
21
  function isoWeekday(instant, offsetMinutes = 0) {
@@ -83,7 +83,7 @@ var ConditionError = class extends Error {
83
83
  this.code = code;
84
84
  }
85
85
  };
86
- const COMPARATORS = new Set([
86
+ const COMPARATORS = /* @__PURE__ */ new Set([
87
87
  "eq",
88
88
  "neq",
89
89
  "gt",
@@ -98,7 +98,7 @@ const COMPARATORS = new Set([
98
98
  "isNull",
99
99
  "isNotNull"
100
100
  ]);
101
- const UNARY_OPS = new Set(["isNull", "isNotNull"]);
101
+ const UNARY_OPS = /* @__PURE__ */ new Set(["isNull", "isNotNull"]);
102
102
  /**
103
103
  * Validate a condition shape before persisting. Recurses through composites.
104
104
  * Throws `ConditionError` so hosts get a stable code + message in API
@@ -1,5 +1,4 @@
1
1
  import { IdLike } from "./reference.mjs";
2
-
3
2
  //#region src/composition/context.d.ts
4
3
  /**
5
4
  * Operation context threaded through package APIs.
@@ -1,2 +1,2 @@
1
- import { A as convertWithSnapshot, C as CURRENCY_PATTERN, D as FxSnapshot, E as FxRounding, F as minorUnitFactor, I as reverseWithSnapshot, L as toCurrencyCode, M as isCurrencyCode, N as isFxSnapshot, O as InvalidCurrencyCodeError, P as isKnownCurrency, S as CURRENCIES, T as CurrencyMismatchError, j as currencyCode, k as MINOR_UNIT_FACTOR, w as CurrencyCode } from "./money-DPo9fLBi.mjs";
1
+ import { A as convertWithSnapshot, C as CURRENCY_PATTERN, D as FxSnapshot, E as FxRounding, F as minorUnitFactor, I as reverseWithSnapshot, L as toCurrencyCode, M as isCurrencyCode, N as isFxSnapshot, O as InvalidCurrencyCodeError, P as isKnownCurrency, S as CURRENCIES, T as CurrencyMismatchError, j as currencyCode, k as MINOR_UNIT_FACTOR, w as CurrencyCode } from "./money-CfAvfNiM.mjs";
2
2
  export { CURRENCIES, CURRENCY_PATTERN, CurrencyCode, CurrencyMismatchError, FxRounding, FxSnapshot, InvalidCurrencyCodeError, MINOR_UNIT_FACTOR, convertWithSnapshot, currencyCode, isCurrencyCode, isFxSnapshot, isKnownCurrency, minorUnitFactor, reverseWithSnapshot, toCurrencyCode };
@@ -0,0 +1,90 @@
1
+ //#region src/composition/environment.d.ts
2
+ /**
3
+ * `NODE_ENV` classification — the ONE answer to "which environment is this?".
4
+ *
5
+ * ## Why this is a primitive and not four copies
6
+ *
7
+ * `NODE_ENV` has two accepted spellings per bucket in practice — `production` OR `prod`, `test` OR
8
+ * `qa` — so a local comparison against one literal is wrong for the other, and it is wrong in the
9
+ * expensive direction. Almost every call site computes a NEGATIVE (`!== 'production'`,
10
+ * `isProduction === false`), so a missed spelling does not close a gate, it OPENS one.
11
+ *
12
+ * That is not hypothetical. Every item below was found in shipped code, and **not one of them
13
+ * threw**:
14
+ *
15
+ * | site | what `NODE_ENV=prod` did |
16
+ * |---|---|
17
+ * | `arc/plugins/errorHandler.ts` | returned the RAW thrown message of a 500 to the client |
18
+ * | `arc/auth/sessionManager.ts` | issued session cookies **without `Secure`** |
19
+ * | gym: pos / revenue / membership / order / catalog / attendance | permitted NON-TRANSACTIONAL money writes in production |
20
+ * | gym `app.ts` | selected arc's DEVELOPMENT preset in production |
21
+ * | be-prod `media.engine.ts` | enabled the in-memory media driver fallback — uploads 200, files gone on restart |
22
+ * | be-prod `contract.ports.ts`, `crm.module.ts` | non-atomic receivable / lead-conversion writes |
23
+ * | be-prod `logger.ts` | pino's pretty TRANSPORT in production |
24
+ * | be-prod `guest-order.resource.ts` | rate limiter armed under `qa` and nowhere else |
25
+ *
26
+ * `qa` had the mirror problem: classified as development by one host and test by another, so
27
+ * multi-boot support silently vanished on a QA run.
28
+ *
29
+ * **This logic already existed four times** — be-prod `classifyEnv`, gym `businessMode()`, arc's own
30
+ * CLI config template (as a `z.preprocess`), and implicitly in arc's four raw comparison sites. All
31
+ * four agreed on the spellings and the buckets, which is exactly why collapsing them is mechanical
32
+ * rather than a behaviour change. A fifth copy was one new vertical away.
33
+ *
34
+ * ## Why primitives owns it
35
+ *
36
+ * arc's own ownership rule: *primitives owns pure cross-package contracts, arc owns runtime.* This
37
+ * is a pure total function with no dependencies, and its consumers are not all arc apps — kernels,
38
+ * workers, CLIs and the spine need the same answer without pulling in a web framework. primitives
39
+ * is already a required peer of arc, so every arc host has it at no additional cost.
40
+ *
41
+ * ## Prefer an EXPLICIT signal where one exists
42
+ *
43
+ * If a caller already has a declared environment — arc's `preset`, a `deployment.config`, an
44
+ * injected flag — **use that instead**. It is a specific instruction, and this function reads an
45
+ * ambient default. A specific setting must never be overridden by a general one.
46
+ */
47
+ /** The three buckets everything downstream reasons about. Mutually exclusive and total. */
48
+ type EnvClass = 'development' | 'test' | 'production';
49
+ /**
50
+ * Classify a raw `NODE_ENV` value.
51
+ *
52
+ * Total by construction — every input yields exactly one bucket, so a caller can never observe a
53
+ * fourth state or `undefined`.
54
+ *
55
+ * ## Whitespace IS trimmed, and that is a deliberate divergence from the four copies
56
+ *
57
+ * None of the previous implementations trimmed. A value carrying a trailing space or newline —
58
+ * routine from a `.env` file, a Docker `ENV` line, or a CI variable pasted with a line ending —
59
+ * therefore matched nothing and fell through to `development`. That is the dangerous direction: a
60
+ * genuine production deployment classified as development, which is precisely the failure this
61
+ * function exists to prevent. Trimming can only ever move a value TOWARD the bucket its author
62
+ * plainly intended.
63
+ *
64
+ * ## An unrecognised value falls back to `development`
65
+ *
66
+ * Preserved from all four prior implementations, so adopting this changes no existing behaviour.
67
+ *
68
+ * Be aware of what it costs: a typo (`prodction`) classifies as development and therefore OPENS the
69
+ * gates a production deployment wanted shut. The stricter alternative — normalise known aliases and
70
+ * THROW on anything else — is more defensible ("an input you do not understand must fail, not
71
+ * widen"), but it is a breaking operational change for any deployment currently running an
72
+ * unrecognised value and belongs in its own release with its own test. Do not change it here
73
+ * silently.
74
+ */
75
+ declare function classifyEnv(env: string | undefined | null): EnvClass;
76
+ /**
77
+ * `true` when the value classifies as production.
78
+ *
79
+ * A named predicate rather than a `classifyEnv(x) === 'production'` at each call site: a literal
80
+ * comparison against an env string is the exact shape that caused every defect listed above, so
81
+ * this module should not contain one — even a correct one — for the next reader to copy.
82
+ *
83
+ * Deliberately the ONLY predicate. `isTest` / `isDevelopment` were added and removed the same day:
84
+ * they had zero importers, because consumers that need all three buckets build their own triple
85
+ * from `classifyEnv` (be-prod's `config.*`, gym's `businessMode()`). Speculative surface on a
86
+ * published package is not free — it has to be supported, and it invited a needless version bump.
87
+ */
88
+ declare function isProductionEnv(env: string | undefined | null): boolean;
89
+ //#endregion
90
+ export { EnvClass, classifyEnv, isProductionEnv };
@@ -0,0 +1,53 @@
1
+ //#region src/composition/environment.ts
2
+ /**
3
+ * Classify a raw `NODE_ENV` value.
4
+ *
5
+ * Total by construction — every input yields exactly one bucket, so a caller can never observe a
6
+ * fourth state or `undefined`.
7
+ *
8
+ * ## Whitespace IS trimmed, and that is a deliberate divergence from the four copies
9
+ *
10
+ * None of the previous implementations trimmed. A value carrying a trailing space or newline —
11
+ * routine from a `.env` file, a Docker `ENV` line, or a CI variable pasted with a line ending —
12
+ * therefore matched nothing and fell through to `development`. That is the dangerous direction: a
13
+ * genuine production deployment classified as development, which is precisely the failure this
14
+ * function exists to prevent. Trimming can only ever move a value TOWARD the bucket its author
15
+ * plainly intended.
16
+ *
17
+ * ## An unrecognised value falls back to `development`
18
+ *
19
+ * Preserved from all four prior implementations, so adopting this changes no existing behaviour.
20
+ *
21
+ * Be aware of what it costs: a typo (`prodction`) classifies as development and therefore OPENS the
22
+ * gates a production deployment wanted shut. The stricter alternative — normalise known aliases and
23
+ * THROW on anything else — is more defensible ("an input you do not understand must fail, not
24
+ * widen"), but it is a breaking operational change for any deployment currently running an
25
+ * unrecognised value and belongs in its own release with its own test. Do not change it here
26
+ * silently.
27
+ */
28
+ function classifyEnv(env) {
29
+ switch ((env ?? "").trim().toLowerCase()) {
30
+ case "production":
31
+ case "prod": return "production";
32
+ case "test":
33
+ case "qa": return "test";
34
+ default: return "development";
35
+ }
36
+ }
37
+ /**
38
+ * `true` when the value classifies as production.
39
+ *
40
+ * A named predicate rather than a `classifyEnv(x) === 'production'` at each call site: a literal
41
+ * comparison against an env string is the exact shape that caused every defect listed above, so
42
+ * this module should not contain one — even a correct one — for the next reader to copy.
43
+ *
44
+ * Deliberately the ONLY predicate. `isTest` / `isDevelopment` were added and removed the same day:
45
+ * they had zero importers, because consumers that need all three buckets build their own triple
46
+ * from `classifyEnv` (be-prod's `config.*`, gym's `businessMode()`). Speculative surface on a
47
+ * published package is not free — it has to be supported, and it invited a needless version bump.
48
+ */
49
+ function isProductionEnv(env) {
50
+ return classifyEnv(env) === "production";
51
+ }
52
+ //#endregion
53
+ export { classifyEnv, isProductionEnv };
@@ -1,5 +1,4 @@
1
1
  import { DomainEvent, EventHandler, EventTransport, PublishManyResult } from "./events.mjs";
2
-
3
2
  //#region src/events/event-infra.d.ts
4
3
  /** Minimal error-only logger — assignable from `console`, pino, fastify.log. */
5
4
  interface ErrorLogger {
@@ -85,5 +84,23 @@ declare class InProcessEventBus implements EventTransport {
85
84
  }
86
85
  /** Factory form of {@link InProcessEventBus} for hosts that prefer functions. */
87
86
  declare function createInProcessBus(options?: InProcessEventBusOptions): InProcessEventBus;
87
+ /**
88
+ * `MemoryOutboxStore` does NOT live here. It is exported from exactly one
89
+ * place: `@classytic/primitives/memory-outbox`.
90
+ *
91
+ * This module used to also export it — first as a second, MINIMAL
92
+ * implementation (save / getPending / acknowledge / purge, no leases, no
93
+ * retry, no dead-lettering, no dedupe, no validation), then briefly as a
94
+ * back-compat re-export. Both are gone.
95
+ *
96
+ * The re-export was tempting: seven kernels imported it from here, and one
97
+ * line would have spared seven rebuilds. It was still the wrong call. Two live
98
+ * import paths for one class means new code picks either, the migration stays
99
+ * permanently half-done, and the next person has to learn which path is
100
+ * "real". The cost of the move is seven rebuilds; paying it once is cheaper
101
+ * than carrying a shim forever.
102
+ *
103
+ * This subpath is the in-process BUS. The outbox is its own subpath.
104
+ */
88
105
  //#endregion
89
106
  export { ErrorLogger, InProcessEventBus, InProcessEventBusOptions, createInProcessBus };
@@ -121,7 +121,17 @@ interface IdempotencyClaim<TValue = unknown> {
121
121
  /** Monotonic attempt counter — each successful takeover increments it. */
122
122
  readonly attempts: number;
123
123
  }
124
- type IdempotencyErrorCode = /** Same key, different request body. Never a retry — two commands sharing one key. */'FINGERPRINT_MISMATCH' /** A terminal claim carrying no stored result. Unreplayable; must not answer "ok". */ | 'MISSING_REPLAY' /** The attempt no longer owns the lease — another attempt took over. */ | 'LEASE_LOST' /** Completing / renewing a claim that already reached a terminal state. */ | 'ALREADY_TERMINAL' /** Malformed input (empty key, non-positive lease, invalid instant). */ | 'INVALID_CLAIM';
124
+ type IdempotencyErrorCode =
125
+ /** Same key, different request body. Never a retry — two commands sharing one key. */
126
+ 'FINGERPRINT_MISMATCH' |
127
+ /** A terminal claim carrying no stored result. Unreplayable; must not answer "ok". */
128
+ 'MISSING_REPLAY' |
129
+ /** The attempt no longer owns the lease — another attempt took over. */
130
+ 'LEASE_LOST' |
131
+ /** Completing / renewing a claim that already reached a terminal state. */
132
+ 'ALREADY_TERMINAL' |
133
+ /** Malformed input (empty key, non-positive lease, invalid instant). */
134
+ 'INVALID_CLAIM';
125
135
  declare class IdempotencyError extends Error {
126
136
  readonly name = "IdempotencyError";
127
137
  readonly code: IdempotencyErrorCode;
@@ -1,5 +1,4 @@
1
1
  import { PersonName } from "./person.mjs";
2
-
3
2
  //#region src/identity/identity-verification.d.ts
4
3
  /**
5
4
  * Government / institutional identity documents. `trade_license` and
@@ -1,10 +1,9 @@
1
1
  import { DeadLetteredEvent, DomainEvent } from "./events.mjs";
2
2
  import { OutboxAcknowledgeOptions, OutboxClaimOptions, OutboxErrorInfo, OutboxFailOptions, OutboxStatus, OutboxStore, OutboxWriteOptions } from "./outbox.mjs";
3
-
4
3
  //#region src/events/memory-outbox.d.ts
5
4
  interface MemoryEntry {
6
5
  event: DomainEvent;
7
- status: "pending" | "delivered" | "dead_letter";
6
+ status: 'pending' | 'delivered' | 'dead_letter';
8
7
  attempts: number;
9
8
  visibleAt: number;
10
9
  leaseOwner: string | null;
@@ -1,13 +1,5 @@
1
1
  import { InvalidOutboxEventError, OutboxOwnershipError } from "./outbox.mjs";
2
2
  //#region src/events/memory-outbox.ts
3
- /**
4
- * In-memory outbox store — reference implementation for dev / testing.
5
- *
6
- * Supports the full `OutboxStore` capability set (claim/lease, fail/retry,
7
- * dedupe, visibleAt). Production deployments should pass a `repository` to
8
- * `EventOutbox` instead of using this; the memory store exists for unit
9
- * tests and single-process dev where durable persistence is noise.
10
- */
11
3
  const DEFAULT_LEASE_MS = 3e4;
12
4
  var MemoryOutboxStore = class {
13
5
  entries = [];
package/dist/mixin.d.mts CHANGED
@@ -45,7 +45,7 @@ interface Mixinable {
45
45
  * mixin payload through a well-typed accessor.
46
46
  */
47
47
  type WithMixin<T, K extends string, M> = T & {
48
- readonly mixins: Readonly<Record<string, unknown>> & { readonly [P in K]: M };
48
+ readonly mixins: Readonly<Record<string, unknown>> & { readonly [P in K]: M; };
49
49
  };
50
50
  /**
51
51
  * Attach (or overwrite) a mixin on a base entity. Returns a NEW object —
@@ -1,5 +1,4 @@
1
- import { n as Money } from "./money-DPo9fLBi.mjs";
2
-
1
+ import { n as Money } from "./money-CfAvfNiM.mjs";
3
2
  //#region src/money/monetization.d.ts
4
3
  /**
5
4
  * A map of currency code → {@link Money} for explicit per-currency pricing.
@@ -1,5 +1,4 @@
1
- import { t as Brand } from "./brand-LLXqoh5J.mjs";
2
-
1
+ import { t as Brand } from "./brand-Lj_75Y_r.mjs";
3
2
  //#region src/money/currency.d.ts
4
3
  /**
5
4
  * ISO 4217 currency code (3 uppercase letters).
package/dist/money.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { T as CurrencyMismatchError, _ as negateMoney, a as compareMoney, b as sumMoney, c as fromMajor, d as isPositiveMoney, f as isZeroMoney, g as multiplyMoney, h as money, i as addMoney, l as isMoney, m as minorUnitsToMajor, n as Money, o as equalsMoney, p as majorToMinorUnits, r as absMoney, s as formatMinorUnits, t as MinorUnits, u as isNegativeMoney, v as percentOfMinor, x as toMajor, y as subtractMoney } from "./money-DPo9fLBi.mjs";
1
+ import { T as CurrencyMismatchError, _ as negateMoney, a as compareMoney, b as sumMoney, c as fromMajor, d as isPositiveMoney, f as isZeroMoney, g as multiplyMoney, h as money, i as addMoney, l as isMoney, m as minorUnitsToMajor, n as Money, o as equalsMoney, p as majorToMinorUnits, r as absMoney, s as formatMinorUnits, t as MinorUnits, u as isNegativeMoney, v as percentOfMinor, x as toMajor, y as subtractMoney } from "./money-CfAvfNiM.mjs";
2
2
  export { CurrencyMismatchError, MinorUnits, Money, absMoney, addMoney, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
package/dist/otp.mjs CHANGED
@@ -26,7 +26,7 @@ import { createHmac, randomInt, timingSafeEqual } from "node:crypto";
26
26
  * modeled here so the verify primitive stays stateless between attempts.
27
27
  */
28
28
  const DEFAULT_LENGTH = 6;
29
- const DEFAULT_TTL_MS = 300 * 1e3;
29
+ const DEFAULT_TTL_MS = 3e5;
30
30
  const DEFAULT_MAX_ATTEMPTS = 5;
31
31
  function hmac(code, secret) {
32
32
  return createHmac("sha256", secret).update(code).digest("hex");
package/dist/outbox.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { DeadLetteredEvent, DomainEvent } from "./events.mjs";
2
-
3
2
  //#region src/events/outbox.d.ts
4
3
  /**
5
4
  * Options passed to {@link OutboxStore.save} for richer write semantics.
@@ -20,8 +20,11 @@
20
20
  * Zero runtime, zero deps.
21
21
  */
22
22
  declare const PAYMENT_ALLOCATION_STATUS: {
23
- /** No portion of the payment has been applied to any obligation yet. */readonly UNALLOCATED: "unallocated"; /** Some of the payment is applied; remainder is available credit. */
24
- readonly PARTIAL: "partial"; /** Fully applied — `sum(allocations) === payment.totalAmount`. */
23
+ /** No portion of the payment has been applied to any obligation yet. */
24
+ readonly UNALLOCATED: "unallocated";
25
+ /** Some of the payment is applied; remainder is available credit. */
26
+ readonly PARTIAL: "partial";
27
+ /** Fully applied — `sum(allocations) === payment.totalAmount`. */
25
28
  readonly ALLOCATED: "allocated";
26
29
  /**
27
30
  * Sum of allocations exceeds the payment total — invariant violation
@@ -31,8 +34,11 @@ declare const PAYMENT_ALLOCATION_STATUS: {
31
34
  };
32
35
  type PaymentAllocationStatus = (typeof PAYMENT_ALLOCATION_STATUS)[keyof typeof PAYMENT_ALLOCATION_STATUS];
33
36
  declare const RECONCILIATION_STATUS: {
34
- /** No bank statement line / settlement record has been linked yet. */readonly UNRECONCILED: "unreconciled"; /** Auto-matched to a `BankTransaction` by amount + date + reference. */
35
- readonly MATCHED: "matched"; /** Operator-confirmed match (auto-match failed or was overridden). */
37
+ /** No bank statement line / settlement record has been linked yet. */
38
+ readonly UNRECONCILED: "unreconciled";
39
+ /** Auto-matched to a `BankTransaction` by amount + date + reference. */
40
+ readonly MATCHED: "matched";
41
+ /** Operator-confirmed match (auto-match failed or was overridden). */
36
42
  readonly MANUAL: "manual";
37
43
  /**
38
44
  * Match was contested — amount mismatch, duplicate claim, chargeback
@@ -20,15 +20,29 @@
20
20
  * Zero runtime, zero deps.
21
21
  */
22
22
  const PAYMENT_ALLOCATION_STATUS = {
23
+ /** No portion of the payment has been applied to any obligation yet. */
23
24
  UNALLOCATED: "unallocated",
25
+ /** Some of the payment is applied; remainder is available credit. */
24
26
  PARTIAL: "partial",
27
+ /** Fully applied — `sum(allocations) === payment.totalAmount`. */
25
28
  ALLOCATED: "allocated",
29
+ /**
30
+ * Sum of allocations exceeds the payment total — invariant violation
31
+ * that should be flagged for operator review, never silently accepted.
32
+ */
26
33
  OVER_ALLOCATED: "over_allocated"
27
34
  };
28
35
  const RECONCILIATION_STATUS = {
36
+ /** No bank statement line / settlement record has been linked yet. */
29
37
  UNRECONCILED: "unreconciled",
38
+ /** Auto-matched to a `BankTransaction` by amount + date + reference. */
30
39
  MATCHED: "matched",
40
+ /** Operator-confirmed match (auto-match failed or was overridden). */
31
41
  MANUAL: "manual",
42
+ /**
43
+ * Match was contested — amount mismatch, duplicate claim, chargeback
44
+ * in flight. Halts further automated processing until resolved.
45
+ */
32
46
  DISPUTED: "disputed"
33
47
  };
34
48
  const ALLOCATION_VALUES = new Set(Object.values(PAYMENT_ALLOCATION_STATUS));
@@ -1,7 +1,6 @@
1
- import { n as Money } from "./money-DPo9fLBi.mjs";
1
+ import { n as Money } from "./money-CfAvfNiM.mjs";
2
2
  import { PaymentMethodKind } from "./payment-method-kind.mjs";
3
3
  import { ProviderUnknownCause } from "./payment-gateway.mjs";
4
-
5
4
  //#region src/events/payment-events.d.ts
6
5
  /**
7
6
  * Canonical event-name constants. Packages SHOULD publish events
@@ -378,7 +377,7 @@ type _NoStrayMapKeys = Assert<keyof PaymentEventPayloadMap extends PaymentEventT
378
377
  /** Each payload's own `eventType` literal matches the key it is filed under. */
379
378
  type _DiscriminantMatchesKey = Assert<{ [K in keyof PaymentEventPayloadMap]: PaymentEventPayloadMap[K] extends {
380
379
  eventType: K;
381
- } ? true : false }[keyof PaymentEventPayloadMap] extends true ? true : false>;
380
+ } ? true : false; }[keyof PaymentEventPayloadMap] extends true ? true : false>;
382
381
  type PaymentEventContractGuards = [_EveryTypeMapped, _NoStrayMapKeys, _DiscriminantMatchesKey];
383
382
  /**
384
383
  * Narrow a payload by event type at RUNTIME, with the compile-time narrowing
@@ -18,6 +18,18 @@ const PAYMENT_EVENT_TYPE = {
18
18
  DISPUTE_WON: "payment.dispute_won",
19
19
  DISPUTE_LOST: "payment.dispute_lost",
20
20
  SETTLED: "payment.settled",
21
+ /**
22
+ * The provider command produced NO observable outcome — timeout, abort,
23
+ * network fault, or a provider that holds no record and refuses to guess.
24
+ *
25
+ * This member exists because the port contract (`ProviderCommandResult` in
26
+ * `/payment-gateway`) is three-valued — `confirmed | declined | unknown` —
27
+ * while this catalogue was two-valued. An adapter that hit the third case had
28
+ * nowhere to publish it, so the only shapes on the bus were SUCCEEDED and
29
+ * FAILED, and an unobserved outcome had to be forced into one of them.
30
+ * Forcing it into FAILED is the one that double-charges: it licenses a retry
31
+ * for a command that may already have moved money.
32
+ */
21
33
  UNKNOWN: "payment.unknown"
22
34
  };
23
35
  /**
@@ -1,6 +1,5 @@
1
- import { n as Money } from "./money-DPo9fLBi.mjs";
1
+ import { n as Money } from "./money-CfAvfNiM.mjs";
2
2
  import { PaymentMethodKind } from "./payment-method-kind.mjs";
3
-
4
3
  //#region src/money/payment-gateway.d.ts
5
4
  /**
6
5
  * Static capability declaration — providers expose this so the engine
@@ -363,7 +362,8 @@ type ProviderCommandResult<T> = {
363
362
  outcome: 'declined';
364
363
  error: PaymentDecline;
365
364
  } | {
366
- outcome: 'unknown'; /** Whatever handle we have for asking again later. */
365
+ outcome: 'unknown';
366
+ /** Whatever handle we have for asking again later. */
367
367
  providerReference?: string;
368
368
  /**
369
369
  * NORMALIZED reason we do not know — never a raw exception message.
@@ -23,23 +23,35 @@
23
23
  * "what kind of payment was this" needs to be typed.
24
24
  */
25
25
  declare const PAYMENT_METHOD_KIND: {
26
- /** Credit / debit card — Visa, Mastercard, Amex, RuPay, UnionPay, … */readonly CARD: "card"; /** Standard bank transfer — ACH credit, SEPA credit, IMPS, NEFT, RTGS, wire. */
27
- readonly BANK_TRANSFER: "bank_transfer"; /** Real-time bank rails — UPI, Pix, FedNow, PromptPay, FPX, GrabPay rails. */
28
- readonly INSTANT_BANK_TRANSFER: "instant_bank_transfer"; /** Mandate-based bank pull — SEPA Direct Debit, ACH Debit, BACS Direct Debit, AU BECS. */
29
- readonly DIRECT_DEBIT: "direct_debit"; /** Digital wallet — Apple Pay, Google Pay, Link, PayPal, Cashapp, Alipay, WeChat Pay, Revolut Pay. */
30
- readonly WALLET: "wallet"; /** Cash-in/out wallet infrastructure in unbanked markets — bKash, Nagad, M-Pesa, MoMo, GCash. */
31
- readonly MOBILE_MONEY: "mobile_money"; /** Buy now pay later — Klarna, Afterpay, Affirm, Tabby, Tamara. */
32
- readonly BNPL: "bnpl"; /** Customer-purchased prepaid credit / voucher. */
33
- readonly GIFT_CARD: "gift_card"; /** Physical cash. */
34
- readonly CASH: "cash"; /** Paper cheque. */
35
- readonly CHEQUE: "cheque"; /** Cryptocurrency settlement. */
26
+ /** Credit / debit card — Visa, Mastercard, Amex, RuPay, UnionPay, … */
27
+ readonly CARD: "card";
28
+ /** Standard bank transfer — ACH credit, SEPA credit, IMPS, NEFT, RTGS, wire. */
29
+ readonly BANK_TRANSFER: "bank_transfer";
30
+ /** Real-time bank rails — UPI, Pix, FedNow, PromptPay, FPX, GrabPay rails. */
31
+ readonly INSTANT_BANK_TRANSFER: "instant_bank_transfer";
32
+ /** Mandate-based bank pull — SEPA Direct Debit, ACH Debit, BACS Direct Debit, AU BECS. */
33
+ readonly DIRECT_DEBIT: "direct_debit";
34
+ /** Digital wallet — Apple Pay, Google Pay, Link, PayPal, Cashapp, Alipay, WeChat Pay, Revolut Pay. */
35
+ readonly WALLET: "wallet";
36
+ /** Cash-in/out wallet infrastructure in unbanked markets — bKash, Nagad, M-Pesa, MoMo, GCash. */
37
+ readonly MOBILE_MONEY: "mobile_money";
38
+ /** Buy now pay later — Klarna, Afterpay, Affirm, Tabby, Tamara. */
39
+ readonly BNPL: "bnpl";
40
+ /** Customer-purchased prepaid credit / voucher. */
41
+ readonly GIFT_CARD: "gift_card";
42
+ /** Physical cash. */
43
+ readonly CASH: "cash";
44
+ /** Paper cheque. */
45
+ readonly CHEQUE: "cheque";
46
+ /** Cryptocurrency settlement. */
36
47
  readonly CRYPTO: "cryptocurrency";
37
48
  /**
38
49
  * Operator-recorded settlement that doesn't fit any other kind — e.g.
39
50
  * an offline bank deposit confirmed by phone, an in-kind exchange, a
40
51
  * manual write-off. Always-valid escape hatch.
41
52
  */
42
- readonly MANUAL: "manual"; /** Unknown or vendor-specific kind — last resort. Prefer a specific kind. */
53
+ readonly MANUAL: "manual";
54
+ /** Unknown or vendor-specific kind — last resort. Prefer a specific kind. */
43
55
  readonly OTHER: "other";
44
56
  };
45
57
  type PaymentMethodKind = (typeof PAYMENT_METHOD_KIND)[keyof typeof PAYMENT_METHOD_KIND];
@@ -23,18 +23,35 @@
23
23
  * "what kind of payment was this" needs to be typed.
24
24
  */
25
25
  const PAYMENT_METHOD_KIND = {
26
+ /** Credit / debit card — Visa, Mastercard, Amex, RuPay, UnionPay, … */
26
27
  CARD: "card",
28
+ /** Standard bank transfer — ACH credit, SEPA credit, IMPS, NEFT, RTGS, wire. */
27
29
  BANK_TRANSFER: "bank_transfer",
30
+ /** Real-time bank rails — UPI, Pix, FedNow, PromptPay, FPX, GrabPay rails. */
28
31
  INSTANT_BANK_TRANSFER: "instant_bank_transfer",
32
+ /** Mandate-based bank pull — SEPA Direct Debit, ACH Debit, BACS Direct Debit, AU BECS. */
29
33
  DIRECT_DEBIT: "direct_debit",
34
+ /** Digital wallet — Apple Pay, Google Pay, Link, PayPal, Cashapp, Alipay, WeChat Pay, Revolut Pay. */
30
35
  WALLET: "wallet",
36
+ /** Cash-in/out wallet infrastructure in unbanked markets — bKash, Nagad, M-Pesa, MoMo, GCash. */
31
37
  MOBILE_MONEY: "mobile_money",
38
+ /** Buy now pay later — Klarna, Afterpay, Affirm, Tabby, Tamara. */
32
39
  BNPL: "bnpl",
40
+ /** Customer-purchased prepaid credit / voucher. */
33
41
  GIFT_CARD: "gift_card",
42
+ /** Physical cash. */
34
43
  CASH: "cash",
44
+ /** Paper cheque. */
35
45
  CHEQUE: "cheque",
46
+ /** Cryptocurrency settlement. */
36
47
  CRYPTO: "cryptocurrency",
48
+ /**
49
+ * Operator-recorded settlement that doesn't fit any other kind — e.g.
50
+ * an offline bank deposit confirmed by phone, an in-kind exchange, a
51
+ * manual write-off. Always-valid escape hatch.
52
+ */
37
53
  MANUAL: "manual",
54
+ /** Unknown or vendor-specific kind — last resort. Prefer a specific kind. */
38
55
  OTHER: "other"
39
56
  };
40
57
  const KIND_VALUES = new Set(Object.values(PAYMENT_METHOD_KIND));