@classytic/primitives 0.4.0 → 0.6.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 (50) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/dist/address.d.mts +1 -1
  3. package/dist/address.mjs +1 -1
  4. package/dist/approval.d.mts +1 -1
  5. package/dist/approval.mjs +1 -1
  6. package/dist/bank-transaction.d.mts +167 -0
  7. package/dist/bank-transaction.mjs +1 -0
  8. package/dist/{brand-DApaegoR.d.mts → brand-LLXqoh5J.d.mts} +1 -1
  9. package/dist/brand.d.mts +1 -1
  10. package/dist/cadence.d.mts +1 -1
  11. package/dist/cadence.mjs +1 -1
  12. package/dist/condition.d.mts +76 -0
  13. package/dist/condition.mjs +131 -0
  14. package/dist/context.d.mts +1 -1
  15. package/dist/currency-B4rSdN7D.d.mts +110 -0
  16. package/dist/currency.d.mts +1 -109
  17. package/dist/currency.mjs +1 -1
  18. package/dist/events.d.mts +1 -1
  19. package/dist/events.mjs +1 -1
  20. package/dist/hold.d.mts +1 -1
  21. package/dist/hold.mjs +1 -1
  22. package/dist/mixin.d.mts +73 -0
  23. package/dist/mixin.mjs +47 -0
  24. package/dist/money.d.mts +2 -2
  25. package/dist/money.mjs +1 -1
  26. package/dist/outbox.d.mts +1 -1
  27. package/dist/outbox.mjs +1 -1
  28. package/dist/payment-gateway.d.mts +168 -0
  29. package/dist/payment-gateway.mjs +1 -0
  30. package/dist/period.d.mts +1 -1
  31. package/dist/period.mjs +1 -1
  32. package/dist/person.d.mts +1 -1
  33. package/dist/person.mjs +1 -1
  34. package/dist/phone.d.mts +47 -0
  35. package/dist/phone.mjs +310 -0
  36. package/dist/reference.d.mts +1 -1
  37. package/dist/reference.mjs +1 -1
  38. package/dist/result.d.mts +1 -1
  39. package/dist/result.mjs +1 -1
  40. package/dist/sla-policy.d.mts +116 -0
  41. package/dist/sla-policy.mjs +120 -0
  42. package/dist/sla.d.mts +1 -1
  43. package/dist/sla.mjs +1 -1
  44. package/dist/split-allocation.d.mts +1 -1
  45. package/dist/split-allocation.mjs +1 -1
  46. package/dist/state-machine.d.mts +1 -1
  47. package/dist/state-machine.mjs +1 -1
  48. package/dist/status-history.d.mts +86 -0
  49. package/dist/status-history.mjs +48 -0
  50. package/package.json +29 -1
