@classytic/primitives 0.19.0 → 0.20.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 (45) hide show
  1. package/CHANGELOG.md +906 -751
  2. package/README.md +160 -159
  3. package/dist/approval.d.mts +49 -10
  4. package/dist/approval.mjs +44 -10
  5. package/dist/bank-transaction.d.mts +1 -1
  6. package/dist/cadence-BFQVC1Qp.d.mts +107 -0
  7. package/dist/cadence.d.mts +1 -92
  8. package/dist/cadence.mjs +133 -4
  9. package/dist/calendar-B6fm-FbR.d.mts +46 -0
  10. package/dist/calendar.d.mts +1 -45
  11. package/dist/canonical.mjs +17 -0
  12. package/dist/context.d.mts +28 -1
  13. package/dist/currency.d.mts +2 -2
  14. package/dist/currency.mjs +134 -19
  15. package/dist/event-infra.d.mts +1 -34
  16. package/dist/event-infra.mjs +16 -46
  17. package/dist/idempotency.d.mts +253 -0
  18. package/dist/idempotency.mjs +226 -0
  19. package/dist/memory-outbox.d.mts +64 -0
  20. package/dist/memory-outbox.mjs +172 -0
  21. package/dist/monetization.d.mts +1 -1
  22. package/dist/money-DPo9fLBi.d.mts +347 -0
  23. package/dist/money.d.mts +1 -140
  24. package/dist/money.mjs +6 -15
  25. package/dist/outbox.d.mts +49 -1
  26. package/dist/outbox.mjs +64 -1
  27. package/dist/payment-events.d.mts +132 -4
  28. package/dist/payment-events.mjs +22 -2
  29. package/dist/payment-gateway.d.mts +210 -7
  30. package/dist/payment-gateway.mjs +18 -1
  31. package/dist/period.d.mts +138 -7
  32. package/dist/period.mjs +316 -5
  33. package/dist/person-parse.d.mts +82 -0
  34. package/dist/person-parse.mjs +109 -0
  35. package/dist/proration.d.mts +2 -2
  36. package/dist/proration.mjs +18 -6
  37. package/dist/sla-policy.d.mts +1 -1
  38. package/dist/state-machine.mjs +13 -0
  39. package/dist/suspension.d.mts +1 -1
  40. package/dist/timezone-tlG7ygwx.d.mts +114 -0
  41. package/dist/timezone.d.mts +2 -100
  42. package/dist/timezone.mjs +38 -3
  43. package/dist/unit-cost-rate.d.mts +1 -2
  44. package/package.json +244 -232
  45. package/dist/currency-BCJnJBlb.d.mts +0 -119
