@classytic/primitives 0.20.0 → 0.21.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 +14 -0
- 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 +85 -0
- package/dist/environment.mjs +48 -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 +0 -1
- 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 +14 -3
- 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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.21.0] - 2026-08-10
|
|
7
|
+
|
|
8
|
+
### Added — `./environment`: canonical `NODE_ENV` classification
|
|
9
|
+
|
|
10
|
+
`classifyEnv(raw)` — total function mapping a raw `NODE_ENV` string to `EnvClass = 'development' | 'test' | 'production'`. Every input yields exactly one bucket; no fourth state or `undefined`. Handles both spellings per bucket (`production`/`prod`, `test`/`qa`), trims whitespace (`.env` files and CI variables routinely carry trailing newlines), and defaults unknown values to `development` — the safe-fail direction that keeps local tooling functional.
|
|
11
|
+
|
|
12
|
+
Collapses four copies across the codebase that each missed at least one spelling: `be-prod classifyEnv`, `gym businessMode()`, arc's CLI config template (`z.preprocess`), and arc's four raw comparison sites. The missed spellings were all in the dangerous direction — a `NODE_ENV=prod` instance that matched nothing fell through to `development`, opening security gates (`Secure` cookie flag, arc's dev preset, non-transactional money writes, raw 500 messages to the client) in production.
|
|
13
|
+
|
|
14
|
+
Exports: `classifyEnv`, `EnvClass`, `isProduction`, `isDevelopment`, `isTest`.
|
|
15
|
+
|
|
16
|
+
### Added — `publint` gate on build
|
|
17
|
+
|
|
18
|
+
`publint: true` in `tsdown.config.ts` — catches export-map entries pointing at non-existent dist files at build time rather than at consumer install time.
|
|
19
|
+
|
|
6
20
|
## [0.20.0] - 2026-08-05
|
|
7
21
|
|
|
8
22
|
### Added — `./period` OWNS timezone-aware period resolution (was two copies above the kernels)
|
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,85 @@
|
|
|
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: the raw
|
|
80
|
+
* comparison is the shape that caused every defect listed above, so the codebase should not contain
|
|
81
|
+
* one — even a correct one — for a reader to copy.
|
|
82
|
+
*/
|
|
83
|
+
declare function isProductionEnv(env: string | undefined | null): boolean;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { EnvClass, classifyEnv, isProductionEnv };
|
|
@@ -0,0 +1,48 @@
|
|
|
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: the raw
|
|
41
|
+
* comparison is the shape that caused every defect listed above, so the codebase should not contain
|
|
42
|
+
* one — even a correct one — for a reader to copy.
|
|
43
|
+
*/
|
|
44
|
+
function isProductionEnv(env) {
|
|
45
|
+
return classifyEnv(env) === "production";
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
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,6 +1,5 @@
|
|
|
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;
|
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));
|
package/dist/period.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { t as CivilDate } from "./timezone-
|
|
2
|
-
|
|
1
|
+
import { t as CivilDate } from "./timezone-CxUjxmRU.mjs";
|
|
3
2
|
//#region src/scheduling/period.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* A date range `[start, end]`.
|
|
@@ -55,7 +54,19 @@ type PeriodGranularity = 'year' | 'quarter' | 'month' | 'custom';
|
|
|
55
54
|
declare class DateRangeError extends Error {
|
|
56
55
|
readonly name = "DateRangeError";
|
|
57
56
|
}
|
|
58
|
-
type PeriodErrorCode =
|
|
57
|
+
type PeriodErrorCode =
|
|
58
|
+
/** Two resolution shapes at once (`month` + `quarter`, or either + `start`/`end`). */
|
|
59
|
+
'AMBIGUOUS_PERIOD' |
|
|
60
|
+
/** `start` without `end`, or vice versa. */
|
|
61
|
+
'INCOMPLETE_RANGE' |
|
|
62
|
+
/** `year` / `month` / `quarter` outside its legal domain, or non-integer. */
|
|
63
|
+
'INVALID_FIELD' |
|
|
64
|
+
/** No zone on the period and none supplied by the caller. */
|
|
65
|
+
'MISSING_TIMEZONE' |
|
|
66
|
+
/** The period's own zone and the caller's zone disagree. */
|
|
67
|
+
'TIMEZONE_CONFLICT' |
|
|
68
|
+
/** A `'YYYY'` / `'YYYY-MM'` / `'YYYY-Qn'` label that could not be read. */
|
|
69
|
+
'INVALID_LABEL';
|
|
59
70
|
declare class PeriodError extends Error {
|
|
60
71
|
readonly name = "PeriodError";
|
|
61
72
|
readonly code: PeriodErrorCode;
|
package/dist/person-parse.d.mts
CHANGED
package/dist/phone.d.mts
CHANGED
package/dist/phone.mjs
CHANGED
|
@@ -42,7 +42,7 @@ var PhoneError = class extends Error {
|
|
|
42
42
|
* Kept inline (not exported) because it's an implementation detail of the
|
|
43
43
|
* parser. Length is the only thing we actually need for the split.
|
|
44
44
|
*/
|
|
45
|
-
const E164_PREFIXES_3 = new Set([
|
|
45
|
+
const E164_PREFIXES_3 = /* @__PURE__ */ new Set([
|
|
46
46
|
"210",
|
|
47
47
|
"211",
|
|
48
48
|
"212",
|
|
@@ -215,7 +215,7 @@ const E164_PREFIXES_3 = new Set([
|
|
|
215
215
|
"996",
|
|
216
216
|
"998"
|
|
217
217
|
]);
|
|
218
|
-
const E164_PREFIXES_2 = new Set([
|
|
218
|
+
const E164_PREFIXES_2 = /* @__PURE__ */ new Set([
|
|
219
219
|
"20",
|
|
220
220
|
"27",
|
|
221
221
|
"30",
|
|
@@ -261,7 +261,7 @@ const E164_PREFIXES_2 = new Set([
|
|
|
261
261
|
"95",
|
|
262
262
|
"98"
|
|
263
263
|
]);
|
|
264
|
-
const E164_PREFIXES_1 = new Set(["1", "7"]);
|
|
264
|
+
const E164_PREFIXES_1 = /* @__PURE__ */ new Set(["1", "7"]);
|
|
265
265
|
/**
|
|
266
266
|
* Parse free-form phone input into a normalized `PhoneNumber`. Accepts:
|
|
267
267
|
* - `'+14155550182'` → e164 unchanged
|
package/dist/proration.d.mts
CHANGED
package/dist/retention.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
//#region src/retention/purge-evidence.ts
|
|
2
|
-
const STRATEGY_KINDS = new Set([
|
|
2
|
+
const STRATEGY_KINDS = /* @__PURE__ */ new Set([
|
|
3
3
|
"hard",
|
|
4
4
|
"soft",
|
|
5
5
|
"anonymize"
|
|
6
6
|
]);
|
|
7
|
-
const ACTOR_KINDS = new Set([
|
|
7
|
+
const ACTOR_KINDS = /* @__PURE__ */ new Set([
|
|
8
8
|
"user",
|
|
9
9
|
"system",
|
|
10
10
|
"service"
|
|
11
11
|
]);
|
|
12
|
-
const STATUS_KINDS = new Set([
|
|
12
|
+
const STATUS_KINDS = /* @__PURE__ */ new Set([
|
|
13
13
|
"completed",
|
|
14
14
|
"partial",
|
|
15
15
|
"failed"
|
package/dist/shelf-life.mjs
CHANGED
package/dist/sla-policy.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { s as IsoWeekday } from "./cadence-
|
|
1
|
+
import { s as IsoWeekday } from "./cadence-5X6TiD49.mjs";
|
|
2
2
|
import { SLA } from "./sla.mjs";
|
|
3
|
-
|
|
4
3
|
//#region src/scheduling/sla-policy.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Tier of severity. Hosts decide the keys ('urgent' / 'p0' / 'gold' / etc.) —
|
package/dist/state-diagram.d.mts
CHANGED
package/dist/state-diagram.mjs
CHANGED
|
@@ -54,7 +54,7 @@ function describeStateMachine(machine) {
|
|
|
54
54
|
* that can never leave the state it is stuck in.
|
|
55
55
|
*/
|
|
56
56
|
function unreachableStates(machine, initial) {
|
|
57
|
-
const seen = new Set([initial]);
|
|
57
|
+
const seen = /* @__PURE__ */ new Set([initial]);
|
|
58
58
|
const queue = [initial];
|
|
59
59
|
while (queue.length > 0) for (const next of machine.validTargets(queue.pop())) if (!seen.has(next)) {
|
|
60
60
|
seen.add(next);
|
package/dist/suspension.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { t as UtcOffsetMinutes } from "./calendar-
|
|
1
|
+
import { t as UtcOffsetMinutes } from "./calendar-DJWSbcgE.mjs";
|
|
2
2
|
import { DateRange } from "./period.mjs";
|
|
3
3
|
import { StatusChangeEntry } from "./status-history.mjs";
|
|
4
|
-
|
|
5
4
|
//#region src/workflow/suspension.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* One suspension, past or current.
|
|
@@ -84,7 +83,9 @@ declare function allowancePeriod(at: Date, periodMonths?: number, offsetMinutes?
|
|
|
84
83
|
* December. Ignoring that double-counts a straddling pause in both periods and
|
|
85
84
|
* refuses a member who is within their allowance.
|
|
86
85
|
*/
|
|
87
|
-
declare function suspendedDaysInPeriod(history: readonly SuspensionSpan[], period: DateRange,
|
|
86
|
+
declare function suspendedDaysInPeriod(history: readonly SuspensionSpan[], period: DateRange,
|
|
87
|
+
/** Now — bounds still-open spans. Defaults to the period end. */
|
|
88
|
+
at?: Date): number;
|
|
88
89
|
/** Is any span still open at `at`? */
|
|
89
90
|
declare function openSpans(history: readonly SuspensionSpan[], at: Date): readonly SuspensionSpan[];
|
|
90
91
|
/**
|
|
@@ -144,7 +145,8 @@ declare function spansFromStatusHistory<TStatus extends string>(history: readonl
|
|
|
144
145
|
* resume yet (a crash between the write and the append). Leaving it open would
|
|
145
146
|
* charge the member allowance for time they were not frozen.
|
|
146
147
|
*/
|
|
147
|
-
readonly currentStatus?: TStatus | undefined;
|
|
148
|
+
readonly currentStatus?: TStatus | undefined;
|
|
149
|
+
/** Closes a genuinely-open trailing span for counting. Defaults to open. */
|
|
148
150
|
readonly openEndedAt?: Date | undefined;
|
|
149
151
|
}): SuspensionSpan[];
|
|
150
152
|
//#endregion
|
package/dist/suspension.mjs
CHANGED
|
@@ -39,11 +39,12 @@ import { addMonths, startOfDay } from "./calendar.mjs";
|
|
|
39
39
|
* PURE: no clock reads beyond the `at` you pass, no I/O. The same evaluation runs
|
|
40
40
|
* on the server, in an admin preview, and in a test.
|
|
41
41
|
*/
|
|
42
|
-
const MS_PER_DAY =
|
|
42
|
+
const MS_PER_DAY = 864e5;
|
|
43
43
|
/** The allowance period containing `at`, as a half-open range. */
|
|
44
44
|
function allowancePeriod(at, periodMonths = 12, offsetMinutes = 0) {
|
|
45
|
+
const anchor = startOfDay(at, offsetMinutes);
|
|
45
46
|
return {
|
|
46
|
-
start: addMonths(
|
|
47
|
+
start: addMonths(anchor, -periodMonths),
|
|
47
48
|
end: at
|
|
48
49
|
};
|
|
49
50
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { t as Brand } from "./brand-
|
|
2
|
-
import { s as IsoWeekday } from "./cadence-
|
|
3
|
-
import { t as UtcOffsetMinutes } from "./calendar-
|
|
4
|
-
|
|
1
|
+
import { t as Brand } from "./brand-Lj_75Y_r.mjs";
|
|
2
|
+
import { s as IsoWeekday } from "./cadence-5X6TiD49.mjs";
|
|
3
|
+
import { t as UtcOffsetMinutes } from "./calendar-DJWSbcgE.mjs";
|
|
5
4
|
//#region src/scheduling/timezone.d.ts
|
|
6
5
|
declare class TimeZoneError extends Error {
|
|
7
6
|
constructor(message: string);
|
package/dist/timezone.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as civilDate, c as civilDaysBetween, d as isoWeekOfCivilDate, f as listTimeZones, h as zoneOffsetMinutes, i as addCivilDays, l as isCivilDate, m as zoneOffsetLabel, n as LocalTimeParts, o as civilDateOf, p as localTimeParts, r as TimeZoneError, s as civilDateToInstant, t as CivilDate, u as isValidTimeZone } from "./timezone-
|
|
1
|
+
import { a as civilDate, c as civilDaysBetween, d as isoWeekOfCivilDate, f as listTimeZones, h as zoneOffsetMinutes, i as addCivilDays, l as isCivilDate, m as zoneOffsetLabel, n as LocalTimeParts, o as civilDateOf, p as localTimeParts, r as TimeZoneError, s as civilDateToInstant, t as CivilDate, u as isValidTimeZone } from "./timezone-CxUjxmRU.mjs";
|
|
2
2
|
export { CivilDate, LocalTimeParts, TimeZoneError, addCivilDays, civilDate, civilDateOf, civilDateToInstant, civilDaysBetween, isCivilDate, isValidTimeZone, isoWeekOfCivilDate, listTimeZones, localTimeParts, zoneOffsetLabel, zoneOffsetMinutes };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { t as MinorUnits, w as CurrencyCode } from "./money-
|
|
2
|
-
|
|
1
|
+
import { t as MinorUnits, w as CurrencyCode } from "./money-CfAvfNiM.mjs";
|
|
3
2
|
//#region src/money/unit-cost-rate.d.ts
|
|
4
3
|
/** Fixed, ecosystem-wide rate scale: 6 decimal places below the minor unit. */
|
|
5
4
|
declare const RATE_SCALE = 1000000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Framework-agnostic TypeScript primitives shared across Classytic packages — Money, events, pagination, tenant config, and other building blocks.",
|
|
5
5
|
"author": "Classytic",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"types": "./dist/context.d.mts",
|
|
58
58
|
"default": "./dist/context.mjs"
|
|
59
59
|
},
|
|
60
|
+
"./environment": {
|
|
61
|
+
"types": "./dist/environment.d.mts",
|
|
62
|
+
"default": "./dist/environment.mjs"
|
|
63
|
+
},
|
|
60
64
|
"./events": {
|
|
61
65
|
"types": "./dist/events.d.mts",
|
|
62
66
|
"default": "./dist/events.mjs"
|
|
@@ -225,7 +229,8 @@
|
|
|
225
229
|
"@biomejs/biome": "^2.4.9",
|
|
226
230
|
"@classytic/dev-tools": "^0.2.0",
|
|
227
231
|
"@types/node": "^25.5.0",
|
|
228
|
-
"
|
|
232
|
+
"publint": "^0.3.23",
|
|
233
|
+
"tsdown": "^0.22.14",
|
|
229
234
|
"typescript": "^5.7.0",
|
|
230
235
|
"vitest": "^3.0.0"
|
|
231
236
|
},
|
|
File without changes
|
|
File without changes
|