@@ -0,0 +1,110 @@
1
+ import { t as Brand } from "./brand-LLXqoh5J.mjs";
2
+
3
+ //#region src/money/currency.d.ts
4
+ /**
5
+ * ISO 4217 currency code (3 uppercase letters).
6
+ *
7
+ * Branded `string` — any 3-letter string is structurally valid, but the brand
8
+ * prevents accidental mixing with raw strings. Use {@link toCurrencyCode} to
9
+ * construct one safely.
10
+ */
11
+ type CurrencyCode = Brand<string, 'CurrencyCode'>;
12
+ /**
13
+ * Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
14
+ * needed. Listed here because shared-types ships with these by default.
15
+ */
16
+ declare const CURRENCIES: {
17
+ readonly USD: "USD";
18
+ readonly EUR: "EUR";
19
+ readonly GBP: "GBP";
20
+ readonly JPY: "JPY";
21
+ readonly CNY: "CNY";
22
+ readonly INR: "INR";
23
+ readonly BDT: "BDT";
24
+ readonly AED: "AED";
25
+ readonly SAR: "SAR";
26
+ readonly PKR: "PKR";
27
+ readonly NPR: "NPR";
28
+ readonly LKR: "LKR";
29
+ readonly MYR: "MYR";
30
+ readonly SGD: "SGD";
31
+ readonly AUD: "AUD";
32
+ readonly CAD: "CAD";
33
+ readonly CHF: "CHF";
34
+ readonly HKD: "HKD";
35
+ readonly THB: "THB";
36
+ readonly IDR: "IDR";
37
+ };
38
+ /**
39
+ * Number of minor units per major unit for common currencies.
40
+ *
41
+ * ISO 4217 defines most currencies with 2 decimal places (cents). A few are
42
+ * zero-decimal (JPY, KRW) or three-decimal (BHD, KWD, JOD). When a currency
43
+ * isn't in this table, {@link minorUnitFactor} returns 100 as the safe default.
44
+ */
45
+ declare const MINOR_UNIT_FACTOR: Readonly<Record<string, number>>;
46
+ /**
47
+ * Narrow a raw string to a {@link CurrencyCode}. Returns `null` if the string
48
+ * is not a valid ISO 4217 format (3 uppercase letters). This does not verify
49
+ * the code is registered with ISO — use your own whitelist for that.
50
+ */
51
+ declare function toCurrencyCode(value: string): CurrencyCode | null;
52
+ /** Type predicate for `unknown` input. */
53
+ declare function isCurrencyCode(value: unknown): value is CurrencyCode;
54
+ /**
55
+ * Minor units per major unit (e.g. 100 for USD, 1 for JPY, 1000 for KWD).
56
+ * Falls back to 100 when the currency is unknown — matches the ISO 4217 default.
57
+ */
58
+ declare function minorUnitFactor(currency: string): number;
59
+ /**
60
+ * Frozen FX conversion record — the cost of record for cross-currency
61
+ * transactions. Stored alongside a base-currency amount at the seam where
62
+ * currency crossed (procurement receipt, landed-cost entry, foreign refund).
63
+ *
64
+ * Invariant: once written, never mutated. Revaluation is a new snapshot on
65
+ * a new document — rewriting an existing snapshot corrupts audit history.
66
+ *
67
+ * `rate` is expressed as: 1 sourceCurrency = rate * baseCurrency
68
+ * (e.g. USD→BDT at 110 means rate = 110, so sourceAmount 10 USD → baseAmount 1100 BDT).
69
+ * This matches how banks quote — the source is the "from" currency.
70
+ */
71
+ interface FxSnapshot {
72
+ /** Currency the transaction was invoiced / quoted in. ISO 4217. */
73
+ sourceCurrency: string;
74
+ /** Organisation's functional currency the base amount is expressed in. */
75
+ baseCurrency: string;
76
+ /** 1 unit of sourceCurrency equals this many units of baseCurrency. */
77
+ rate: number;
78
+ /** When the rate was captured — NOT when the document was last touched. */
79
+ snapshotAt: Date;
80
+ /**
81
+ * Optional provenance tag — e.g. 'manual', 'bangladesh-bank',
82
+ * 'openexchangerates.org', 'supplier-invoice'. Free-form; hosts use it for
83
+ * audit filtering and re-pricing policy.
84
+ */
85
+ source?: string;
86
+ }
87
+ /**
88
+ * Apply an {@link FxSnapshot} to a source-currency amount. Returns the
89
+ * equivalent base-currency amount. No rounding — callers that persist into
90
+ * an integer minor-unit column must round explicitly with a deterministic
91
+ * rule (`Math.round`, banker's rounding, floor for duty-conservative
92
+ * postings, etc.).
93
+ */
94
+ declare function convertWithSnapshot(sourceAmount: number, fx: FxSnapshot): number;
95
+ /**
96
+ * Reverse an {@link FxSnapshot} — reconstruct the source-currency amount
97
+ * from a stored base amount. Useful for reporting "cost in original
98
+ * currency". Not round-trip safe under rounding; prefer storing the
99
+ * original source amount alongside the base amount when audit fidelity
100
+ * matters.
101
+ */
102
+ declare function reverseWithSnapshot(baseAmount: number, fx: FxSnapshot): number;
103
+ /**
104
+ * Structural validation — checks shape only. Does NOT verify the rate is
105
+ * economically sensible (8.5 USD→BDT would pass this check). Host-level
106
+ * guards / anomaly detection handle that.
107
+ */
108
+ declare function isFxSnapshot(value: unknown): value is FxSnapshot;
109
+ //#endregion
110
+ export { convertWithSnapshot as a, minorUnitFactor as c, MINOR_UNIT_FACTOR as i, reverseWithSnapshot as l, CurrencyCode as n, isCurrencyCode as o, FxSnapshot as r, isFxSnapshot as s, CURRENCIES as t, toCurrencyCode as u };
@@ -1,110 +1,2 @@
1
- import { t as Brand } from "./brand-DApaegoR.mjs";
2
-
3
- //#region src/currency.d.ts
4
- /**
5
- * ISO 4217 currency code (3 uppercase letters).
6
- *
7
- * Branded `string` — any 3-letter string is structurally valid, but the brand
8
- * prevents accidental mixing with raw strings. Use {@link toCurrencyCode} to
9
- * construct one safely.
10
- */
11
- type CurrencyCode = Brand<string, 'CurrencyCode'>;
12
- /**
13
- * Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
14
- * needed. Listed here because shared-types ships with these by default.
15
- */
16
- declare const CURRENCIES: {
17
- readonly USD: "USD";
18
- readonly EUR: "EUR";
19
- readonly GBP: "GBP";
20
- readonly JPY: "JPY";
21
- readonly CNY: "CNY";
22
- readonly INR: "INR";
23
- readonly BDT: "BDT";
24
- readonly AED: "AED";
25
- readonly SAR: "SAR";
26
- readonly PKR: "PKR";
27
- readonly NPR: "NPR";
28
- readonly LKR: "LKR";
29
- readonly MYR: "MYR";
30
- readonly SGD: "SGD";
31
- readonly AUD: "AUD";
32
- readonly CAD: "CAD";
33
- readonly CHF: "CHF";
34
- readonly HKD: "HKD";
35
- readonly THB: "THB";
36
- readonly IDR: "IDR";
37
- };
38
- /**
39
- * Number of minor units per major unit for common currencies.
40
- *
41
- * ISO 4217 defines most currencies with 2 decimal places (cents). A few are
42
- * zero-decimal (JPY, KRW) or three-decimal (BHD, KWD, JOD). When a currency
43
- * isn't in this table, {@link minorUnitFactor} returns 100 as the safe default.
44
- */
45
- declare const MINOR_UNIT_FACTOR: Readonly<Record<string, number>>;
46
- /**
47
- * Narrow a raw string to a {@link CurrencyCode}. Returns `null` if the string
48
- * is not a valid ISO 4217 format (3 uppercase letters). This does not verify
49
- * the code is registered with ISO — use your own whitelist for that.
50
- */
51
- declare function toCurrencyCode(value: string): CurrencyCode | null;
52
- /** Type predicate for `unknown` input. */
53
- declare function isCurrencyCode(value: unknown): value is CurrencyCode;
54
- /**
55
- * Minor units per major unit (e.g. 100 for USD, 1 for JPY, 1000 for KWD).
56
- * Falls back to 100 when the currency is unknown — matches the ISO 4217 default.
57
- */
58
- declare function minorUnitFactor(currency: string): number;
59
- /**
60
- * Frozen FX conversion record — the cost of record for cross-currency
61
- * transactions. Stored alongside a base-currency amount at the seam where
62
- * currency crossed (procurement receipt, landed-cost entry, foreign refund).
63
- *
64
- * Invariant: once written, never mutated. Revaluation is a new snapshot on
65
- * a new document — rewriting an existing snapshot corrupts audit history.
66
- *
67
- * `rate` is expressed as: 1 sourceCurrency = rate * baseCurrency
68
- * (e.g. USD→BDT at 110 means rate = 110, so sourceAmount 10 USD → baseAmount 1100 BDT).
69
- * This matches how banks quote — the source is the "from" currency.
70
- */
71
- interface FxSnapshot {
72
- /** Currency the transaction was invoiced / quoted in. ISO 4217. */
73
- sourceCurrency: string;
74
- /** Organisation's functional currency the base amount is expressed in. */
75
- baseCurrency: string;
76
- /** 1 unit of sourceCurrency equals this many units of baseCurrency. */
77
- rate: number;
78
- /** When the rate was captured — NOT when the document was last touched. */
79
- snapshotAt: Date;
80
- /**
81
- * Optional provenance tag — e.g. 'manual', 'bangladesh-bank',
82
- * 'openexchangerates.org', 'supplier-invoice'. Free-form; hosts use it for
83
- * audit filtering and re-pricing policy.
84
- */
85
- source?: string;
86
- }
87
- /**
88
- * Apply an {@link FxSnapshot} to a source-currency amount. Returns the
89
- * equivalent base-currency amount. No rounding — callers that persist into
90
- * an integer minor-unit column must round explicitly with a deterministic
91
- * rule (`Math.round`, banker's rounding, floor for duty-conservative
92
- * postings, etc.).
93
- */
94
- declare function convertWithSnapshot(sourceAmount: number, fx: FxSnapshot): number;
95
- /**
96
- * Reverse an {@link FxSnapshot} — reconstruct the source-currency amount
97
- * from a stored base amount. Useful for reporting "cost in original
98
- * currency". Not round-trip safe under rounding; prefer storing the
99
- * original source amount alongside the base amount when audit fidelity
100
- * matters.
101
- */
102
- declare function reverseWithSnapshot(baseAmount: number, fx: FxSnapshot): number;
103
- /**
104
- * Structural validation — checks shape only. Does NOT verify the rate is
105
- * economically sensible (8.5 USD→BDT would pass this check). Host-level
106
- * guards / anomaly detection handle that.
107
- */
108
- declare function isFxSnapshot(value: unknown): value is FxSnapshot;
109
- //#endregion
1
+ import { a as convertWithSnapshot, c as minorUnitFactor, i as MINOR_UNIT_FACTOR, l as reverseWithSnapshot, n as CurrencyCode, o as isCurrencyCode, r as FxSnapshot, s as isFxSnapshot, t as CURRENCIES, u as toCurrencyCode } from "./currency-B4rSdN7D.mjs";
110
2
  export { CURRENCIES, CurrencyCode, FxSnapshot, MINOR_UNIT_FACTOR, convertWithSnapshot, isCurrencyCode, isFxSnapshot, minorUnitFactor, reverseWithSnapshot, toCurrencyCode };