@@ -0,0 +1,114 @@
1
+ import { t as Brand } from "./brand-LLXqoh5J.mjs";
2
+ import { s as IsoWeekday } from "./cadence-BFQVC1Qp.mjs";
3
+ import { t as UtcOffsetMinutes } from "./calendar-B6fm-FbR.mjs";
4
+
5
+ //#region src/scheduling/timezone.d.ts
6
+ declare class TimeZoneError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /**
10
+ * Is `zone` a valid IANA timezone name on this runtime's ICU data?
11
+ * Use at BOOT to validate host config (an offer's `timezone`, an org
12
+ * setting) — fail loud at wiring time, not on the first localized read.
13
+ */
14
+ declare function isValidTimeZone(zone: string): boolean;
15
+ /**
16
+ * Every IANA timezone name this runtime supports — the data source for a
17
+ * Google-Calendar-style zone picker. Straight from ICU via
18
+ * `Intl.supportedValuesOf` (Node >= 18); no bundled tz list to go stale.
19
+ *
20
+ * UIs typically label each entry with its current offset:
21
+ * `listTimeZones().map((z) => ({ zone: z, label: zoneOffsetLabel(now, z) }))`.
22
+ */
23
+ declare function listTimeZones(): readonly string[];
24
+ /**
25
+ * The zone's UTC offset AT `instant`, in minutes east of UTC — DST-exact.
26
+ * `zoneOffsetMinutes(july, 'Europe/London')` → 60; `(january, …)` → 0;
27
+ * `(any, 'Asia/Dhaka')` → 360.
28
+ *
29
+ * This is the value `/calendar`'s docstring tells hosts to "resolve and pass
30
+ * in": `startOfMonth(instant, zoneOffsetMinutes(instant, zone))`.
31
+ */
32
+ declare function zoneOffsetMinutes(instant: Date, zone: string): UtcOffsetMinutes;
33
+ /**
34
+ * The zone's offset label AT `instant` — `'GMT+06:00'` / `'GMT-05:30'` /
35
+ * `'GMT'`. Ready-made display text for zone pickers.
36
+ */
37
+ declare function zoneOffsetLabel(instant: Date, zone: string): string;
38
+ interface LocalTimeParts {
39
+ /** ISO weekday in the zone — 1=Mon … 7=Sun (matches `/cadence`). */
40
+ isoWeekday: IsoWeekday;
41
+ /** Hour of day in the zone, 0–23. */
42
+ hour: number;
43
+ /** Minute of hour, 0–59. */
44
+ minute: number;
45
+ /** `hour * 60 + minute` — the shape "HH:MM window" checks want. */
46
+ minutesOfDay: number;
47
+ }
48
+ /**
49
+ * The wall-clock weekday + time of `instant` in `zone` — what pricing
50
+ * schedules ("peak Fri 17:00–22:00"), slot grids, and working-hours checks
51
+ * evaluate against. DST-exact per instant.
52
+ */
53
+ declare function localTimeParts(instant: Date, zone: string): LocalTimeParts;
54
+ /**
55
+ * A calendar day — `'YYYY-MM-DD'` — with NO time and NO zone until one is
56
+ * bound. The type for hotel nights, POS business dates, statement periods,
57
+ * VAT filing years. Lexicographic order == chronological order, so it's
58
+ * index- and range-query-friendly as a Mongo string key.
59
+ */
60
+ type CivilDate = Brand<string, 'CivilDate'>;
61
+ /** Runtime guard for external input. */
62
+ declare function isCivilDate(value: unknown): value is CivilDate;
63
+ /** Validate + brand a `'YYYY-MM-DD'` string. Throws on malformed input. */
64
+ declare function civilDate(value: string): CivilDate;
65
+ /**
66
+ * The calendar day `instant` falls on IN `zone` — e.g. the POS business
67
+ * date: `civilDateOf(new Date(), 'Asia/Dhaka')`. Never depends on the
68
+ * server's TZ env.
69
+ */
70
+ declare function civilDateOf(instant: Date, zone: string): CivilDate;
71
+ /**
72
+ * Bind a civil date (+ optional wall time) to a zone → the exact instant.
73
+ * `civilDateToInstant(night, hotelZone, { hour: 14 })` = check-in instant;
74
+ * with no `time`, local midnight — the DST-exact "start of that day in that
75
+ * zone".
76
+ *
77
+ * Two-pass offset resolution: the offset is looked up AT the candidate
78
+ * instant, then re-checked, so days where a DST transition sits between the
79
+ * wall time and the first guess still resolve correctly. Honest limit: a
80
+ * wall time that does not exist (inside a spring-forward gap) resolves to
81
+ * the instant the shifted clock reaches — the same choice Temporal's
82
+ * `disambiguation: 'compatible'` makes.
83
+ */
84
+ declare function civilDateToInstant(cd: CivilDate, zone: string, time?: {
85
+ hour?: number;
86
+ minute?: number;
87
+ second?: number;
88
+ millisecond?: number;
89
+ }): Date;
90
+ /**
91
+ * Pure calendar-day arithmetic — `addCivilDays('2026-02-28', 1)` →
92
+ * `'2026-03-01'`. Zone-free by definition (a civil date has no zone);
93
+ * use it to iterate hotel nights / statement days.
94
+ */
95
+ declare function addCivilDays(cd: CivilDate, days: number): CivilDate;
96
+ /**
97
+ * Whole days from `a` to `b` (positive when `b` is later) — the NIGHTS
98
+ * count for a `[checkIn, checkOut)` stay expressed as civil dates.
99
+ */
100
+ /**
101
+ * The ISO-8601 week a civil date belongs to, as `YYYY-Www`.
102
+ *
103
+ * Zone-free by definition — a civil date has no zone — so callers resolve the
104
+ * date in their business timezone FIRST (`civilDateOf`) and label it here.
105
+ *
106
+ * ISO week 1 is the week containing the first Thursday, which means the week
107
+ * YEAR can differ from the date's own: 2025-12-29 is `2026-W01`. Naive
108
+ * "day-of-year / 7" numbering gets that wrong every few years, silently
109
+ * splitting one business week across two report buckets.
110
+ */
111
+ declare function isoWeekOfCivilDate(cd: CivilDate): string;
112
+ declare function civilDaysBetween(a: CivilDate, b: CivilDate): number;
113
+ //#endregion
114
+ export { civilDate as a, civilDaysBetween as c, isoWeekOfCivilDate as d, listTimeZones as f, zoneOffsetMinutes as h, addCivilDays as i, isCivilDate as l, zoneOffsetLabel as m, LocalTimeParts as n, civilDateOf as o, localTimeParts as p, TimeZoneError as r, civilDateToInstant as s, CivilDate as t, isValidTimeZone as u };
@@ -1,100 +1,2 @@
1
- import { t as Brand } from "./brand-LLXqoh5J.mjs";
2
- import { IsoWeekday } from "./cadence.mjs";
3
- import { UtcOffsetMinutes } from "./calendar.mjs";
4
-
5
- //#region src/scheduling/timezone.d.ts
6
- declare class TimeZoneError extends Error {
7
- constructor(message: string);
8
- }
9
- /**
10
- * Is `zone` a valid IANA timezone name on this runtime's ICU data?
11
- * Use at BOOT to validate host config (an offer's `timezone`, an org
12
- * setting) — fail loud at wiring time, not on the first localized read.
13
- */
14
- declare function isValidTimeZone(zone: string): boolean;
15
- /**
16
- * Every IANA timezone name this runtime supports — the data source for a
17
- * Google-Calendar-style zone picker. Straight from ICU via
18
- * `Intl.supportedValuesOf` (Node >= 18); no bundled tz list to go stale.
19
- *
20
- * UIs typically label each entry with its current offset:
21
- * `listTimeZones().map((z) => ({ zone: z, label: zoneOffsetLabel(now, z) }))`.
22
- */
23
- declare function listTimeZones(): readonly string[];
24
- /**
25
- * The zone's UTC offset AT `instant`, in minutes east of UTC — DST-exact.
26
- * `zoneOffsetMinutes(july, 'Europe/London')` → 60; `(january, …)` → 0;
27
- * `(any, 'Asia/Dhaka')` → 360.
28
- *
29
- * This is the value `/calendar`'s docstring tells hosts to "resolve and pass
30
- * in": `startOfMonth(instant, zoneOffsetMinutes(instant, zone))`.
31
- */
32
- declare function zoneOffsetMinutes(instant: Date, zone: string): UtcOffsetMinutes;
33
- /**
34
- * The zone's offset label AT `instant` — `'GMT+06:00'` / `'GMT-05:30'` /
35
- * `'GMT'`. Ready-made display text for zone pickers.
36
- */
37
- declare function zoneOffsetLabel(instant: Date, zone: string): string;
38
- interface LocalTimeParts {
39
- /** ISO weekday in the zone — 1=Mon … 7=Sun (matches `/cadence`). */
40
- isoWeekday: IsoWeekday;
41
- /** Hour of day in the zone, 0–23. */
42
- hour: number;
43
- /** Minute of hour, 0–59. */
44
- minute: number;
45
- /** `hour * 60 + minute` — the shape "HH:MM window" checks want. */
46
- minutesOfDay: number;
47
- }
48
- /**
49
- * The wall-clock weekday + time of `instant` in `zone` — what pricing
50
- * schedules ("peak Fri 17:00–22:00"), slot grids, and working-hours checks
51
- * evaluate against. DST-exact per instant.
52
- */
53
- declare function localTimeParts(instant: Date, zone: string): LocalTimeParts;
54
- /**
55
- * A calendar day — `'YYYY-MM-DD'` — with NO time and NO zone until one is
56
- * bound. The type for hotel nights, POS business dates, statement periods,
57
- * VAT filing years. Lexicographic order == chronological order, so it's
58
- * index- and range-query-friendly as a Mongo string key.
59
- */
60
- type CivilDate = Brand<string, 'CivilDate'>;
61
- /** Runtime guard for external input. */
62
- declare function isCivilDate(value: unknown): value is CivilDate;
63
- /** Validate + brand a `'YYYY-MM-DD'` string. Throws on malformed input. */
64
- declare function civilDate(value: string): CivilDate;
65
- /**
66
- * The calendar day `instant` falls on IN `zone` — e.g. the POS business
67
- * date: `civilDateOf(new Date(), 'Asia/Dhaka')`. Never depends on the
68
- * server's TZ env.
69
- */
70
- declare function civilDateOf(instant: Date, zone: string): CivilDate;
71
- /**
72
- * Bind a civil date (+ optional wall time) to a zone → the exact instant.
73
- * `civilDateToInstant(night, hotelZone, { hour: 14 })` = check-in instant;
74
- * with no `time`, local midnight — the DST-exact "start of that day in that
75
- * zone".
76
- *
77
- * Two-pass offset resolution: the offset is looked up AT the candidate
78
- * instant, then re-checked, so days where a DST transition sits between the
79
- * wall time and the first guess still resolve correctly. Honest limit: a
80
- * wall time that does not exist (inside a spring-forward gap) resolves to
81
- * the instant the shifted clock reaches — the same choice Temporal's
82
- * `disambiguation: 'compatible'` makes.
83
- */
84
- declare function civilDateToInstant(cd: CivilDate, zone: string, time?: {
85
- hour?: number;
86
- minute?: number;
87
- }): Date;
88
- /**
89
- * Pure calendar-day arithmetic — `addCivilDays('2026-02-28', 1)` →
90
- * `'2026-03-01'`. Zone-free by definition (a civil date has no zone);
91
- * use it to iterate hotel nights / statement days.
92
- */
93
- declare function addCivilDays(cd: CivilDate, days: number): CivilDate;
94
- /**
95
- * Whole days from `a` to `b` (positive when `b` is later) — the NIGHTS
96
- * count for a `[checkIn, checkOut)` stay expressed as civil dates.
97
- */
98
- declare function civilDaysBetween(a: CivilDate, b: CivilDate): number;
99
- //#endregion
100
- export { CivilDate, LocalTimeParts, TimeZoneError, addCivilDays, civilDate, civilDateOf, civilDateToInstant, civilDaysBetween, isCivilDate, isValidTimeZone, listTimeZones, localTimeParts, zoneOffsetLabel, zoneOffsetMinutes };
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-tlG7ygwx.mjs";
2
+ export { CivilDate, LocalTimeParts, TimeZoneError, addCivilDays, civilDate, civilDateOf, civilDateToInstant, civilDaysBetween, isCivilDate, isValidTimeZone, isoWeekOfCivilDate, listTimeZones, localTimeParts, zoneOffsetLabel, zoneOffsetMinutes };
package/dist/timezone.mjs CHANGED
@@ -78,7 +78,19 @@ function listTimeZones() {
78
78
  cachedZones = Object.freeze(intl.supportedValuesOf("timeZone"));
79
79
  return cachedZones;
80
80
  }
