@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.
- package/CHANGELOG.md +934 -906
- package/dist/approval.d.mts +3 -3
- package/dist/bank-transaction.d.mts +1 -2
- package/dist/{brand-LLXqoh5J.d.mts → brand-Lj_75Y_r.d.mts} +6 -6
- package/dist/brand.d.mts +1 -1
- package/dist/cadence.d.mts +1 -1
- package/dist/cadence.mjs +2 -2
- package/dist/calendar.d.mts +1 -1
- package/dist/calendar.mjs +2 -2
- package/dist/condition.mjs +2 -2
- package/dist/context.d.mts +0 -1
- package/dist/currency.d.mts +1 -1
- package/dist/environment.d.mts +90 -0
- package/dist/environment.mjs +53 -0
- package/dist/event-infra.d.mts +18 -1
- package/dist/idempotency.d.mts +11 -1
- package/dist/identity-verification.d.mts +0 -1
- package/dist/memory-outbox.d.mts +1 -2
- package/dist/memory-outbox.mjs +0 -8
- package/dist/mixin.d.mts +1 -1
- package/dist/monetization.d.mts +1 -2
- package/dist/{money-DPo9fLBi.d.mts → money-CfAvfNiM.d.mts} +1 -2
- package/dist/money.d.mts +1 -1
- package/dist/otp.mjs +1 -1
- package/dist/outbox.d.mts +0 -1
- package/dist/payment-allocation-status.d.mts +10 -4
- package/dist/payment-allocation-status.mjs +14 -0
- package/dist/payment-events.d.mts +2 -3
- package/dist/payment-events.mjs +12 -0
- package/dist/payment-gateway.d.mts +3 -3
- package/dist/payment-method-kind.d.mts +23 -11
- package/dist/payment-method-kind.mjs +17 -0
- package/dist/period.d.mts +39 -4
- package/dist/period.mjs +50 -1
- package/dist/person-parse.d.mts +0 -1
- package/dist/phone.d.mts +0 -1
- package/dist/phone.mjs +3 -3
- package/dist/proration.d.mts +1 -2
- package/dist/retention.mjs +3 -3
- package/dist/shelf-life.mjs +1 -1
- package/dist/sla-policy.d.mts +1 -2
- package/dist/state-diagram.d.mts +0 -1
- package/dist/state-diagram.mjs +1 -1
- package/dist/suspension.d.mts +6 -4
- package/dist/suspension.mjs +3 -2
- package/dist/{timezone-tlG7ygwx.d.mts → timezone-CxUjxmRU.d.mts} +3 -4
- package/dist/timezone.d.mts +1 -1
- package/dist/unit-cost-rate.d.mts +1 -2
- package/package.json +7 -2
- /package/dist/{cadence-BFQVC1Qp.d.mts → cadence-5X6TiD49.d.mts} +0 -0
- /package/dist/{calendar-B6fm-FbR.d.mts → calendar-DJWSbcgE.d.mts} +0 -0
package/dist/approval.d.mts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
96
|
+
'CHAIN_MISSING';
|
|
97
97
|
declare class ApprovalError extends Error {
|
|
98
98
|
readonly name = "ApprovalError";
|
|
99
99
|
readonly code: ApprovalErrorCode;
|
|
@@ -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]
|
|
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]
|
|
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-
|
|
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 };
|
package/dist/cadence.d.mts
CHANGED
|
@@ -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-
|
|
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()) /
|
|
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 =
|
|
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);
|
package/dist/calendar.d.mts
CHANGED
|
@@ -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-
|
|
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) {
|
package/dist/condition.mjs
CHANGED
|
@@ -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
|
package/dist/context.d.mts
CHANGED
package/dist/currency.d.mts
CHANGED
|
@@ -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-
|
|
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 };
|
package/dist/event-infra.d.mts
CHANGED
|
@@ -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 };
|
package/dist/idempotency.d.mts
CHANGED
|
@@ -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 =
|
|
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;
|
package/dist/memory-outbox.d.mts
CHANGED
|
@@ -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:
|
|
6
|
+
status: 'pending' | 'delivered' | 'dead_letter';
|
|
8
7
|
attempts: number;
|
|
9
8
|
visibleAt: number;
|
|
10
9
|
leaseOwner: string | null;
|
package/dist/memory-outbox.mjs
CHANGED
|
@@ -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 —
|
package/dist/monetization.d.mts
CHANGED
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-
|
|
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 =
|
|
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
|
@@ -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. */
|
|
24
|
-
readonly
|
|
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. */
|
|
35
|
-
readonly
|
|
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-
|
|
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
|
package/dist/payment-events.mjs
CHANGED
|
@@ -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-
|
|
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';
|
|
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, … */
|
|
27
|
-
readonly
|
|
28
|
-
|
|
29
|
-
readonly
|
|
30
|
-
|
|
31
|
-
readonly
|
|
32
|
-
|
|
33
|
-
readonly
|
|
34
|
-
|
|
35
|
-
readonly
|
|
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";
|
|
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));
|