package/dist/currency.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/currency.ts
1
+ //#region src/money/currency.ts
2
2
  /**
3
3
  * Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
4
4
  * needed. Listed here because shared-types ships with these by default.
package/dist/events.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/events.d.ts
1
+ //#region src/events/events.d.ts
2
2
  /**
3
3
  * Domain event transport primitives.
4
4
  *
package/dist/events.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/events.ts
1
+ //#region src/events/events.ts
2
2
  /**
3
3
  * Build a {@link DomainEvent} with auto-filled `id` + `timestamp`. All other
4
4
  * `EventMeta` fields are caller-controlled and override the defaults if
package/dist/hold.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/hold.d.ts
1
+ //#region src/workflow/hold.d.ts
2
2
  /**
3
3
  * Hold reason value object + collection helpers.
4
4
  *
package/dist/hold.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/hold.ts
1
+ //#region src/workflow/hold.ts
2
2
  var HoldError = class extends Error {
3
3
  name = "HoldError";
4
4
  code;
@@ -0,0 +1,73 @@
1
+ //#region src/composition/mixin.d.ts
2
+ /**
3
+ * Additive composition for domain entities — without subclassing.
4
+ *
5
+ * Most mixin system (good idea, heavy implementation) lets a `Contact`
6
+ * become a `Contact & Customer` without rewriting the type or copying
7
+ * fields. We replicate the GOOD bit (additive type composition stored as
8
+ * nested data) with none of the bad bits (no decorators, no global class
9
+ * registry, no proxies).
10
+ *
11
+ * Pattern: store mixin payloads on a known well-typed key (`mixins.customer`,
12
+ * `mixins.lead-fit`, …) instead of spreading them onto the entity root. This
13
+ * keeps the base type stable and lets each mixin own its own keyspace —
14
+ * additive schema evolution without breaking exhaustiveness.
15
+ *
16
+ * @example
17
+ * import { withMixin, getMixin, hasMixin } from '@classytic/primitives/mixin';
18
+ *
19
+ * interface Contact { id: string; name: string; mixins?: Record<string, unknown>; }
20
+ * interface CustomerFacts { lifetimeValue: number; firstOrderAt: Date; }
21
+ *
22
+ * const c: Contact = { id: 'c1', name: 'Acme' };
23
+ * const enriched = withMixin<Contact, 'customer', CustomerFacts>(c, 'customer', {
24
+ * lifetimeValue: 9200,
25
+ * firstOrderAt: new Date('2024-09-12'),
26
+ * });
27
+ *
28
+ * getMixin<CustomerFacts>(enriched, 'customer')?.lifetimeValue; // 9200
29
+ * hasMixin(enriched, 'customer'); // true
30
+ *
31
+ * // Domain stays exhaustive — the base type didn't change, only the
32
+ * // optional `mixins` namespace did.
33
+ */
34
+ /**
35
+ * Marker interface — entities that participate in the mixin protocol declare
36
+ * `mixins?: Mixinable['mixins']`. Hosts can opt in retroactively by widening
37
+ * their existing types via intersection.
38
+ */
39
+ interface Mixinable {
40
+ readonly mixins?: Readonly<Record<string, unknown>>;
41
+ }
42
+ /**
43
+ * Result type: the base `T` with `mixins[K]` typed as `M`. The intersection
44
+ * keeps callers exhaustive over the original entity while exposing the
45
+ * mixin payload through a well-typed accessor.
46
+ */
47
+ type WithMixin<T, K extends string, M> = T & {
48
+ readonly mixins: Readonly<Record<string, unknown>> & { readonly [P in K]: M };
49
+ };
50
+ /**
51
+ * Attach (or overwrite) a mixin on a base entity. Returns a NEW object —
52
+ * never mutates. The other mixins on the entity are preserved by spread.
53
+ *
54
+ * Type-level: caller picks the mixin key and the mixin payload shape; we
55
+ * intersect them into the return type so chained reads stay precise.
56
+ */
57
+ declare function withMixin<T extends Mixinable, K extends string, M>(base: T, key: K, data: M): WithMixin<T, K, M>;
58
+ /**
59
+ * Pull a mixin payload off an entity by key. Returns `null` (not `undefined`)
60
+ * so call sites can use `??` to fall through to a default without ambiguity
61
+ * with a real `undefined` field on the payload itself.
62
+ */
63
+ declare function getMixin<M>(entity: Mixinable, key: string): M | null;
64
+ /** Cheap presence check — useful for `if (hasMixin(c, 'customer')) …` branches. */
65
+ declare function hasMixin(entity: Mixinable, key: string): boolean;
66
+ /**
67
+ * Remove a mixin. Returns a new entity. No-op if the mixin isn't present.
68
+ * Useful for migrations / GDPR scrubs ("remove `customer` payload but keep
69
+ * the base `Contact` row").
70
+ */
71
+ declare function withoutMixin<T extends Mixinable>(entity: T, key: string): T;
72
+ //#endregion
73
+ export { Mixinable, WithMixin, getMixin, hasMixin, withMixin, withoutMixin };
package/dist/mixin.mjs ADDED
@@ -0,0 +1,47 @@
1
+ //#region src/composition/mixin.ts
2
+ /**
3
+ * Attach (or overwrite) a mixin on a base entity. Returns a NEW object —
4
+ * never mutates. The other mixins on the entity are preserved by spread.
5
+ *
6
+ * Type-level: caller picks the mixin key and the mixin payload shape; we
7
+ * intersect them into the return type so chained reads stay precise.
8
+ */
9
+ function withMixin(base, key, data) {
10
+ return {
11
+ ...base,
12
+ mixins: {
13
+ ...base.mixins ?? {},
14
+ [key]: data
15
+ }
16
+ };
17
+ }
18
+ /**
19
+ * Pull a mixin payload off an entity by key. Returns `null` (not `undefined`)
20
+ * so call sites can use `??` to fall through to a default without ambiguity
21
+ * with a real `undefined` field on the payload itself.
22
+ */
23
+ function getMixin(entity, key) {
24
+ if (entity.mixins === void 0 || entity.mixins === null) return null;
25
+ const found = entity.mixins[key];
26
+ return found === void 0 ? null : found;
27
+ }
28
+ /** Cheap presence check — useful for `if (hasMixin(c, 'customer')) …` branches. */
29
+ function hasMixin(entity, key) {
30
+ return entity.mixins !== void 0 && entity.mixins !== null && entity.mixins[key] !== void 0;
31
+ }
32
+ /**
33
+ * Remove a mixin. Returns a new entity. No-op if the mixin isn't present.
34
+ * Useful for migrations / GDPR scrubs ("remove `customer` payload but keep
35
+ * the base `Contact` row").
36
+ */
37
+ function withoutMixin(entity, key) {
38
+ if (!hasMixin(entity, key)) return entity;
39
+ const next = { ...entity.mixins };
40
+ delete next[key];
41
+ return {
42
+ ...entity,
43
+ mixins: next
44
+ };
45
+ }
46
+ //#endregion
47
+ export { getMixin, hasMixin, withMixin, withoutMixin };
package/dist/money.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { CurrencyCode } from "./currency.mjs";
1
+ import { n as CurrencyCode } from "./currency-B4rSdN7D.mjs";
2
2
 