81
- const OFFSET_RE = /GMT(?:([+-])(\d{2}):(\d{2}))?/;
81
+ /**
82
+ * ANCHORED on purpose.
83
+ *
84
+ * The unanchored form (`/GMT(?:([+-])(\d{2}):(\d{2}))?/`) made the offset group
85
+ * OPTIONAL *and* skippable: any label the runtime emits that this pattern cannot
86
+ * fully parse — `'GMT+5:30'` from an ICU build that does not zero-pad, or a
87
+ * locale that prefixes text — still matched the bare `GMT` alternative and
88
+ * resolved to offset **0**. A half-read offset became "UTC" with no error, which
89
+ * silently shifts every business-day boundary derived from it. Anchoring makes an
90
+ * unrecognised label throw instead; `\d{1,2}` accepts the unpadded variant rather
91
+ * than mis-reading it.
92
+ */
93
+ const OFFSET_RE = /^GMT(?:([+-])(\d{1,2}):(\d{2}))?$/;
82
94
  /**
83
95
  * The zone's UTC offset AT `instant`, in minutes east of UTC — DST-exact.
84
96
  * `zoneOffsetMinutes(july, 'Europe/London')` → 60; `(january, …)` → 0;
@@ -204,7 +216,9 @@ function civilDateToInstant(cd, zone, time = {}) {
204
216
  if (!m) throw new TimeZoneError(`Invalid civil date '${cd}'.`);
205
217
  const hour = time.hour ?? 0;
206
218
  const minute = time.minute ?? 0;
207
- const wallAsUtc = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]), hour, minute);
219
+ const second = time.second ?? 0;
220
+ const millisecond = time.millisecond ?? 0;
221
+ const wallAsUtc = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]), hour, minute, second, millisecond);
208
222
  const firstOffset = zoneOffsetMinutes(new Date(wallAsUtc), zone);
209
223
  let instantMs = wallAsUtc - firstOffset * MINUTE_MS;
210
224
  const refined = zoneOffsetMinutes(new Date(instantMs), zone);
@@ -226,6 +240,27 @@ function addCivilDays(cd, days) {
226
240
  * Whole days from `a` to `b` (positive when `b` is later) — the NIGHTS
227
241
  * count for a `[checkIn, checkOut)` stay expressed as civil dates.
228
242
  */
