@classytic/primitives 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +934 -906
- package/dist/approval.d.mts +3 -3
- package/dist/bank-transaction.d.mts +1 -2
- package/dist/{brand-LLXqoh5J.d.mts → brand-Lj_75Y_r.d.mts} +6 -6
- package/dist/brand.d.mts +1 -1
- package/dist/cadence.d.mts +1 -1
- package/dist/cadence.mjs +2 -2
- package/dist/calendar.d.mts +1 -1
- package/dist/calendar.mjs +2 -2
- package/dist/condition.mjs +2 -2
- package/dist/context.d.mts +0 -1
- package/dist/currency.d.mts +1 -1
- package/dist/environment.d.mts +90 -0
- package/dist/environment.mjs +53 -0
- package/dist/event-infra.d.mts +18 -1
- package/dist/idempotency.d.mts +11 -1
- package/dist/identity-verification.d.mts +0 -1
- package/dist/memory-outbox.d.mts +1 -2
- package/dist/memory-outbox.mjs +0 -8
- package/dist/mixin.d.mts +1 -1
- package/dist/monetization.d.mts +1 -2
- package/dist/{money-DPo9fLBi.d.mts → money-CfAvfNiM.d.mts} +1 -2
- package/dist/money.d.mts +1 -1
- package/dist/otp.mjs +1 -1
- package/dist/outbox.d.mts +0 -1
- package/dist/payment-allocation-status.d.mts +10 -4
- package/dist/payment-allocation-status.mjs +14 -0
- package/dist/payment-events.d.mts +2 -3
- package/dist/payment-events.mjs +12 -0
- package/dist/payment-gateway.d.mts +3 -3
- package/dist/payment-method-kind.d.mts +23 -11
- package/dist/payment-method-kind.mjs +17 -0
- package/dist/period.d.mts +39 -4
- package/dist/period.mjs +50 -1
- package/dist/person-parse.d.mts +0 -1
- package/dist/phone.d.mts +0 -1
- package/dist/phone.mjs +3 -3
- package/dist/proration.d.mts +1 -2
- package/dist/retention.mjs +3 -3
- package/dist/shelf-life.mjs +1 -1
- package/dist/sla-policy.d.mts +1 -2
- package/dist/state-diagram.d.mts +0 -1
- package/dist/state-diagram.mjs +1 -1
- package/dist/suspension.d.mts +6 -4
- package/dist/suspension.mjs +3 -2
- package/dist/{timezone-tlG7ygwx.d.mts → timezone-CxUjxmRU.d.mts} +3 -4
- package/dist/timezone.d.mts +1 -1
- package/dist/unit-cost-rate.d.mts +1 -2
- package/package.json +7 -2
- /package/dist/{cadence-BFQVC1Qp.d.mts → cadence-5X6TiD49.d.mts} +0 -0
- /package/dist/{calendar-B6fm-FbR.d.mts → calendar-DJWSbcgE.d.mts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,906 +1,934 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
|
-
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
-
|
|
6
|
-
## [0.
|
|
7
|
-
|
|
8
|
-
### Added — `./period`
|
|
9
|
-
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
a
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- **`
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
`
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
- **`
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
- **`
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
- **`
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
`
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
`
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
(`
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
## [0.7.
|
|
406
|
-
|
|
407
|
-
### Added —
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
- **`/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
- **`/
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
- **`/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
###
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
####
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
`
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
`
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
`
|
|
838
|
-
|
|
839
|
-
`
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
`
|
|
843
|
-
- **`/
|
|
844
|
-
`
|
|
845
|
-
- **`/
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
`
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`
|
|
853
|
-
`
|
|
854
|
-
- **`/
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
`
|
|
859
|
-
|
|
860
|
-
`
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
- **`/
|
|
864
|
-
|
|
865
|
-
`
|
|
866
|
-
|
|
867
|
-
`
|
|
868
|
-
`
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
`
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
`
|
|
880
|
-
|
|
881
|
-
`
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
`
|
|
900
|
-
`
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
|
+
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## [0.22.0] - 2026-08-12
|
|
7
|
+
|
|
8
|
+
### Added — `./period`: `shiftPeriod(period, n)` — calendar arithmetic without instants
|
|
9
|
+
|
|
10
|
+
Shift a MONTH or QUARTER period by `n` of its own units (negative = back); YEAR shifts by years; a `custom` (ad-hoc start/end) period has no unit to shift and throws rather than guessing one. No `Date` is constructed and no zone is consulted — shifting a month is pure `(year, month)` arithmetic, and the `timezone` rides along unchanged so the shifted period still resolves against the calendar it was defined in.
|
|
11
|
+
|
|
12
|
+
Exists because both inline shapes callers reach for are wrong the same way: `new Date(Date.UTC(y, m - 1 - n, 1))` answers a business-calendar question with a UTC instant (six hours off at both ends in Asia/Dhaka — the bug class this module's header documents five instances of), and hand-rolled `y * 12 + (m - 1) - n` integer math is a third copy of a rule `Period` already owns, with the year rollover exactly where copies go wrong. First consumer: be-prod `business-date.ts` ("two filing months ago").
|
|
13
|
+
|
|
14
|
+
**Versioning note:** this export briefly existed in a working copy labelled 0.21.0 after 0.21.0 had already been published without it — same version, different surface. 0.22.0 restores the invariant that a version number names exactly one contract; `>=0.22.0` is the correct floor for any consumer importing `shiftPeriod`.
|
|
15
|
+
|
|
16
|
+
### Docs — `./environment` docblock refinement (no API change)
|
|
17
|
+
|
|
18
|
+
The 0.21.0 entry below originally listed `isProduction` / `isDevelopment` / `isTest`; the published surface is `classifyEnv`, `EnvClass`, `isProductionEnv` only — `isProductionEnv` is deliberately the single predicate (consumers needing all three buckets build their own triple from `classifyEnv`). The entry below is corrected in place; runtime and declarations are byte-identical to published 0.21.0 apart from comments.
|
|
19
|
+
|
|
20
|
+
## [0.21.0] - 2026-08-10
|
|
21
|
+
|
|
22
|
+
### Added — `./environment`: canonical `NODE_ENV` classification
|
|
23
|
+
|
|
24
|
+
`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.
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
Exports: `classifyEnv`, `EnvClass`, `isProductionEnv`.
|
|
29
|
+
|
|
30
|
+
### Added — `publint` gate on build
|
|
31
|
+
|
|
32
|
+
`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.
|
|
33
|
+
|
|
34
|
+
## [0.20.0] - 2026-08-05
|
|
35
|
+
|
|
36
|
+
### Added — `./period` OWNS timezone-aware period resolution (was two copies above the kernels)
|
|
37
|
+
|
|
38
|
+
`Period` was `{ year, month?, quarter? }` with no timezone and no resolver, so it
|
|
39
|
+
could not express "the business month of August in Asia/Dhaka". The resolver
|
|
40
|
+
existed TWICE, both ABOVE the kernels — `@spinekit/kit/period`
|
|
41
|
+
(`createBusinessCalendar`) and be-prod's `#lib/utils/business-date` — which meant
|
|
42
|
+
a kernel could reach neither and re-derived the boundary inline with `Date.UTC`.
|
|
43
|
+
Five silent UTC-month defects came out of that (`aggregateMonthlyVat`, the
|
|
44
|
+
sales-fact reconciler, and three in bd-tax Mushak).
|
|
45
|
+
|
|
46
|
+
This is a MOVE of the proven `createBusinessCalendar` logic, not a re-derivation:
|
|
47
|
+
same civil-date round-trip through `/timezone`, same half-open ranges, same
|
|
48
|
+
DST-exactness.
|
|
49
|
+
|
|
50
|
+
- **`Period.timezone?`** — the IANA zone a period is DEFINED in, so a persisted or
|
|
51
|
+
transported period keeps its own calendar.
|
|
52
|
+
- **`resolvePeriod(period, timezone?)`** → half-open `DateRange`. Throws when no
|
|
53
|
+
zone is available anywhere (never silently answers in UTC) and throws when the
|
|
54
|
+
period's zone and the caller's zone DISAGREE.
|
|
55
|
+
- **`resolveDay` / `resolveMonth` / `resolveQuarter` / `resolveYear` /
|
|
56
|
+
`resolveDateSpan` / `dayStart`** — the granularity-specific forms. All return
|
|
57
|
+
HALF-OPEN `[start, end)`, which tiles exactly.
|
|
58
|
+
- **`periodOf(instant, granularity, timezone)`** — the inverse: which filing
|
|
59
|
+
period an instant belongs to on the LOCAL calendar.
|
|
60
|
+
- **`parsePeriod` / `formatPeriod` / `granularityOf` / `periodTimeZone`** —
|
|
61
|
+
`'YYYY'` · `'YYYY-MM'` · `'YYYY-Qn'`. `parsePeriod` REFUSES a `'YYYY-MM-DD'`
|
|
62
|
+
rather than reading it as its month (a ~30× widening).
|
|
63
|
+
- **`inclusiveEnd(range)`** — `end - 1ms` for legacy `$lte` call sites, derived
|
|
64
|
+
from the resolved end so it stays DST-exact.
|
|
65
|
+
- **`PeriodError`** + **`PeriodErrorCode`** (`AMBIGUOUS_PERIOD` ·
|
|
66
|
+
`INCOMPLETE_RANGE` · `INVALID_FIELD` · `MISSING_TIMEZONE` · `TIMEZONE_CONFLICT`
|
|
67
|
+
· `INVALID_LABEL`).
|
|
68
|
+
|
|
69
|
+
A `Period` carrying two resolution shapes (`month` + `quarter`, or either with
|
|
70
|
+
`start`/`end`) now throws instead of preferring one.
|
|
71
|
+
|
|
72
|
+
### Added — `./idempotency`: the claim + lease CONTRACT
|
|
73
|
+
|
|
74
|
+
Three kernels hand-rolled incompatible versions of one state machine — `cart`
|
|
75
|
+
(`in_flight`/`succeeded`/`failed`, the only one with crash-window leasing),
|
|
76
|
+
`catalog` offers (`pending`/`completed`, no lease), `contract` amendments
|
|
77
|
+
(implicit). Types + PURE helpers only; no persistence, no collection, no driver.
|
|
78
|
+
|
|
79
|
+
- **`ClaimOutcome = 'claimed' | 'replayed' | 'in_flight'`** — three-valued, and
|
|
80
|
+
`in_flight` is NOT a failure. Mapping a live concurrent lease onto an error
|
|
81
|
+
licenses a retry, which is a double-apply.
|
|
82
|
+
- **`IdempotencyClaim`** (`identity` · `requestFingerprint` · `state` ·
|
|
83
|
+
`leaseToken` · `leaseExpiresAt` · `attempts` · `result`), **`ClaimResult`**,
|
|
84
|
+
**`ClaimState`**, **`IdempotencyIdentity`**.
|
|
85
|
+
- **`decideClaim(existing, request)`** — the whole state machine as one pure
|
|
86
|
+
function. Throws `FINGERPRINT_MISMATCH` (same key, different body) and
|
|
87
|
+
`MISSING_REPLAY` (terminal record with no stored result) rather than answering.
|
|
88
|
+
- **`completeClaim` / `renewLease`** — both refuse when the caller lost its lease,
|
|
89
|
+
so a superseded attempt cannot overwrite the winner's result.
|
|
90
|
+
- **`identityKey`** (canonical-JSON composite, so a delimiter inside a scope value
|
|
91
|
+
cannot collide two identities), **`fingerprintRequest`**, **`newLeaseToken`**,
|
|
92
|
+
**`isLeaseExpired`**, **`holdsLease`**, **`isInFlight`**, **`DEFAULT_LEASE_MS`**.
|
|
93
|
+
|
|
94
|
+
### Changed — BREAKING: five API decisions taken before spine wiring starts
|
|
95
|
+
|
|
96
|
+
- **`./cadence` — `Cadence.timezone` is now AUTHORITATIVE, not display-only.**
|
|
97
|
+
It was documented as decorative while `stepMonthly`/`stepYearly` used
|
|
98
|
+
`getUTCMonth()`/`getUTCDate()`, so `timezone: 'Asia/Dhaka'` + `dayOfMonth: 1`
|
|
99
|
+
with a midnight anchor placed every occurrence on the local **2nd**. All four
|
|
100
|
+
kinds now run on the zone's wall clock when one is set (daily advances LOCAL
|
|
101
|
+
days — 23h/25h across a DST transition), and stay UTC when it is absent.
|
|
102
|
+
`validateCadence` rejects an unresolvable zone with the new
|
|
103
|
+
`INVALID_TIMEZONE`. **Behavioural change for any persisted cadence that
|
|
104
|
+
already carries a `timezone`** — the occurrences move to where the field
|
|
105
|
+
always claimed they were.
|
|
106
|
+
- **`./approval` — `assertApproved` no longer passes on a `null` chain.** It now
|
|
107
|
+
throws the new `CHAIN_MISSING`. Absence is not approval, and a mongoose schema
|
|
108
|
+
that never declared `approvalChain` strips it on write, so the gate could not
|
|
109
|
+
tell "no approval configured" from "approval silently lost". The explicit
|
|
110
|
+
opt-out is the new **`assertApprovedIfPresent`**. The second parameter is now
|
|
111
|
+
an options object (`{ message }`), not a bare string.
|
|
112
|
+
- **`./payment-events` — every payload carries an `eventType` discriminant, and
|
|
113
|
+
there is a `payment.unknown` event.** `PaymentInitiatedPayload` and
|
|
114
|
+
`PaymentSucceededPayload` were structurally IDENTICAL with no discriminant on
|
|
115
|
+
the union, so a handler typed for "funds received" accepted "money in flight".
|
|
116
|
+
Adds **`PaymentUnknownPayload`** (the port contract's third outcome had no
|
|
117
|
+
event, so an unobserved result had to be forced into SUCCEEDED or FAILED),
|
|
118
|
+
**`PaymentEventPayloadMap`**, **`isPaymentEvent`**, **`isFundsReceived`**, and
|
|
119
|
+
three compile-time drift guards over the map.
|
|
120
|
+
- **`./currency` — `convertWithSnapshot` / `reverseWithSnapshot` take and return
|
|
121
|
+
`Money`.** A bare `number` meant nothing checked the amount was denominated in
|
|
122
|
+
`fx.sourceCurrency`; a USD→BDT snapshot applied to a EUR amount type-checked
|
|
123
|
+
and returned a plausible wrong number. Both now throw `CurrencyMismatchError`
|
|
124
|
+
on a mismatch, handle differing minor-unit exponents (USD 2 → JPY 0), and take
|
|
125
|
+
an explicit **`FxRounding`** (`'half-away-from-zero'` default · `'floor'` ·
|
|
126
|
+
`'ceil'`) instead of returning an unrounded float for each caller to round
|
|
127
|
+
differently.
|
|
128
|
+
- **`./money` — `Money.currency` is `CurrencyCode`, not `CurrencyCode | string`.**
|
|
129
|
+
A union with `string` collapses to `string`, so the brand was decoration on
|
|
130
|
+
the one type where it matters and `money()` validated nothing. `money()`,
|
|
131
|
+
`fromMajor()` and `sumMoney()` now validate and brand; `isMoney` format-checks
|
|
132
|
+
the code instead of `typeof === 'string'`. New **`currencyCode(value)`**
|
|
133
|
+
(throwing constructor) and **`InvalidCurrencyCodeError`**.
|
|
134
|
+
**`CurrencyMismatchError` is now DECLARED in `./currency`** (so the FX seam can
|
|
135
|
+
throw it without an ESM cycle) and re-exported from `./money`, so existing
|
|
136
|
+
imports are unaffected.
|
|
137
|
+
|
|
138
|
+
Migration: a `Money` object literal now needs a branded code —
|
|
139
|
+
`money(1999, 'USD')`, or `{ amount: 1999, currency: currencyCode('USD') }`.
|
|
140
|
+
|
|
141
|
+
### Changed — six silent-permissiveness defects now FAIL instead of answering
|
|
142
|
+
|
|
143
|
+
Every item below previously returned a plausible-looking value where it should
|
|
144
|
+
have errored. All are behaviour changes for malformed input only; correct input
|
|
145
|
+
is unaffected. Falsifying tests live in `tests/unit/fail-loud-regressions.test.ts`.
|
|
146
|
+
|
|
147
|
+
- **`./period` — an Invalid Date no longer reports "no overlap".** `rangesOverlap`,
|
|
148
|
+
`isWithin` and `rangeDurationMs` throw the new `DateRangeError` on a non-finite
|
|
149
|
+
endpoint; `isDateRange` returns `false` for one. Every comparison against `NaN`
|
|
150
|
+
is `false`, so a corrupt range used to answer "no conflict" — the PERMISSIVE
|
|
151
|
+
answer for the three documented consumers (order booking collision, flow
|
|
152
|
+
reservation-window collision, promo campaign overlap), i.e. a silent double-book.
|
|
153
|
+
- **`./currency` — the ISO 4217 exponent table is now exhaustive.** All 0-decimal
|
|
154
|
+
(`BIF CLP DJF GNF ISK KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF`), 3-decimal
|
|
155
|
+
(`BHD IQD JOD KWD LYD OMR TND`) and 4-decimal (`CLF UYW`) currencies are listed,
|
|
156
|
+
so `minorUnitFactor`'s `?? 100` fallback is reachable only by a code that is not
|
|
157
|
+
a real ISO currency. Previously `fromMajor(1500, 'GNF')` stored 150 000 minor
|
|
158
|
+
units — a 100× error that looks like a number. New `isKnownCurrency(code)`
|
|
159
|
+
distinguishes "listed" from "assumed" for boot-time config validation.
|
|
160
|
+
- **`./proration` — a missing allocated part throws instead of becoming `0`.** The
|
|
161
|
+
`?? 0` fallbacks in `splitByPeriodFraction` / `allocateMoneyByFraction` are now
|
|
162
|
+
`ProrationError('ALLOCATION_INVARIANT')`. A zero credit and a genuinely
|
|
163
|
+
fully-consumed period were indistinguishable downstream.
|
|
164
|
+
- **`./state-machine` — `assertAndClaim` rejects an EMPTY `from` list.**
|
|
165
|
+
`validSources(to)` returns `[]` for an unreachable status, and the documented
|
|
166
|
+
`from: machine.validSources(x)` idiom then ran the assert loop zero times and
|
|
167
|
+
handed the CAS an empty allow-list. An empty allow-list must never mean "any".
|
|
168
|
+
- **`./timezone` — the offset regex is anchored.** An offset label the runtime
|
|
169
|
+
emits in an unexpected shape (e.g. an unpadded `GMT+5:30`) matched the optional
|
|
170
|
+
bare-`GMT` branch and resolved to **UTC**, silently shifting every business-day
|
|
171
|
+
boundary derived from it. Unrecognised labels now throw; `\d{1,2}` accepts the
|
|
172
|
+
unpadded variant rather than mis-reading it.
|
|
173
|
+
- **`./canonical` — class instances are rejected, not collapsed.** A
|
|
174
|
+
`Types.ObjectId`, `Buffer` or hydrated Mongoose subdocument has no own
|
|
175
|
+
enumerable keys, so it digested as `{}` and two manifests referencing DIFFERENT
|
|
176
|
+
documents hashed identically — the integrity check reported "unchanged".
|
|
177
|
+
`canonicalJson` now throws `CanonicalizeError` for any object whose prototype is
|
|
178
|
+
neither `Object.prototype` nor `null`. Plain objects, null-prototype objects,
|
|
179
|
+
arrays and `Date` are unaffected.
|
|
180
|
+
|
|
181
|
+
### Changed — the publish smoke gate now sweeps the whole export map
|
|
182
|
+
|
|
183
|
+
`tests/smoke/smoke.mjs` derives its subpath list from `package.json#exports`
|
|
184
|
+
(44/44) instead of a hand-maintained list that covered 23, and asserts each entry
|
|
185
|
+
declares both `types` and `default` and that both files exist. The gate runs in
|
|
186
|
+
`prepublishOnly`; previously a subpath that lost its tsdown entry would have
|
|
187
|
+
published an export map pointing at a missing file with the gate printing OK.
|
|
188
|
+
|
|
189
|
+
## [0.19.0] - 2026-08-01
|
|
190
|
+
|
|
191
|
+
### Added — `./monetization`: canonical pricing classification contract
|
|
192
|
+
|
|
193
|
+
- **`@classytic/primitives/monetization`** — the single home for how a product
|
|
194
|
+
is sold and priced, so the classification survives end-to-end across catalog,
|
|
195
|
+
revenue, order, and entitlement kernels without each maintaining its own enum.
|
|
196
|
+
- **`Monetization`** discriminated union — `FreeMonetization | OneTimeMonetization |
|
|
197
|
+
SubscriptionMonetization | BundleMonetization | UsageMonetization`, each
|
|
198
|
+
carrying its own nested pricing shape.
|
|
199
|
+
- **`MONETIZATION_KINDS`** + **`MonetizationKind`** + **`isMonetizationKind`** —
|
|
200
|
+
exhaustive kind list with runtime guard.
|
|
201
|
+
- Type-guard predicates: **`isFreeMonetization`**, **`isOneTimeMonetization`**,
|
|
202
|
+
**`isSubscriptionMonetization`**, **`isBundleMonetization`**,
|
|
203
|
+
**`isUsageMonetization`**.
|
|
204
|
+
- Supporting types: `PriceTier`, `OneTimePricing`, `SubscriptionPlan`,
|
|
205
|
+
`UsageTier`, `UsageRating`, `DurationUnit`, `MoneyByCurrency`.
|
|
206
|
+
|
|
207
|
+
### Added — `./proration`: pure proration arithmetic
|
|
208
|
+
|
|
209
|
+
- **`@classytic/primitives/proration`** — timezone-agnostic, policy-free
|
|
210
|
+
proration math. Holds no commercial policy (no upgrade/downgrade semantics,
|
|
211
|
+
no discount or tax logic) — those live in the contract kernel.
|
|
212
|
+
- **`periodProgress(input)`** — fraction of a billing period consumed given
|
|
213
|
+
start, end, and current instants as `Date`. Returns `PeriodFraction`.
|
|
214
|
+
- **`splitByPeriodFraction(amount, fraction, granularity?)`** — split a `Money`
|
|
215
|
+
amount into consumed/remaining parts; `granularity: 'whole_day'` (default) or
|
|
216
|
+
`'exact'`.
|
|
217
|
+
- **`allocateMoneyByFraction(amount, fraction)`** — proportional allocation
|
|
218
|
+
using the largest-remainder allocator so `consumed + remaining === amount`
|
|
219
|
+
exactly (no penny drift).
|
|
220
|
+
- **`ProrationError`** + **`ProrationErrorCode`** (`INVALID_PERIOD` |
|
|
221
|
+
`INVALID_FRACTION`) — typed errors for out-of-range inputs.
|
|
222
|
+
- Supporting types: `PeriodFraction`, `PeriodProgressInput`,
|
|
223
|
+
`ProrationGranularity`.
|
|
224
|
+
|
|
225
|
+
## [0.18.0] - 2026-07-29
|
|
226
|
+
|
|
227
|
+
### Added — `./subject`: `SubjectRef` polymorphic identity pointer
|
|
228
|
+
|
|
229
|
+
- **`@classytic/primitives/subject`** — `SubjectRef` (`{ subjectModel, subjectRef }`) — WHO a record is about, polymorphically. The pairing spans every kernel that holds data about a person without owning them (attendance, access, loyalty, credentials). Deliberately kept distinct from `ExternalRef` (provenance — "came from") even though the shapes are identical: a grant carries both at once and they mean different things.
|
|
230
|
+
- **`buildSubjectRef(model, id)`** — returns `SubjectRef | null`; `null` when `id` is absent/blank, because a malformed binding silently matches nothing (an entitlement nobody holds, a credential no revoke can find) rather than carrying `"undefined"` as a string.
|
|
231
|
+
- **`SubjectRef`** type exported from `@classytic/primitives/subject`.
|
|
232
|
+
|
|
233
|
+
### Added — `./state-diagram`: Mermaid diagram from a `StateMachine`
|
|
234
|
+
|
|
235
|
+
- **`@classytic/primitives/state-diagram`** — `renderStateDiagram(machine, opts?)` — generates a `stateDiagram-v2` Mermaid string from the same `StateMachine` object the runtime CAS enforces. Removes the class of drift where a hand-drawn prose comment diverges from the actual transition table. Pure: string in, string out, no I/O.
|
|
236
|
+
- **`StateDiagramOptions`** — `title?`, `highlight?` (mark specific states), `noteOn?` (per-state annotations).
|
|
237
|
+
|
|
238
|
+
### Added — `./suspension`: time-bounded suspension/pause policy
|
|
239
|
+
|
|
240
|
+
- **`@classytic/primitives/suspension`** — pure suspension policy computation (no persistence). Models a pause the subject is ENTITLED to take, bounded by an annual allowance, that ends by itself — distinct from `/hold` (an indefinite blocker needing manual resolution).
|
|
241
|
+
- **`evaluateSuspension(policy, history, opts)`** — always returns `autoResumeAt` (the instant the pause MUST end) so a sweep can resume it whether or not the subject ever comes back. Covers: gym membership freeze, subscription pause, unpaid leave.
|
|
242
|
+
- **`SuspensionPolicy`** — `maxDaysPerPeriod`, `minDurationDays`, `period` ('annual' | 'rolling').
|
|
243
|
+
- **`SuspensionEntry`** — a historical pause record (`startedAt`, `endedAt?`, `reason?`).
|
|
244
|
+
- **`SuspensionResult`** — `autoResumeAt`, `daysUsed`, `daysRemaining`, `status`.
|
|
245
|
+
|
|
246
|
+
### Added — `OutboxStore.transactionalSave` (`/outbox`)
|
|
247
|
+
|
|
248
|
+
- **`OutboxStore.transactionalSave?: boolean`** — declares whether `save` enlists `options.session` in the caller's transaction. Absent/`false` = "assume NOT atomic". Set `true` ONLY when `save` passes the session to the same transactional resource the caller writes (e.g. a mongoose model on the same connection). Callers that need atomicity (`@classytic/access`'s `atomically()`) check this flag and fail at boot rather than claiming a guarantee they cannot keep.
|
|
249
|
+
- **`MemoryOutboxStore.transactionalSave = false`** — explicitly declared; an in-memory store cannot participate in a Mongo transaction, so its write survives a rollback.
|
|
250
|
+
|
|
251
|
+
### Changed — `shelf-life.ts` deduplicates `addDays`
|
|
252
|
+
|
|
253
|
+
- `addDays` is now imported from `./calendar` instead of being re-defined inline. No behavior change.
|
|
254
|
+
|
|
255
|
+
## [0.16.0] - 2026-07-27
|
|
256
|
+
|
|
257
|
+
### Added — `assertApproved` + `CHAIN_INCOMPLETE` (`/approval`)
|
|
258
|
+
|
|
259
|
+
- **`ApprovalErrorCode` gains `'CHAIN_INCOMPLETE'`** — the single canonical
|
|
260
|
+
error code for "finalize/commit attempted before the chain reached `approved`".
|
|
261
|
+
Hosts route it through `rethrowApprovalError` to the `approval.chain_incomplete`
|
|
262
|
+
wire code so all commit/post/finalize boundaries raise the same structured error.
|
|
263
|
+
- **`assertApproved(chain, message?)`** — canonical finalize-time gate: asserts
|
|
264
|
+
the chain has reached `approved`, else throws `ApprovalError('CHAIN_INCOMPLETE')`.
|
|
265
|
+
A `null`/absent chain passes (no chain required). Use at every journal-entry
|
|
266
|
+
post, PO approve, transfer dispatch, etc. to replace per-domain hand-rolled
|
|
267
|
+
divergent codes with one definition.
|
|
268
|
+
|
|
269
|
+
Purely additive — no existing exports changed.
|
|
270
|
+
|
|
271
|
+
## [0.15.0] - 2026-07-25
|
|
272
|
+
|
|
273
|
+
### Added — `/retention`: `PurgeEvidence` value object
|
|
274
|
+
|
|
275
|
+
- **New `@classytic/primitives/retention` subpath** exporting **`PurgeEvidence`**
|
|
276
|
+
— the persisted evidence record for a GDPR / data-retention purge operation
|
|
277
|
+
(proof of what was purged, over what scope, by whom, why, and whether
|
|
278
|
+
analytical measures were retained). Shape: `{ id, subject: { ref, model? },
|
|
279
|
+
scope, strategy: 'hard'|'soft'|'anonymize', measuresRetained, processed,
|
|
280
|
+
occurredAt, actor: { ref, kind: 'user'|'system'|'service' }, reason,
|
|
281
|
+
legalBasis?, status: 'completed'|'partial'|'failed', operationId?,
|
|
282
|
+
startedAt?, completedAt?, results?: PurgeResourceResult[],
|
|
283
|
+
verification?: PurgeVerificationSummary }`.
|
|
284
|
+
- **`createPurgeEvidence(input)`** — pure builder (auto-fills `id` + `occurredAt`
|
|
285
|
+
when absent, defaults `status` to `'completed'`). **`isPurgeEvidence(value)`**
|
|
286
|
+
type guard + **`assertPurgeEvidence(value)`** throwing assertion.
|
|
287
|
+
|
|
288
|
+
### Added — `/canonical`: strict deterministic JSON + integrity digest
|
|
289
|
+
|
|
290
|
+
- **New `@classytic/primitives/canonical` subpath** — `canonicalJson(value)`
|
|
291
|
+
produces a deterministic string (keys sorted recursively, `Date` → `{"$date":"<iso>"}`,
|
|
292
|
+
strict rejection of `undefined`/`NaN`/`BigInt`/`Map`/`Set`/cycles with
|
|
293
|
+
`CanonicalizeError`). **`sha256Hex(input)`** and **`canonicalDigest(value)`**
|
|
294
|
+
(= `sha256Hex(canonicalJson(value))`) — integrity checksums, not tamper-proof
|
|
295
|
+
seals. Uses `node:crypto` directly (same precedent as `/otp`).
|
|
296
|
+
|
|
297
|
+
### Added — `/event-infra`: `propagateHandlerErrors` option
|
|
298
|
+
|
|
299
|
+
- **`InProcessEventBusOptions.propagateHandlerErrors?: boolean`** (default
|
|
300
|
+
`false` — existing error-isolating behavior unchanged). When `true`, `publish`
|
|
301
|
+
runs handlers sequentially and rethrows the first handler error to the
|
|
302
|
+
publisher — for single-consumer projection/relay lanes where a failed handler
|
|
303
|
+
must not be silently acked. `publishMany` maps each event's failure into its
|
|
304
|
+
`PublishManyResult` entry instead of rejecting the batch.
|
|
305
|
+
|
|
306
|
+
## [0.13.0] - 2026-07-22
|
|
307
|
+
|
|
308
|
+
### Added — `/unit-cost-rate`: exact per-unit monetary rates (bigint-safe)
|
|
309
|
+
|
|
310
|
+
- **`@classytic/primitives/unit-cost-rate`** — fractional per-unit monetary
|
|
311
|
+
rates stored as scaled integers (`scaledAmount = minorPerUnit × RATE_SCALE`,
|
|
312
|
+
`RATE_SCALE = 1_000_000`). Solves the WAC / FIFO valuation problem where
|
|
313
|
+
dividing a total by a fractional quantity yields a sub-minor-unit rate
|
|
314
|
+
(100 paisa ÷ 3 units = 33.333… paisa/unit) that must not lose precision.
|
|
315
|
+
Exports:
|
|
316
|
+
- `unitCostRate(minorPerUnit, currency)` — direct rate constructor
|
|
317
|
+
- `unitCostRateFromTotal(totalMinor, quantity, currency)` — WAC/FIFO
|
|
318
|
+
constructor; bigint-safe, QTY_SCALE grid for fractional quantities
|
|
319
|
+
- `extendedAmount(rate, qty, mode?)` — the ONE rate→total rounding boundary;
|
|
320
|
+
bigint multiplication to keep large amounts exact; default `'half-even'`
|
|
321
|
+
(banker's rounding), `'half-up'` available for legacy parity
|
|
322
|
+
- `rateMinorPerUnit(rate)` — display helper (fractional number, NOT for math)
|
|
323
|
+
- `isUnitCostRate(value)` — structural type guard
|
|
324
|
+
- `UnitCostRate<C>` interface, `UnitCostRateError`, `UnitCostRateErrorCode`,
|
|
325
|
+
`RoundingMode`, `RATE_SCALE`
|
|
326
|
+
- For distributing a top-down charge across lines with no lost minor unit,
|
|
327
|
+
compose with `allocate` from `@classytic/primitives/split-allocation`.
|
|
328
|
+
|
|
329
|
+
### Changed — `/outbox` is now the canonical contract owner
|
|
330
|
+
|
|
331
|
+
- **`@classytic/primitives/outbox` owns the transactional-outbox contract.**
|
|
332
|
+
Previously this file was documented as a mirror of `@classytic/arc`'s copy
|
|
333
|
+
("arc is source of truth, keep bit-identical") while arc's docs said the
|
|
334
|
+
opposite — a manual-sync drift risk. Settled: primitives owns pure
|
|
335
|
+
cross-package contracts (events + outbox); arc owns runtime behavior
|
|
336
|
+
(`EventOutbox` relay, stores, backoff, Fastify integration) and, from
|
|
337
|
+
arc 2.24+, imports + re-exports this contract instead of duplicating it.
|
|
338
|
+
- **Cross-package `instanceof` now works.** Because arc re-exports these exact
|
|
339
|
+
`OutboxOwnershipError` / `InvalidOutboxEventError` classes, a store built
|
|
340
|
+
against primitives throws the same class identity arc's relay catches.
|
|
341
|
+
Under the old duplication, arc's `instanceof` checks silently missed
|
|
342
|
+
primitives-thrown ownership errors.
|
|
343
|
+
- No export names or signatures changed — contract surface is identical.
|
|
344
|
+
|
|
345
|
+
## [0.11.0] - 2026-07-08
|
|
346
|
+
|
|
347
|
+
### Added — eKYC vocabulary + secure OTP primitive
|
|
348
|
+
|
|
349
|
+
- **`@classytic/primitives/identity-verification`** — cross-package eKYC data shapes:
|
|
350
|
+
`IdentityDocumentType`, `VerificationCheck`, `VerificationLevel`, `SubjectKind`,
|
|
351
|
+
`IdentitySubject`, `DocumentReference`, `IdentityProviderCapabilities`,
|
|
352
|
+
`VerificationStatus`, `CheckResult`, `CreateVerificationParams`,
|
|
353
|
+
`VerificationSession`, `VerificationWebhookEvent`. Pure helpers: `isVerified`,
|
|
354
|
+
`checksSatisfy`, `deriveStatus`. The identity twin of `payment-gateway` — shapes
|
|
355
|
+
here, contracts in engines, provider implementations anywhere. Serves `@classytic/esign`
|
|
356
|
+
(IdentityBridge port), `@classytic/payee` (ComplianceBridge), and PSP merchant KYC.
|
|
357
|
+
- **`@classytic/primitives/otp`** — secure OTP challenge lifecycle. Guarantees: code
|
|
358
|
+
never persisted (only HMAC-SHA256 digest stored), constant-time `timingSafeEqual`
|
|
359
|
+
comparison, rejection-free `randomInt` CSPRNG (no modulo bias), bounded attempt
|
|
360
|
+
counter, TTL expiry, single-use verified state. Exports: `createOtpChallenge`,
|
|
361
|
+
`verifyOtpChallenge`, `isOtpVerified`, `isOtpActive`. Delivery stays in
|
|
362
|
+
`@classytic/notifications` — this primitive owns only the verify half.
|
|
363
|
+
|
|
364
|
+
## [0.9.1] - 2026-07-04
|
|
365
|
+
|
|
366
|
+
### Added
|
|
367
|
+
|
|
368
|
+
- **`CURRENCY_PATTERN` exported from `/currency`** — the ISO 4217 regex
|
|
369
|
+
(`/^[A-Z]{3}$/`) behind `isCurrencyCode` / `toCurrencyCode`. Consumers
|
|
370
|
+
embed the SAME pattern in JSON-Schema-representable validators (zod
|
|
371
|
+
`.regex`, Mongoose `match`, OpenAPI `pattern`) instead of hand-rolling
|
|
372
|
+
copies — `.refine(isCurrencyCode)` validates at runtime but is dropped
|
|
373
|
+
by `z.toJSONSchema`, silently losing the constraint from generated API
|
|
374
|
+
docs.
|
|
375
|
+
|
|
376
|
+
## [0.9.0] - 2026-07-02
|
|
377
|
+
|
|
378
|
+
### Added — `/timezone`: IANA resolution + civil dates (the door for `/calendar`'s escape hatch)
|
|
379
|
+
|
|
380
|
+
`/calendar` owns wall-clock ARITHMETIC over a fixed `UtcOffsetMinutes` and
|
|
381
|
+
tells hosts to "resolve the offset for the instant and pass it in" — but the
|
|
382
|
+
stack had no shared resolver, so consumers hand-rolled `Intl.DateTimeFormat`
|
|
383
|
+
(three divergent copies found: catalog ×2, be-prod POS). `/timezone` is that
|
|
384
|
+
resolver — zero-dep (Intl IS the ICU tz database), Temporal-shaped names for
|
|
385
|
+
a future mechanical migration:
|
|
386
|
+
|
|
387
|
+
- **`zoneOffsetMinutes(instant, zone)`** — per-instant, DST-exact offset;
|
|
388
|
+
composes directly with `/calendar`: `startOfMonth(i, zoneOffsetMinutes(i, z))`.
|
|
389
|
+
- **`localTimeParts(instant, zone)`** — `{ isoWeekday, hour, minute,
|
|
390
|
+
minutesOfDay }` in the zone; what pricing windows / slot grids /
|
|
391
|
+
working-hours checks evaluate against (canonicalizes catalog's resolvers).
|
|
392
|
+
- **`CivilDate`** (branded `'YYYY-MM-DD'`) + `civilDate` / `isCivilDate` /
|
|
393
|
+
`civilDateOf(instant, zone)` / `civilDateToInstant(cd, zone, time?)`
|
|
394
|
+
(two-pass DST-safe) / `addCivilDays` / `civilDaysBetween` — the type for
|
|
395
|
+
hotel nights, POS business dates, VAT filing years. Lexicographic ==
|
|
396
|
+
chronological, so it's a range-queryable Mongo string key.
|
|
397
|
+
- **`isValidTimeZone(zone)`** — boot-time host-config validation;
|
|
398
|
+
**`listTimeZones()`** + **`zoneOffsetLabel(instant, zone)`** — the data
|
|
399
|
+
source + label for Google-Calendar-style zone pickers, straight from ICU
|
|
400
|
+
(`Intl.supportedValuesOf`), no bundled list to go stale.
|
|
401
|
+
- `TimeZoneError`; formatter instances cached per zone.
|
|
402
|
+
|
|
403
|
+
No arithmetic here (that stays in `/calendar`) — resolution only.
|
|
404
|
+
|
|
405
|
+
## [0.7.2] - 2026-05-30
|
|
406
|
+
|
|
407
|
+
### Added — `BankTransaction` per-account routing + pending lifecycle fields
|
|
408
|
+
|
|
409
|
+
Three additive optional fields on `BankTransaction` (`/bank-transaction`):
|
|
410
|
+
|
|
411
|
+
- **`sourceAccountId?: string`** — the vendor-side account this row belongs to
|
|
412
|
+
(Plaid `account_id`, Xero `BankAccount.AccountID`). Sync feeds return
|
|
413
|
+
transactions spanning multiple accounts in one batch; this lets consumers
|
|
414
|
+
route each row to the right account per-transaction (multi-account sync)
|
|
415
|
+
instead of forcing the whole batch onto one account. Statement formats
|
|
416
|
+
(single-account files) leave it undefined — the account lives on
|
|
417
|
+
`BankStatement.account`.
|
|
418
|
+
- **`pending?: boolean`** — the entry hasn't posted/cleared yet (real-time
|
|
419
|
+
feeds like Plaid `pending: true`; statement formats leave it undefined).
|
|
420
|
+
Consumers must treat pending rows as provisional — don't reconcile/post
|
|
421
|
+
them as final.
|
|
422
|
+
- **`supersedesExternalId?: string`** — set on a POSTED row that replaces an
|
|
423
|
+
earlier PENDING one, carrying the pending row's `externalId` (Plaid
|
|
424
|
+
`pending_transaction_id`). Pending and posted rows have different
|
|
425
|
+
`externalId`s, so consumers MUST use this back-pointer to drop/replace the
|
|
426
|
+
superseded pending row on ingest — otherwise both persist as duplicates.
|
|
427
|
+
|
|
428
|
+
Backward-compatible (both optional). Closes a fidelity gap where Plaid's
|
|
429
|
+
pending state was dropped at the canonical boundary, so downstream consumers
|
|
430
|
+
(`@classytic/fin-io` Plaid mapper, `@classytic/revenue` import) couldn't
|
|
431
|
+
distinguish provisional rows or dedupe pending→posted.
|
|
432
|
+
|
|
433
|
+
## [0.7.1] - 2026-05-26
|
|
434
|
+
|
|
435
|
+
### Added — workflow + scheduling + composition primitives
|
|
436
|
+
|
|
437
|
+
Four new flat subpaths (additive):
|
|
438
|
+
|
|
439
|
+
- **`/status-history`** — append-only status transition log primitive.
|
|
440
|
+
- **`/condition`** — declarative condition expressions for workflow gates.
|
|
441
|
+
- **`/mixin`** — composition helpers for primitive object assembly.
|
|
442
|
+
- **`/sla-policy`** — SLA policy definitions paired with the existing `/sla` runtime.
|
|
443
|
+
|
|
444
|
+
### Added — payment event coverage
|
|
445
|
+
|
|
446
|
+
Expanded `PAYMENT_EVENT_TYPE` catalogue to cover auth/capture and dispute
|
|
447
|
+
lifecycles: `AUTHORIZED`, `CAPTURED`, `AUTH_VOIDED`, `DISPUTED`,
|
|
448
|
+
`DISPUTE_WON`, `DISPUTE_LOST`, `SETTLED`.
|
|
449
|
+
|
|
450
|
+
`PaymentRefundedPayload` and `PaymentReversedPayload` now carry
|
|
451
|
+
`originalAmount` (and `reversedAmount` / `isPartial` on reversed) so
|
|
452
|
+
consumers can detect partial vs full operations without a follow-up
|
|
453
|
+
Payment lookup.
|
|
454
|
+
|
|
455
|
+
## [0.7.0] - 2026-05-26
|
|
456
|
+
|
|
457
|
+
### Added — payment domain primitives
|
|
458
|
+
|
|
459
|
+
Three new subpaths shared across every payment-aware package
|
|
460
|
+
(`@classytic/invoice`, `@classytic/revenue`, `@classytic/order`, future
|
|
461
|
+
POS / AP / subscription packages). Each ships under a flat subpath so
|
|
462
|
+
consumers tree-shake what they don't import.
|
|
463
|
+
|
|
464
|
+
- **`/payment-method-kind`** — `PaymentMethodKind` universal vocabulary
|
|
465
|
+
(kind vs host-registered `methodCode`) so packages can speak `kind`
|
|
466
|
+
while hosts speak `code`.
|
|
467
|
+
- **`/payment-allocation-status`** — orthogonal allocation +
|
|
468
|
+
reconciliation status enums for payment legs (`unallocated`,
|
|
469
|
+
`partial`, `allocated`, `over_allocated`, plus reconciliation states).
|
|
470
|
+
- **`/payment-events`** — pure TS payload contracts for payment domain
|
|
471
|
+
events. Wire-only (no Zod, no runtime). Anchored on `Money` +
|
|
472
|
+
`PaymentMethodKind`; causation/correlation stay on `EventMeta`.
|
|
473
|
+
|
|
474
|
+
No breaking changes.
|
|
475
|
+
|
|
476
|
+
## [0.6.0] - 2026-05-17
|
|
477
|
+
|
|
478
|
+
### Added — five new primitives for CRM / workflow domains
|
|
479
|
+
|
|
480
|
+
Five new primitives covering gaps the existing surface didn't address.
|
|
481
|
+
Each ships under its own flat subpath import (no breaking changes).
|
|
482
|
+
|
|
483
|
+
- **`/phone`** — `PhoneNumber` value object + `parsePhone(input)` that
|
|
484
|
+
normalizes free-form input to E.164, captures the country calling code
|
|
485
|
+
as a discrete field, and reports failures via `Result<PhoneNumber,
|
|
486
|
+
PhoneError>`. Zero external deps; ITU-T E.164 prefixes inlined. Hosts
|
|
487
|
+
that want region-of-issue / line-type detection layer on top with
|
|
488
|
+
libphonenumber-js.
|
|
489
|
+
|
|
490
|
+
- **`/status-history`** — Generic, immutable status-change log:
|
|
491
|
+
`StatusChangeEntry<TStatus>`, `appendStatus()`, `timeInStatus()`,
|
|
492
|
+
`lastTransitionTo()`. Captures `durationInPriorMs` at write time so
|
|
493
|
+
funnel-velocity dashboards ("avg time in Qualification") work without
|
|
494
|
+
re-walking timestamps. Pairs naturally with `/state-machine`.
|
|
495
|
+
|
|
496
|
+
- **`/condition`** — Tiny JSON-serializable predicate DSL: `FieldCondition`
|
|
497
|
+
(`{ field, op, value }`) composed via `AllCondition` / `AnyCondition`
|
|
498
|
+
/ `NotCondition`, plus a total `evaluate(condition, target)` and a
|
|
499
|
+
`validateCondition()` for boot-time schema checks. Powers SLA scope
|
|
500
|
+
filters, drip "if opened/clicked" branches, automation triggers, and
|
|
501
|
+
permission row-scopes — anywhere a predicate needs to round-trip
|
|
502
|
+
through a database.
|
|
503
|
+
|
|
504
|
+
- **`/mixin`** — Additive type composition without subclassing:
|
|
505
|
+
`withMixin<T, K, M>()` / `getMixin()` / `hasMixin()` / `withoutMixin()`.
|
|
506
|
+
Lets a `Contact` carry per-domain payloads (`mixins.customer`,
|
|
507
|
+
`mixins.lead-fit`) on a typed `mixins` namespace without bloating the
|
|
508
|
+
base type. Plain object composition — no decorators, no global
|
|
509
|
+
hierarchy, no proxies.
|
|
510
|
+
|
|
511
|
+
- **`/sla-policy`** — Higher-order SLA layered on the existing `/sla`:
|
|
512
|
+
priority matrix (`urgent` / `high` / `normal` → durations), first-
|
|
513
|
+
response vs rolling-response semantics, and working-hours window
|
|
514
|
+
(weekdays + start/end minute + holidays). `defineSLAPolicy()` validates
|
|
515
|
+
at boot; `evaluateSLAStatus()` produces `{ kind, responseBy,
|
|
516
|
+
remainingMs, breached }` from a policy + per-entity inputs. The
|
|
517
|
+
existing simple `/sla` interface is unchanged — `sla-policy` derives
|
|
518
|
+
concrete `SLA` instances from a policy + priority and delegates to it.
|
|
519
|
+
|
|
520
|
+
### Changed — source-tree reorganization (no public-surface impact)
|
|
521
|
+
|
|
522
|
+
The 24 primitive source files moved from a flat `src/*.ts` layout into
|
|
523
|
+
six category folders so the package is self-documenting at the file
|
|
524
|
+
system level:
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
src/
|
|
528
|
+
money/ money, currency, split-allocation, bank-transaction, payment-gateway
|
|
529
|
+
identity/ person, address, phone
|
|
530
|
+
scheduling/ period, cadence, sla, sla-policy
|
|
531
|
+
workflow/ state-machine, status-history, approval, hold, condition
|
|
532
|
+
events/ events, outbox
|
|
533
|
+
composition/ mixin, reference, context, brand, result
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Public subpath imports are unchanged** — `@classytic/primitives/money`,
|
|
537
|
+
`@classytic/primitives/sla`, etc. still resolve. tsdown emits each entry
|
|
538
|
+
to `dist/<name>.mjs` regardless of source nesting. Existing consumers
|
|
539
|
+
upgrade with `npm install @classytic/primitives@^0.6.0` and a no-op
|
|
540
|
+
diff.
|
|
541
|
+
|
|
542
|
+
### Dependencies
|
|
543
|
+
|
|
544
|
+
Still zero runtime deps. Node 22+.
|
|
545
|
+
|
|
546
|
+
## [0.5.0] - 2026-05-05
|
|
547
|
+
|
|
548
|
+
### Added — `/bank-transaction` subpath
|
|
549
|
+
|
|
550
|
+
Canonical bank-feed shapes shared across `@classytic/fin-io` (parsers
|
|
551
|
+
produce these), `@classytic/revenue` (`transactionRepository.import()`
|
|
552
|
+
consumes them), and `@classytic/ledger` (`bankStatementMapper`
|
|
553
|
+
consumes them). Single source of truth for the cross-package
|
|
554
|
+
vocabulary; eliminates the structural mirrors fin-io and revenue
|
|
555
|
+
were carrying.
|
|
556
|
+
|
|
557
|
+
Exports: `BankTransaction`, `BankCounterparty`, `BankAccount`,
|
|
558
|
+
`BankStatement`, `BankStatementSource`, `BankImportReport`,
|
|
559
|
+
`BankImportRowError`. All anchored on `Money` from `/money` (integer
|
|
560
|
+
minor units, `number`).
|
|
561
|
+
|
|
562
|
+
### Added — `/payment-gateway` subpath
|
|
563
|
+
|
|
564
|
+
Canonical payment-gateway data shapes. **Lets any provider package
|
|
565
|
+
(Stripe, Razorpay, SSLCommerz, PayPal, bKash, Nagad, manual, …)
|
|
566
|
+
implement against primitives' types without depending on revenue's
|
|
567
|
+
heavyweight runtime.** Provider authors peer-dep on
|
|
568
|
+
`@classytic/primitives` only.
|
|
569
|
+
|
|
570
|
+
Exports: `CreateIntentParams`, `PaymentIntent`, `PaymentResult`,
|
|
571
|
+
`RefundResult`, `WebhookEvent`, `ProviderCapabilities`. All `amount`
|
|
572
|
+
fields are `Money` from `/money` (no flat `amount: number, currency:
|
|
573
|
+
string` pairs — currency travels inside Money).
|
|
574
|
+
|
|
575
|
+
The `PaymentProvider` abstract class — the *contract* every provider
|
|
576
|
+
must satisfy — stays in `@classytic/revenue`. Revenue is the data
|
|
577
|
+
engine that consumes providers; provider packages live elsewhere.
|
|
578
|
+
|
|
579
|
+
### Architectural intent
|
|
580
|
+
|
|
581
|
+
Revenue 3.0 + primitives 0.5 + fin-io 0.3 establish a clean
|
|
582
|
+
three-tier seam:
|
|
583
|
+
|
|
584
|
+
1. **primitives** — pure data shapes (no runtime, no deps). Owns
|
|
585
|
+
`Money`, `BankTransaction`, `PaymentIntent`, every cross-
|
|
586
|
+
package vocabulary type.
|
|
587
|
+
2. **revenue** — the engine. Owns the abstract contracts
|
|
588
|
+
(`PaymentProvider`, `BankFeedProvider`), state machines, repos.
|
|
589
|
+
Consumes primitives shapes.
|
|
590
|
+
3. **provider packages** (`revenue-stripe`, `revenue-razorpay`,
|
|
591
|
+
`@classytic/fin-io/adapters/revenue`, …) — implementations.
|
|
592
|
+
Peer-dep on primitives. Optional peer-dep on revenue for the
|
|
593
|
+
abstract base class.
|
|
594
|
+
|
|
595
|
+
This eliminates the cycle where every provider package transitively
|
|
596
|
+
pulled mongoose + mongokit just to know what a `PaymentIntent`
|
|
597
|
+
looked like. Tree-shaking, build times, and dep-graph honesty all
|
|
598
|
+
improve.
|
|
599
|
+
|
|
600
|
+
### Migration
|
|
601
|
+
|
|
602
|
+
Consumers MUST import from primitives subpaths. No re-exports from
|
|
603
|
+
revenue or fin-io (PACKAGE_RULES P2):
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
// Before
|
|
607
|
+
import type { PaymentIntent, CreateIntentParams } from '@classytic/revenue';
|
|
608
|
+
import type { CanonicalTransaction, Money } from '@classytic/fin-io';
|
|
609
|
+
|
|
610
|
+
// After
|
|
611
|
+
import type {
|
|
612
|
+
PaymentIntent,
|
|
613
|
+
CreateIntentParams,
|
|
614
|
+
} from '@classytic/primitives/payment-gateway';
|
|
615
|
+
import type { BankTransaction } from '@classytic/primitives/bank-transaction';
|
|
616
|
+
import type { Money } from '@classytic/primitives/money';
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Provider helper classes (`PaymentIntent`, `PaymentResult`,
|
|
620
|
+
`RefundResult`, `WebhookEvent`) are deleted from revenue — they were
|
|
621
|
+
trivial wrappers around plain objects. Provider implementations now
|
|
622
|
+
return plain object literals matching the interface.
|
|
623
|
+
|
|
624
|
+
`CreateIntentParams.amount` is now `Money` (`{ amount: number,
|
|
625
|
+
currency: string }`), not the flat `amount + currency` pair. Revenue's
|
|
626
|
+
repository wraps internally:
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
const intent = await provider.createIntent({
|
|
630
|
+
amount: { amount: params.amount, currency },
|
|
631
|
+
metadata: params.metadata,
|
|
632
|
+
...params.paymentData,
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## [0.3.1] - 2026-05-02
|
|
637
|
+
|
|
638
|
+
### Added — state-machine ↔ mongokit/sqlitekit integration
|
|
639
|
+
|
|
640
|
+
State-machine primitives now compose directly with the kit-level CAS surface shipped in mongokit 3.13 and the sqlitekit follow-up. Three additions:
|
|
641
|
+
|
|
642
|
+
- **`StateMachine.validTargets(from)`** — every status the aggregate can move TO from `from`. Useful for UI dropdowns ("which actions are legal right now?") and for fanning a single source state into a multi-source CAS via a repo's `claim({ from: machine.validTargets(current), to })`.
|
|
643
|
+
- **`StateMachine.validSources(to)`** — every status that can transition INTO `to` (reverse adjacency). Pair with a repo's multi-source `claim({ from: machine.validSources(to), to })` when the target is fixed but the caller doesn't yet know the current state — the canonical "cancel from any non-terminal" / "error from any in-flight" pattern. Pre-computed at definition time, O(1) lookup, frozen arrays safe to expose by reference.
|
|
644
|
+
- **`assertAndClaim(machine, repo, id, args)`** — pairs `machine.assertTransition` (sync domain check) with `repo.claim()` (atomic CAS) in one call. The canonical state-machine-backed CAS pattern. Skipping either layer leaves a hole: skip `assertTransition` and bad transitions reach storage; skip `claim` and concurrent writers race.
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
import { defineStateMachine, assertAndClaim } from '@classytic/primitives/state-machine';
|
|
648
|
+
|
|
649
|
+
const ORDER_MACHINE = defineStateMachine<OrderStatus>({
|
|
650
|
+
name: 'Order',
|
|
651
|
+
transitions: {
|
|
652
|
+
draft: ['approved', 'cancelled'],
|
|
653
|
+
approved: ['shipped', 'cancelled'],
|
|
654
|
+
shipped: [],
|
|
655
|
+
cancelled: [],
|
|
656
|
+
},
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// Single-source:
|
|
660
|
+
const updated = await assertAndClaim(ORDER_MACHINE, orderRepo, orderId, {
|
|
661
|
+
from: 'draft',
|
|
662
|
+
to: 'approved',
|
|
663
|
+
patch: { approvedAt: new Date() },
|
|
664
|
+
options: { organizationId: ctx.organizationId, session },
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// Multi-source via reverse adjacency — "cancel from any legal predecessor":
|
|
668
|
+
await assertAndClaim(ORDER_MACHINE, orderRepo, orderId, {
|
|
669
|
+
from: ORDER_MACHINE.validSources('cancelled'), // ['draft', 'approved']
|
|
670
|
+
to: 'cancelled',
|
|
671
|
+
patch: { cancelledAt: new Date() },
|
|
672
|
+
options: { organizationId: ctx.organizationId },
|
|
673
|
+
});
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
#### `ClaimableRepo<TDoc>` — structural dependency on the kit
|
|
677
|
+
|
|
678
|
+
`assertAndClaim` accepts any object matching `ClaimableRepo<TDoc>` — a structural type that mirrors the mongokit 3.13 `Repository.claim()` signature. Primitives doesn't take a hard peer dep on mongokit; consumers wire the kit they want. Sqlitekit's `claim()` matches the same shape (verified by mongokit/sqlitekit cross-conformance).
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
export interface ClaimableRepo<TDoc> {
|
|
682
|
+
claim(
|
|
683
|
+
id: string,
|
|
684
|
+
transition: {
|
|
685
|
+
field?: string;
|
|
686
|
+
from: unknown | readonly unknown[];
|
|
687
|
+
to: unknown;
|
|
688
|
+
where?: Record<string, unknown>;
|
|
689
|
+
},
|
|
690
|
+
patch?: Record<string, unknown>,
|
|
691
|
+
options?: Record<string, unknown>,
|
|
692
|
+
): Promise<TDoc | null>;
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
#### Why a 0.3.1 (not 0.4.0)
|
|
697
|
+
|
|
698
|
+
Purely additive — no removals, no signature changes. `defineStateMachine` callers from 0.3.0 see two new methods on the returned `StateMachine` (`validTargets`, `validSources`) and one new free function (`assertAndClaim`). Backwards-compatible.
|
|
699
|
+
|
|
700
|
+
## [0.3.0] - 2026-04-29
|
|
701
|
+
|
|
702
|
+
### Removed — `/errors` subpath (BREAKING)
|
|
703
|
+
|
|
704
|
+
- Deleted `@classytic/primitives/errors` (`ErrorContract`, `ErrorDetail`, `ErrorCode`, `ERROR_CODES`).
|
|
705
|
+
- Errors are infrastructure-shaped (HTTP-coupled, repository-coupled, RFC 7807-shaped). Moved to `@classytic/repo-core/errors` where they sit next to `pagination`, `tenant`, `cache`, `hooks`, `schema` — every other repository contract — and join the existing `HttpError` throwable contract there. Single canonical home for both the wire (`ErrorContract`) and throwable (`HttpError`) shapes.
|
|
706
|
+
|
|
707
|
+
#### Migration
|
|
708
|
+
|
|
709
|
+
```diff
|
|
710
|
+
- import type { ErrorContract, ErrorDetail, ErrorCode } from '@classytic/primitives/errors';
|
|
711
|
+
- import { ERROR_CODES } from '@classytic/primitives/errors';
|
|
712
|
+
+ import type { ErrorContract, ErrorDetail, ErrorCode } from '@classytic/repo-core/errors';
|
|
713
|
+
+ import { ERROR_CODES } from '@classytic/repo-core/errors';
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Field shapes and code values are unchanged — pure relocation.
|
|
717
|
+
|
|
718
|
+
### Removed — `/tenant` subpath (BREAKING)
|
|
719
|
+
|
|
720
|
+
- Deleted `@classytic/primitives/tenant` (`TenantConfig`, `TenantStrategy`, `TenantFieldType`, `DEFAULT_TENANT_CONFIG`, `resolveTenantConfig`, `ResolvedTenantConfig`).
|
|
721
|
+
- Tenant scope is **infrastructure-shaped** (describes how queries get scoped to a database / repository), not a domain primitive like Money or Address. Moved to `@classytic/repo-core/tenant` where it sits next to `context`, `filter`, `hooks`, `schema`, `cache` — every other repository contract.
|
|
722
|
+
- Mongokit and sqlitekit already peer-dep `@classytic/repo-core`, so consuming `TenantConfig` from there costs zero extra peer deps for those kits.
|
|
723
|
+
|
|
724
|
+
#### Migration
|
|
725
|
+
|
|
726
|
+
```diff
|
|
727
|
+
- import type { TenantConfig } from '@classytic/primitives/tenant';
|
|
728
|
+
- import { resolveTenantConfig, DEFAULT_TENANT_CONFIG } from '@classytic/primitives/tenant';
|
|
729
|
+
+ import type { TenantConfig } from '@classytic/repo-core/tenant';
|
|
730
|
+
+ import { resolveTenantConfig, DEFAULT_TENANT_CONFIG } from '@classytic/repo-core/tenant';
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Field shapes, runtime semantics, and defaults are unchanged. The custom-strategy escape hatch (`strategy: 'custom'` + `resolve: (ctx) => filterShape`) works identically.
|
|
734
|
+
|
|
735
|
+
### Removed — `/pagination` subpath (BREAKING)
|
|
736
|
+
|
|
737
|
+
- Deleted `@classytic/primitives/pagination` (`PageParams`, `KeysetParams`, `SortSpec`, `SortDirection`, `OffsetPage<T>`, `KeysetPage<T>`, `AggregatePage<T>`, `emptyOffsetPage`, `emptyKeysetPage`).
|
|
738
|
+
- Pagination shapes are owned by **`@classytic/repo-core/pagination`** — that package ships both the types AND the cursor encoding/skip-math/keyset-validation algorithms. Primitives' duplicate type-only declarations existed before the contract was canonised in repo-core; keeping them invited drift.
|
|
739
|
+
|
|
740
|
+
### Migration
|
|
741
|
+
|
|
742
|
+
```diff
|
|
743
|
+
- import type { OffsetPage, KeysetPage, AggregatePage, SortSpec } from '@classytic/primitives/pagination';
|
|
744
|
+
+ import type {
|
|
745
|
+
+ OffsetPaginationResult,
|
|
746
|
+
+ KeysetPaginationResult,
|
|
747
|
+
+ AggregatePaginationResult,
|
|
748
|
+
+ SortSpec,
|
|
749
|
+
+ } from '@classytic/repo-core/pagination';
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
Note: shape names also changed (`OffsetPage` → `OffsetPaginationResult`, etc.) to match repo-core's naming. Field names align with what mongokit, sqlitekit, and arc already produced — those packages always returned repo-core shapes; primitives' types were a parallel, never-quite-aligned vocabulary.
|
|
753
|
+
|
|
754
|
+
If you also need HTTP wire envelopes (`{ success: true } & Result`), repo-core 0.3.0 ships `OffsetPaginationResponse`, `KeysetPaginationResponse`, `AggregatePaginationResponse`, `BareListResponse`, `PaginatedResponse`, plus a `toCanonicalList()` runtime normalizer.
|
|
755
|
+
|
|
756
|
+
### Why now (and why this is the only breaking change in this release)
|
|
757
|
+
|
|
758
|
+
Two layers of pagination shapes drifted across the org: primitives declared one set, repo-core declared a different set, mongokit + sqlitekit + arc all consumed repo-core's. Removing primitives' set is purely subtractive — nobody internally imports from `@classytic/primitives/pagination`, and external consumers get a clear migration path to repo-core. No new ground to break later.
|
|
759
|
+
|
|
760
|
+
## [0.2.0]
|
|
761
|
+
|
|
762
|
+
### Added
|
|
763
|
+
|
|
764
|
+
- **`/state-machine` subpath** — `defineStateMachine<TStatus>({ name, transitions, errorFactory? })`
|
|
765
|
+
declarative state-transition primitive. Replaces hand-rolled
|
|
766
|
+
`if (status === 'X') throw` blocks across kernels:
|
|
767
|
+
|
|
768
|
+
```typescript
|
|
769
|
+
import { defineStateMachine } from '@classytic/primitives/state-machine';
|
|
770
|
+
|
|
771
|
+
const ORDER_MACHINE = defineStateMachine<OrderStatus>({
|
|
772
|
+
name: 'Order',
|
|
773
|
+
transitions: {
|
|
774
|
+
draft: ['approved', 'cancelled'],
|
|
775
|
+
approved: ['shipped', 'cancelled'],
|
|
776
|
+
shipped: [],
|
|
777
|
+
cancelled: [],
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
ORDER_MACHINE.assertTransition('order-1', 'draft', 'approved'); // OK
|
|
782
|
+
ORDER_MACHINE.assertTransition('order-1', 'shipped', 'draft'); // throws
|
|
783
|
+
ORDER_MACHINE.canTransition('draft', 'shipped'); // false
|
|
784
|
+
ORDER_MACHINE.isTerminal('shipped'); // true
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Generic over the status string-union — `Record<TStatus, readonly TStatus[]>`
|
|
788
|
+
on `transitions` enforces exhaustiveness at the call site. Kernels with their
|
|
789
|
+
own typed transition error class (e.g. flow's `InvalidTransitionError`) wire
|
|
790
|
+
it via `errorFactory` so the thrown type stays domain-consistent. Standalone
|
|
791
|
+
callers get the bundled `IllegalTransitionError` (carries `entityType` /
|
|
792
|
+
`entityId` / `from` / `to` + `code: 'illegal_transition'` + `status: 422`).
|
|
793
|
+
|
|
794
|
+
First consumer: `@classytic/flow` `PROCUREMENT_MACHINE` (procurement-order
|
|
795
|
+
approve / cancel transitions).
|
|
796
|
+
|
|
797
|
+
## [0.1.1]
|
|
798
|
+
|
|
799
|
+
### Added
|
|
800
|
+
|
|
801
|
+
- **`MinorUnits<C>` branded type on `/money`** — wire-format monetary amount
|
|
802
|
+
(integer minor units, branded with phantom currency) for HTTP/JSON fields
|
|
803
|
+
where a full `Money` struct is overkill. Erases at runtime; `MinorUnits<'BDT'>`
|
|
804
|
+
vs `MinorUnits<'USD'>` collide at compile time. Pair with structured `Money`
|
|
805
|
+
for in-process arithmetic. Constructor: `MinorUnits(amount, currency?)`.
|
|
806
|
+
|
|
807
|
+
### Changed
|
|
808
|
+
|
|
809
|
+
- **`/tenant` `contextKey` cascade default** — `resolveTenantConfig({ tenantField: 'branchId' })`
|
|
810
|
+
now defaults `contextKey` to `'branchId'` instead of `'organizationId'`.
|
|
811
|
+
When a host renames `tenantField`, their `OperationContext` carries the id
|
|
812
|
+
under the same key in the overwhelming majority of cases; mirroring the
|
|
813
|
+
rename is the least-surprise default. Explicit `contextKey` still overrides
|
|
814
|
+
(set when the doc field and context key genuinely diverge). Existing call
|
|
815
|
+
sites that supplied both `tenantField` and `contextKey` are unaffected.
|
|
816
|
+
|
|
817
|
+
## [0.1.0]
|
|
818
|
+
|
|
819
|
+
Initial release. Framework-agnostic TypeScript primitives shared across
|
|
820
|
+
Classytic packages. Zero runtime dependencies. Node 22+. ESM only.
|
|
821
|
+
260 tests (unit + integration) across 20 files.
|
|
822
|
+
|
|
823
|
+
All subpaths are importable independently; there is **no root barrel
|
|
824
|
+
re-export** — consumers must use subpath imports (`@classytic/primitives/money`,
|
|
825
|
+
`/events`, `/tenant`, …) to keep the Node ESM loader + TypeScript compiler
|
|
826
|
+
from walking the full module graph on any convenience import.
|
|
827
|
+
|
|
828
|
+
### Subpaths
|
|
829
|
+
|
|
830
|
+
- **`/money`** — `Money` type + integer-minor-unit arithmetic (`money`,
|
|
831
|
+
`addMoney`, `subtractMoney`, `multiplyMoney`, `sumMoney`, `negateMoney`,
|
|
832
|
+
`absMoney`, `compareMoney`, `equalsMoney`, `isZeroMoney`, `isPositiveMoney`,
|
|
833
|
+
`isNegativeMoney`, `isMoney`, `fromMajor`, `toMajor`); `CurrencyMismatchError`.
|
|
834
|
+
- **`/currency`** — `CurrencyCode` brand; `CURRENCIES` table; `MINOR_UNIT_FACTOR`,
|
|
835
|
+
`minorUnitFactor`, `isCurrencyCode`, `toCurrencyCode`.
|
|
836
|
+
- **`/address`** — `Address`, `ContactAddress`, `GeoPoint`, `GeoJsonPoint`;
|
|
837
|
+
`toGeoJsonPoint`, `fromGeoJsonPoint`.
|
|
838
|
+
- **`/period`** — `DateRange`, `Period`; `isDateRange`, `isWithin`,
|
|
839
|
+
`rangeDurationMs`.
|
|
840
|
+
- **`/pagination`** — `PageParams`, `OffsetPage<T>`, `KeysetPage<T>`,
|
|
841
|
+
`AggregatePage`, `KeysetParams`, `SortSpec`, `SortDirection`;
|
|
842
|
+
`emptyOffsetPage`, `emptyKeysetPage`.
|
|
843
|
+
- **`/reference`** — `ExternalRef`, `DocumentRef`, `ObjectIdLike`, `IdLike`;
|
|
844
|
+
`idToString`, `isExternalRef`.
|
|
845
|
+
- **`/context`** — `OperationContext` (the identity + tracing bag every
|
|
846
|
+
Classytic package accepts); `ActorRef`.
|
|
847
|
+
- **`/events`** — `EventMeta` (arc v2.9-aligned — 13 fields including
|
|
848
|
+
`schemaVersion`, `causationId`, `partitionKey`, `source`, `idempotencyKey`,
|
|
849
|
+
`aggregate`), `DomainEvent<T>`, `EventTransport`, `EventHandler`,
|
|
850
|
+
`EventLogger`, `DeadLetteredEvent<T>`, `PublishManyResult`; `createEvent`,
|
|
851
|
+
`createChildEvent` (auto-chains `causationId`, inherits `correlationId` /
|
|
852
|
+
`userId` / `organizationId` / `source` / `idempotencyKey` — NOT
|
|
853
|
+
`aggregate`), `matchEventPattern`.
|
|
854
|
+
- **`/outbox`** — transactional outbox **contract** mirrored from arc v2.9:
|
|
855
|
+
`OutboxStore`, `OutboxWriteOptions`, `OutboxClaimOptions`,
|
|
856
|
+
`OutboxAcknowledgeOptions`, `OutboxFailOptions`, `OutboxErrorInfo`,
|
|
857
|
+
`OutboxFailureContext`, `OutboxFailureDecision`, `OutboxFailurePolicy`;
|
|
858
|
+
`OutboxOwnershipError`, `InvalidOutboxEventError`. **Contract only** —
|
|
859
|
+
runtime (`EventOutbox`, `MemoryOutboxStore`, `MongoOutboxStore`,
|
|
860
|
+
`exponentialBackoff`) ships in `@classytic/arc/events` and
|
|
861
|
+
`@classytic/arc/events/mongo`. Domain packages implement `OutboxStore`
|
|
862
|
+
without peer-depping arc.
|
|
863
|
+
- **`/tenant`** — `TenantConfig` (with `strategy: 'field' | 'none' | 'custom'`
|
|
864
|
+
and optional `resolve(ctx)` for custom strategy), `TenantFieldType`,
|
|
865
|
+
`TenantStrategy`, `ResolvedTenantConfig`; `DEFAULT_TENANT_CONFIG`,
|
|
866
|
+
`resolveTenantConfig`. Field names match `@classytic/mongokit`'s
|
|
867
|
+
`MultiTenantOptions` so a resolved config threads into
|
|
868
|
+
`multiTenantPlugin({ ... })` without translation.
|
|
869
|
+
- **`/result`** — `Result<T, E>`; `ok`, `err`, `isOk`, `isErr`, `unwrap`,
|
|
870
|
+
`mapResult`, `mapError`.
|
|
871
|
+
- **`/brand`** — `Brand<T, B>`, `Prettify`, `DeepPartial`, `DeepReadonly`,
|
|
872
|
+
`RequireKeys`, `OptionalKeys`, `NonEmptyArray`.
|
|
873
|
+
- **`/errors`** — `ErrorContract`, `ErrorDetail`, `ErrorCode`, `ERROR_CODES`.
|
|
874
|
+
- **`/split-allocation`** — `allocate(total, subjects, method)` returns a
|
|
875
|
+
`SplitResult` whose parts sum exactly to `total` via the largest-remainder
|
|
876
|
+
method. Methods: `equal`, `by-qty`, `by-weight`, `by-volume`, `by-value`,
|
|
877
|
+
`by-percent`. Deterministic — identical inputs produce identical outputs;
|
|
878
|
+
ties broken by input order. Handles negative totals by sign-flipping.
|
|
879
|
+
Exports: `allocate`, `isBalanced`, `SplitAllocationError`, types
|
|
880
|
+
`SplitMethod`, `SplitSubject`, `SplitPart`, `SplitResult`,
|
|
881
|
+
`SplitAllocationErrorCode`.
|
|
882
|
+
- **`/approval`** — `ApprovalChain` value object with pure FSM helpers.
|
|
883
|
+
Supports sequential and parallel chains, per-step quorum
|
|
884
|
+
(`requiredApprovals`), threshold-gated skips. Any step-level rejection
|
|
885
|
+
rejects the chain; all steps `approved`|`skipped` approves it. Exports:
|
|
886
|
+
`createChain`, `applyDecision`, `skipStep`, `nextPendingStep`,
|
|
887
|
+
`pendingSteps`, `isApproved`, `isRejected`, `isPending`, `decisionCount`,
|
|
888
|
+
`ApprovalError`, types `ApprovalChain`, `ApprovalStep`, `ApprovalDecision`,
|
|
889
|
+
`Approver`, `ApprovalStatus`, `ChainOrder`, `ApprovalThreshold`,
|
|
890
|
+
`CreateChainInput`, `CreateStepInput`, `DecisionInput`, `ApprovalErrorCode`.
|
|
891
|
+
- **`/cadence`** — recurrence spec with pure UTC date arithmetic. Kinds:
|
|
892
|
+
`daily`, `weekly`, `monthly`, `yearly`, `cron` (opaque — host parses).
|
|
893
|
+
`dayOfMonth: 31` snaps to the last day of shorter months; Feb 29 snaps
|
|
894
|
+
to Feb 28 in non-leap years. Exports: `nextOccurrence`,
|
|
895
|
+
`occurrencesBetween`, `validateCadence`, `CadenceError`, types `Cadence`,
|
|
896
|
+
`DailyCadence`, `WeeklyCadence`, `MonthlyCadence`, `YearlyCadence`,
|
|
897
|
+
`CronCadence`, `CadenceKind`, `IsoWeekday`, `CadenceErrorCode`.
|
|
898
|
+
- **`/hold`** — `HoldReason` + collection helpers. Host embeds
|
|
899
|
+
`HoldReason[]` on documents. Exports: `addHold`, `resolveHold`,
|
|
900
|
+
`activeHolds`, `resolvedHolds`, `isOnHold`, `hasActiveHoldOfCode`,
|
|
901
|
+
`HoldError`, types `HoldReason`, `HoldActor`, `HoldActorKind`,
|
|
902
|
+
`AddHoldInput`, `ResolveHoldInput`, `HoldErrorCode`.
|
|
903
|
+
- **`/sla`** — target duration + breach-detection arithmetic. Policy is
|
|
904
|
+
carried but not executed — host reads `breachPolicy` (`warn` | `escalate`
|
|
905
|
+
| `block`) and acts. Exports: `breachedAt`, `remainingMs`, `elapsedMs`,
|
|
906
|
+
`isBreached`, `consumedFraction`, `validateSLA`, `SLAError`, types
|
|
907
|
+
`SLA`, `BreachPolicy`, `SLAErrorCode`.
|
|
908
|
+
- **`/person`** — person/contact shapes shared across order, crm, hr.
|
|
909
|
+
`PersonName`, `ContactInfo`, `EmergencyContact`, `Gender`; formatters
|
|
910
|
+
`formatFullName`, `formatDisplayName`.
|
|
911
|
+
|
|
912
|
+
### Design principles
|
|
913
|
+
|
|
914
|
+
- Pure functions on plain data. No I/O, no Mongo, no events.
|
|
915
|
+
- Immutable return values. Helpers return new collections; originals are never mutated.
|
|
916
|
+
- Deterministic. Same inputs → same outputs. Clock sources injected
|
|
917
|
+
explicitly so tests don't flake.
|
|
918
|
+
- Errors are typed. Every module ships an `<Name>Error` class with a
|
|
919
|
+
discriminated `code: <Name>ErrorCode` string union.
|
|
920
|
+
- Subpath-imports only — no root barrel.
|
|
921
|
+
|
|
922
|
+
### Ownership direction
|
|
923
|
+
|
|
924
|
+
Arc is the source of truth for event types. Primitives **mirrors** arc
|
|
925
|
+
(`@classytic/arc/events/EventTransport.ts`, `@classytic/arc/events/outbox.ts`)
|
|
926
|
+
verbatim for `EventMeta`, `DomainEvent`, `EventTransport`,
|
|
927
|
+
`DeadLetteredEvent`, `EventLogger`, `PublishManyResult`, and the full
|
|
928
|
+
`OutboxStore` contract surface. Helpers (`createEvent`, `createChildEvent`)
|
|
929
|
+
are allowed to be more defensive but match arc's signatures.
|
|
930
|
+
|
|
931
|
+
Packages that peer-dep `@classytic/primitives` can write
|
|
932
|
+
`class FooOutboxRepository implements OutboxStore` without pulling arc into
|
|
933
|
+
their dependency graph; hosts import `EventOutbox` + `MongoOutboxStore` from
|
|
934
|
+
arc and wire them against package-provided stores.
|