3
- //#region src/money.d.ts
3
+ //#region src/money/money.d.ts
4
4
  /**
5
5
  * Wire-format monetary amount: integer minor units, branded with a phantom
6
6
  * currency type so calls like `formatBdt(usdValue)` fail at compile time.
package/dist/money.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { minorUnitFactor } from "./currency.mjs";
2
- //#region src/money.ts
2
+ //#region src/money/money.ts
3
3
  /**
4
4
  * Cast a raw number to {@link MinorUnits}. Pass the currency as the second
5
5
  * argument to fix the phantom type. With no second arg, `MinorUnits<string>`
package/dist/outbox.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DeadLetteredEvent, DomainEvent } from "./events.mjs";
2
2
 
3
- //#region src/outbox.d.ts
3
+ //#region src/events/outbox.d.ts
4
4
  /**
5
5
  * Options passed to {@link OutboxStore.save} for richer write semantics.
6
6
  * Stores may ignore fields they don't support — contract is best-effort.
package/dist/outbox.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/outbox.ts
1
+ //#region src/events/outbox.ts
2
2
  /**
3
3
  * Thrown by a store when `acknowledge` / `fail` is called by a consumer that
4
4
  * does not own the event's current lease.
@@ -0,0 +1,168 @@
1
+ import { Money } from "./money.mjs";
2
+
3
+ //#region src/money/payment-gateway.d.ts
4
+ /**
5
+ * Static capability declaration — providers expose this so the engine
6
+ * can branch on what's supported (e.g. skip the refund button for
7
+ * cash providers, route 3DS-required cards to verification flows).
8
+ */
9
+ interface ProviderCapabilities {
10
+ /** Provider exposes a webhook endpoint the host can register. */
11
+ supportsWebhooks: boolean;
12
+ /** Provider can reverse a captured charge. */
13
+ supportsRefunds: boolean;
14
+ /** Provider can refund less than the captured amount. */
15
+ supportsPartialRefunds: boolean;
16
+ /**
17
+ * Verification requires a human (admin marks "received") rather than
18
+ * an API call. True for cash, manual bank transfer, mobile-money
19
+ * outside an API window.
20
+ */
21
+ requiresManualVerification: boolean;
22
+ }
23
+ /**
24
+ * Arguments to `provider.createIntent(...)`. Engine constructs this
25
+ * from the host's checkout payload and the resolved currency. The
26
+ * `metadata` blob is opaque pass-through — providers MAY forward it to
27
+ * the upstream gateway (Stripe attaches it to the PaymentIntent), but
28
+ * MUST NOT mutate or interpret it.
29
+ */
30
+ interface CreateIntentParams {
31
+ /** Amount + currency. Integer minor units; provider converts to its native shape. */
32
+ amount: Money;
33
+ /** Host-side customer id, if any. Forwarded to the gateway. */
34
+ customerId?: string;
35
+ /** Where the customer returns after off-site payment (Stripe Checkout, PayPal). */
36
+ returnUrl?: string;
37
+ /** Pass-through metadata. Visible on the gateway dashboard / webhook events. */
38
+ metadata?: Record<string, unknown>;
39
+ /**
40
+ * Provider-specific knobs. Each gateway documents its own keys —
41
+ * Stripe has `payment_method_types`, SSLCommerz has `tran_id`, bKash
42
+ * has `intent`, etc. Engine doesn't peek inside.
43
+ */
44
+ [key: string]: unknown;
45
+ }
46
+ /**
47
+ * What `provider.createIntent(...)` returns. The engine persists
48
+ * `id` / `paymentIntentId` / `sessionId` on the Transaction's
49
+ * `gateway` block so subsequent `verify` / webhook lookups can find
50
+ * the row.
51
+ */
52
+ interface PaymentIntent {
53
+ /** Unique gateway-scoped id for this intent (`pi_xxx`, `ch_xxx`, etc.). */
54
+ id: string;
55
+ /** Lowercase gateway name (`'stripe'`, `'razorpay'`, `'sslcommerz'`, `'bkash'`, `'manual'`). */
56
+ provider: string;
57
+ /**
58
+ * Lifecycle status as the gateway reports it. Free-form string —
59
+ * engine maps known values to its own state machine. Common values:
60
+ * `'pending'`, `'processing'`, `'requires_action'`, `'succeeded'`,
61
+ * `'failed'`, `'cancelled'`.
62
+ */
63
+ status: string;
64
+ /** Amount + currency that the intent locks in. */
65
+ amount: Money;
66
+ /**
67
+ * Session token for hosted checkout flows (Stripe Checkout `session_id`,
68
+ * SSLCommerz `sessionkey`, bKash `paymentID`). When the gateway
69
+ * uses a redirect-style flow this is the lookup key.
70
+ */
71
+ sessionId?: string | null;
72
+ /**
73
+ * Stripe-style `pi_xxx` payment-intent id. May equal `id` for
74
+ * gateways that don't distinguish session vs intent.
75
+ */
76
+ paymentIntentId?: string | null;
77
+ /** Frontend hand-off — Stripe Elements / Razorpay Checkout consume this. */
78
+ clientSecret?: string;
79
+ /** Off-site redirect URL (PayPal `approve` link, SSLCommerz GatewayPageURL). */
80
+ paymentUrl?: string;
81
+ /** Manual-flow instructions ("transfer to A/c 1234, reference X"). */
82
+ instructions?: string;
83
+ /** Pass-through metadata echoed from the create call. */
84
+ metadata?: Record<string, unknown>;
85
+ /** Raw provider response — kept for audit. Consumers MUST NOT rely on shape. */
86
+ raw?: unknown;
87
+ }
88
+ /**
89
+ * What `provider.verifyPayment(intentId)` returns. The engine maps
90
+ * this to its `TRANSACTION_STATUS` state machine.
91
+ */
92
+ interface PaymentResult {
93
+ /** Gateway-scoped id (intent id, charge id, tran id — whatever the gateway uses). */
94
+ id: string;
95
+ /** Lowercase gateway name. */
96
+ provider: string;
97
+ /**
98
+ * Closed status set — narrower than `PaymentIntent.status` because
99
+ * verification has settled the lifecycle.
100
+ *
101
+ * - `'succeeded'` — payment captured, funds owed to merchant.
102
+ * - `'failed'` — terminal failure. Engine moves Transaction to FAILED.
103
+ * - `'processing'` — async processing (ACH, e-wallet pending bank).
104
+ * - `'requires_action'` — 3DS, OTP, or out-of-band approval pending.
105
+ */
106
+ status: 'succeeded' | 'failed' | 'processing' | 'requires_action';
107
+ /** Captured amount as the gateway reports it (may differ from intent on partial capture). */
108
+ amount?: Money;
109
+ /** When the gateway booked the capture. */
110
+ paidAt?: Date;
111
+ /** Pass-through metadata (gateway customer id, fee breakdown, etc.). */
112
+ metadata?: Record<string, unknown>;
113
+ /** Raw response — audit only. */
114
+ raw?: unknown;
115
+ }
116
+ /**
117
+ * What `provider.refund(...)` returns. Successful refunds become a
118
+ * new `'refund'` Transaction (flow: `'outflow'`) on the engine side.
119
+ */
120
+ interface RefundResult {
121
+ /** Gateway-scoped refund id (Stripe `re_xxx`, etc.). */
122
+ id: string;
123
+ /** Lowercase gateway name. */
124
+ provider: string;
125
+ status: 'succeeded' | 'failed' | 'processing';
126
+ /** Amount reversed, as the gateway reports it. May be partial. */
127
+ amount?: Money;
128
+ /** When the gateway booked the refund. */
129
+ refundedAt?: Date;
130
+ /** Reason string forwarded from the engine call (or filled by the gateway). */
131
+ reason?: string;
132
+ metadata?: Record<string, unknown>;
133
+ raw?: unknown;
134
+ }
135
+ /**
136
+ * Parsed webhook event from a gateway's HTTP delivery. Engine stamps
137
+ * `id` on the Transaction's `webhook.eventId` for atomic dedup —
138
+ * replays of the same `id` are no-ops.
139
+ */
140
+ interface WebhookEvent {
141
+ /**
142
+ * Gateway-scoped event id (Stripe `evt_xxx`, Razorpay
143
+ * `webhook_event_id`). Used as the dedup key — repeating delivery
144
+ * attempts MUST share the same id.
145
+ */
146
+ id: string;
147
+ /** Lowercase gateway name. */
148
+ provider: string;
149
+ /** Event type (`'payment.succeeded'`, `'charge.refunded'`, etc.). */
150
+ type: string;
151
+ /**
152
+ * Routing fields the engine uses to find the matching Transaction:
153
+ * - `sessionId` — for redirect-style flows.
154
+ * - `paymentIntentId` — for Stripe-style flows.
155
+ * Plus arbitrary additional gateway data.
156
+ */
157
+ data: {
158
+ sessionId?: string;
159
+ paymentIntentId?: string;
160
+ [key: string]: unknown;
161
+ };
162
+ /** When the gateway emitted the event. */
163
+ createdAt?: Date;
164
+ /** Raw provider payload — audit only. */
165
+ raw?: unknown;
166
+ }
167
+ //#endregion
168
+ export { CreateIntentParams, PaymentIntent, PaymentResult, ProviderCapabilities, RefundResult, WebhookEvent };
@@ -0,0 +1 @@
1
+ export {};
package/dist/period.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/period.d.ts
1
+ //#region src/scheduling/period.d.ts
2
2
  /**
3
3
  * A closed date range `[start, end]`. Both endpoints inclusive — consumers
4
4
  * decide whether to treat them as `[start, end)` at the query layer.
package/dist/period.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/period.ts
1
+ //#region src/scheduling/period.ts
2
2
  function isDateRange(value) {
3
3
  return typeof value === "object" && value !== null && value.start instanceof Date && value.end instanceof Date;
4
4
  }
package/dist/person.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/person.d.ts
1
+ //#region src/identity/person.d.ts
2
2
  /**
3
3
  * Structured person name. Handles the parts most locales need; domains that
4
4
  * require more (per-locale honorifics, patronymics) extend from here.
package/dist/person.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/person.ts
1
+ //#region src/identity/person.ts
2
2
  function formatFullName(name) {
3
3
  return [
4
4
  name.prefix,
@@ -0,0 +1,47 @@
1
+ import { Result } from "./result.mjs";
2
+
3
+ //#region src/identity/phone.d.ts
4
+ type PhoneErrorCode = 'EMPTY' | 'MISSING_PLUS' | 'NOT_E164' | 'TOO_SHORT' | 'TOO_LONG';
5
+ declare class PhoneError extends Error {
6
+ readonly name = "PhoneError";
7
+ readonly code: PhoneErrorCode;
8
+ constructor(code: PhoneErrorCode, message: string);
9
+ }
10
+ /**
11
+ * Parsed phone number. Stored E.164 (always begins with `+`, digits only after).
12
+ * `callingCode` is the 1–3 digit country dial code (`'1'` US, `'44'` UK,
13
+ * `'880'` BD, etc.) — useful for routing without re-parsing.
14
+ *
15
+ * Construction goes through `parsePhone()` (never assemble by hand) so the
16
+ * invariants are guaranteed by the parser.
17
+ */
18
+ interface PhoneNumber {
19
+ readonly e164: string;
20
+ readonly callingCode: string;
21
+ /** The national-number portion (digits after the calling code). */
22
+ readonly nationalNumber: string;
23
+ }
24
+ /**
25
+ * Parse free-form phone input into a normalized `PhoneNumber`. Accepts:
26
+ * - `'+14155550182'` → e164 unchanged
27
+ * - `'+1 (415) 555-0182'` → strips spaces / punctuation
28
+ * - `'+1.415.555.0182'` → strips dots
29
+ *
30
+ * Rejects anything without a leading `+` — we don't guess the country.
31
+ * If the host knows the user's region, prepend the code before calling.
32
+ */
33
+ declare function parsePhone(input: string): Result<PhoneNumber, PhoneError>;
34
+ /** Convenience: returns the E.164 string. Useful when piping to a transport. */
35
+ declare function formatPhone(phone: PhoneNumber): string;
36
+ /**
37
+ * Best-effort national-format render: groups the national number into 3-digit
38
+ * blocks. Not locale-aware — for display polish, hosts should format per
39
+ * region. Useful as a default in admin UIs.
40
+ *
41
+ * @example formatNational(parsePhone('+14155550182').value!) → '+1 415 555 0182'
42
+ */
43
+ declare function formatNational(phone: PhoneNumber): string;
44
+ /** Type guard. */
45
+ declare function isPhoneNumber(value: unknown): value is PhoneNumber;
46
+ //#endregion
47
+ export { PhoneError, PhoneErrorCode, PhoneNumber, formatNational, formatPhone, isPhoneNumber, parsePhone };