243
+ /**
244
+ * The ISO-8601 week a civil date belongs to, as `YYYY-Www`.
245
+ *
246
+ * Zone-free by definition — a civil date has no zone — so callers resolve the
247
+ * date in their business timezone FIRST (`civilDateOf`) and label it here.
248
+ *
249
+ * ISO week 1 is the week containing the first Thursday, which means the week
250
+ * YEAR can differ from the date's own: 2025-12-29 is `2026-W01`. Naive
251
+ * "day-of-year / 7" numbering gets that wrong every few years, silently
252
+ * splitting one business week across two report buckets.
253
+ */
254
+ function isoWeekOfCivilDate(cd) {
255
+ const m = CIVIL_RE.exec(cd);
256
+ if (!m) throw new TimeZoneError(`Invalid civil date '${cd}'.`);
257
+ const utc = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
258
+ const dow = new Date(utc).getUTCDay() || 7;
259
+ const thursday = new Date(utc + (4 - dow) * 864e5);
260
+ const weekYear = thursday.getUTCFullYear();
261
+ const week = Math.floor((thursday.getTime() - Date.UTC(weekYear, 0, 1)) / 864e5 / 7) + 1;
262
+ return `${weekYear}-W${String(week).padStart(2, "0")}`;
263
+ }
229
264
  function civilDaysBetween(a, b) {
230
265
  const pa = CIVIL_RE.exec(a);
231
266
  const pb = CIVIL_RE.exec(b);
@@ -235,4 +270,4 @@ function civilDaysBetween(a, b) {
235
270
  return Math.round((mb - ma) / 864e5);
236
271
  }
237
272
  //#endregion
238
- export { TimeZoneError, addCivilDays, civilDate, civilDateOf, civilDateToInstant, civilDaysBetween, isCivilDate, isValidTimeZone, listTimeZones, localTimeParts, zoneOffsetLabel, zoneOffsetMinutes };
273
+ export { TimeZoneError, addCivilDays, civilDate, civilDateOf, civilDateToInstant, civilDaysBetween, isCivilDate, isValidTimeZone, isoWeekOfCivilDate, listTimeZones, localTimeParts, zoneOffsetLabel, zoneOffsetMinutes };
@@ -1,5 +1,4 @@
1
- import { r as CurrencyCode } from "./currency-BCJnJBlb.mjs";
2
- import { MinorUnits } from "./money.mjs";
1
+ import { t as MinorUnits, w as CurrencyCode } from "./money-DPo9fLBi.mjs";
3
2
 
4
3
  //#region src/money/unit-cost-rate.d.ts
5
4
  /** Fixed, ecosystem-wide rate scale: 6 decimal places below the minor unit. */