@classytic/loyalty 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +321 -231
- package/LICENSE +75 -21
- package/README.md +2 -2
- package/dist/{config-Dtun1duw.d.mts → config-CtkGj3xZ.d.mts} +41 -22
- package/dist/{constants-DkQYkOK1.mjs → constants-DOPBh6kt.mjs} +1 -3
- package/dist/domain/enums/index.d.mts +1 -1
- package/dist/domain/enums/index.mjs +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +2 -2
- package/dist/{events-DN5_p6T0.mjs → events-BLjg3uLA.mjs} +22 -31
- package/dist/{index-Dngaj21_.d.mts → index-CR2k7jQ8.d.mts} +81 -14
- package/dist/{index-YSsi4e_G.d.mts → index-Cp_sXNpG.d.mts} +35 -4
- package/dist/index-D6GvuvmF.d.mts +692 -0
- package/dist/{index-D_5Ojc2c.d.mts → index-YUFamhDk.d.mts} +7 -17
- package/dist/index.d.mts +60 -29
- package/dist/index.mjs +99 -52
- package/dist/models/index.d.mts +3 -1273
- package/dist/models/index.mjs +2 -2
- package/dist/{models-Ddh62lgO.mjs → models-weWqS5Bc.mjs} +152 -70
- package/dist/notifications/index.d.mts +1 -2
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-CjWfUlrA.mjs → repositories-B676z4bE.mjs} +181 -66
- package/dist/types/index.d.mts +3 -3
- package/package.json +119 -116
- /package/dist/{index-DMK8a98J.d.mts → index-D_Oe3aDk.d.mts} +0 -0
- /package/dist/{loyalty-events-C38JA3cU.mjs → loyalty-events-DXirolM_.mjs} +0 -0
- /package/dist/{notification.port-CJ9nXj0k.d.mts → notification.port-wY4IKimz.d.mts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,231 +1,321 @@
|
|
|
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
|
-
###
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
`minOrderAmount`
|
|
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
|
-
the
|
|
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
|
-
|
|
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.6.0] — 2026-08-17
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `SessionNotInTransactionError` — thrown when a host passes `ctx.session` that has no active transaction; a bare session provides no atomicity and would leave the points mutation standing if the outbox save fails
|
|
10
|
+
- `ResolvedTenant` type alias (deprecated shim for `ResolvedTenantConfig` from `@classytic/repo-core/tenant`)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `TenantConfig` widened to `RepoTenantConfig | boolean` (was `false | PrimitivesTenantConfig`); hosts rename `field` → `tenantField` and `type` → `fieldType`
|
|
14
|
+
- `in-process-bus` consolidated onto primitives `InProcessEventBus`; event catalog, dispatch, and repository types aligned
|
|
15
|
+
- Schemas and repositories updated: earning-rule, redemption, tier-definition, point-transaction, referral
|
|
16
|
+
- `src/index.ts` exports reorganised for tree-shaking
|
|
17
|
+
|
|
18
|
+
## 1.0.0 - 2026-07-29
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **License:** relicensed from MIT to the **Classytic Source-Available License** (Community & Commercial). Evaluation/development use remains free; production use now requires a commercial license from Classytic LLC. See `LICENSE`.
|
|
22
|
+
- Major bump marks the license change; versions published before 1.0.0 remain under their original MIT terms.
|
|
23
|
+
|
|
24
|
+
## 0.7.1 - 2026-07-22 (unpublished)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Internal: `InProcess*Bus` is now a thin subclass of `@classytic/primitives/event-infra`'s shared `InProcessEventBus` (dedup / error-isolation / close semantics unchanged); `MemoryOutboxStore` re-exported from the shared runtime where applicable. No public API, event-name, or wire-envelope change. Requires `@classytic/primitives >=0.13.0`.
|
|
28
|
+
|
|
29
|
+
## [0.7.0] — unpublished (2026-07-22)
|
|
30
|
+
|
|
31
|
+
### Added — engine lifecycle: `destroy()`
|
|
32
|
+
|
|
33
|
+
- **`LoyaltyEngine.destroy()`** — closes the self-created
|
|
34
|
+
`InProcessLoyaltyBus` (skipped when the host injected `eventTransport`).
|
|
35
|
+
- `destroy()` follows the fleet ownsBus convention (manufacturing/purchase
|
|
36
|
+
pattern): only what the engine itself created is closed; a host-injected
|
|
37
|
+
transport is the host's to close. Idempotent — a second call is a no-op.
|
|
38
|
+
Mongoose models/connection are untouched (host-owned).
|
|
39
|
+
- Lifecycle coverage in `tests/integration/engine-lifecycle.test.ts`.
|
|
40
|
+
|
|
41
|
+
## [0.6.0] — 2026-07-20
|
|
42
|
+
|
|
43
|
+
### Changed — money contract: integer MINOR units + explicit rates, no major-unit ambiguity (BREAKING)
|
|
44
|
+
|
|
45
|
+
Every monetary number in loyalty was an ambiguous major-currency float
|
|
46
|
+
(unenforced), and both earning rates carried an implicit "per 1 MAJOR unit"
|
|
47
|
+
basis. Aligned with `@classytic/flow@0.6.0`'s TOTAL-vs-RATE standardization
|
|
48
|
+
(see flow `wiki/conventions.md` §15). Loyalty stays ambient-single-currency —
|
|
49
|
+
NO currency field on any doc or API; the host owns currency.
|
|
50
|
+
|
|
51
|
+
- **All money AMOUNTS are now integers in the host's minor unit** (paisa for
|
|
52
|
+
BDT), enforced by `mongooseMinorUnits()` from the new
|
|
53
|
+
**`@classytic/validation` dependency (`>=0.2.0`)**:
|
|
54
|
+
- `EarningConditions.minOrderAmount`
|
|
55
|
+
- `LoyaltyConfig.redemption.minOrderAmount` (`DEFAULT_MIN_ORDER_AMOUNT`
|
|
56
|
+
stays `0` — zero is unit-invariant)
|
|
57
|
+
- `Redemption.discountAmount`, `Redemption.initialBalance`,
|
|
58
|
+
`Redemption.remainingBalance`
|
|
59
|
+
- `TierQualification.minLifetimeSpend` (declared-only field — tier checks
|
|
60
|
+
still use lifetime POINTS; re-documented + validated, no math change)
|
|
61
|
+
- API inputs: `evaluateOrder({ orderTotal, items[].amount })` and
|
|
62
|
+
`validate`/`reserve({ orderTotal })`. `validateOrderTotal` now requires a
|
|
63
|
+
non-negative **safe integer** ("orderTotal must be a non-negative safe
|
|
64
|
+
integer (minor units)") — floats and majors fail loud.
|
|
65
|
+
- **`EarningReward.amountPerPoint`** is now MINOR UNITS PER POINT (integer
|
|
66
|
+
≥ 1, bespoke rate validator). "1 point per ৳100" = `amountPerPoint: 10000`.
|
|
67
|
+
- **`EarningReward.pointsPerAmount` requires a new sibling `rateBasis`**
|
|
68
|
+
(integer minor units ≥ 1): `points = pointsPerAmount × (orderTotal /
|
|
69
|
+
rateBasis)`, rounded ONCE per the rule's `roundingMode`. The old implicit
|
|
70
|
+
"points per 1 MAJOR unit" basis was the bug being killed. Pairing enforced
|
|
71
|
+
at the schema level (pre-validate) AND by a runtime `ValidationError` guard
|
|
72
|
+
in the calculators (legacy/hand-written docs fail loud).
|
|
73
|
+
- **`conversionRate` (points per 1 MAJOR unit) is REPLACED by
|
|
74
|
+
`pointValueMinor`** (integer minor units ONE point redeems for, ≥ 1) on
|
|
75
|
+
`LoyaltyConfig.program`, `ResolvedConfig`, and the `Redemption` doc snapshot.
|
|
76
|
+
`conversionRate: 10` (10 pts per ৳1) ≡ `pointValueMinor: 10` (1 pt = 10
|
|
77
|
+
paisa). `DEFAULT_CONVERSION_RATE` → `DEFAULT_POINT_VALUE_MINOR` (still `10`
|
|
78
|
+
— same economic default). Boot validation: "pointValueMinor must be a
|
|
79
|
+
positive integer (minor units per point)".
|
|
80
|
+
- **Redemption math is now EXACT integer arithmetic** (no more floor-division
|
|
81
|
+
remainder): `requestedDiscountMinor = pointsToRedeem × pointValueMinor`;
|
|
82
|
+
percent cap `maxDiscountMinor = floor(orderTotal × maxRedeemPercent / 100)`
|
|
83
|
+
unchanged in shape; when capped, `actualPoints = floor(cappedDiscount /
|
|
84
|
+
pointValueMinor)` and the discount is **recomputed as `actualPoints ×
|
|
85
|
+
pointValueMinor`** — the granted discount ALWAYS equals exactly the points
|
|
86
|
+
charged (no orphaned remainder paisa; documented invariant).
|
|
87
|
+
- **Event contract**: `RedemptionPayload.discountAmount` is integer minor
|
|
88
|
+
units; the Zod catalog schema now asserts `.int().nonnegative()` (Zod event
|
|
89
|
+
schemas are not tsc-checked against the TS interfaces — flow's lesson).
|
|
90
|
+
|
|
91
|
+
**Migration**: clean cutover, no dual-read shim. Re-express persisted majors
|
|
92
|
+
as minors (× minor-unit factor), rename `conversionRate` → `pointValueMinor`
|
|
93
|
+
(config + redemption docs), and add `rateBasis` to every rule that sets
|
|
94
|
+
`pointsPerAmount` (old implicit basis "per ৳1" = `rateBasis: 100`).
|
|
95
|
+
|
|
96
|
+
## [0.5.0] — unpublished (2026-07-15)
|
|
97
|
+
|
|
98
|
+
### Changed — mongokit 3.22 adopted (peer floors: mongokit >=3.22.0, repo-core >=0.13.0, primitives >=0.11.0)
|
|
99
|
+
|
|
100
|
+
- **Points ledger fence enforced** — `PointTransaction` rows are the
|
|
101
|
+
balance audit trail; `appendOnlyPlugin` now refuses every mutation
|
|
102
|
+
path (405 `APPEND_ONLY_VIOLATION`). Previously nothing enforced it —
|
|
103
|
+
a stray `update()` could rewrite an earn/burn row and silently
|
|
104
|
+
desync the audit trail from member balances. The expiry sweeper's
|
|
105
|
+
`expiredAt` stamp is the one engine-owned lifecycle write and passes
|
|
106
|
+
the audited `bypassAppendOnly` escape.
|
|
107
|
+
- No FSM boilerplate existed (no assert+claim sites, no
|
|
108
|
+
read-modify-write history) — loyalty was already clean there; this
|
|
109
|
+
release is the fence + kit refresh (clean registry install, no
|
|
110
|
+
legacy flags).
|
|
111
|
+
|
|
112
|
+
## [0.4.0] — addendum (2026-07-05)
|
|
113
|
+
|
|
114
|
+
### Changed (behavioral)
|
|
115
|
+
|
|
116
|
+
- **§P8.1 relay-only session guard** — `UnmanagedSessionError` now throws
|
|
117
|
+
only when a host session arrives with NEITHER a `[PENDING_EVENTS]`
|
|
118
|
+
queue NOR an outbox. Session + outbox + no queue is legal
|
|
119
|
+
durable-relay-only mode: the row commits with the host transaction, the
|
|
120
|
+
relay delivers post-commit, the immediate transport publish is skipped.
|
|
121
|
+
(Previously the guard was stricter than spec and threw even with an
|
|
122
|
+
outbox wired.) Aligns loyalty with invoice / catalog / order /
|
|
123
|
+
purchase / transfer / promo / payrun.
|
|
124
|
+
- **Stack floors raised** — peers `@classytic/mongokit >=3.18.0`,
|
|
125
|
+
`@classytic/repo-core >=0.7.0`; full suite verified against those
|
|
126
|
+
versions.
|
|
127
|
+
|
|
128
|
+
## [0.4.0] — 2026-07-03
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **`evaluateAction` is now idempotent per recurrence key.** The order path
|
|
133
|
+
dedupes with `${orderId}:${ruleId}`, but the action path (signup /
|
|
134
|
+
referral / birthday / review) never set an `idempotencyKey` — a retried
|
|
135
|
+
sweep or double-fired event double-awarded points. When `referenceId` is
|
|
136
|
+
supplied, each rule award now carries `${referenceId}:${ruleId}`; callers
|
|
137
|
+
encode the recurrence period in `referenceId` (e.g.
|
|
138
|
+
`birthday:2026:<memberId>`) and the unique partial index on
|
|
139
|
+
`idempotencyKey` becomes the authoritative once-only guard.
|
|
140
|
+
- **Action rules honor `conditions` now.** `evaluateAction` skipped
|
|
141
|
+
`matchesConditions` entirely, silently ignoring admin-authored
|
|
142
|
+
`tiers` / `dateRange` / `dayOfWeek` on action-type rules ("only gold
|
|
143
|
+
members get birthday points" did nothing). Conditions now apply;
|
|
144
|
+
`minOrderAmount` is skipped for actions (no order to measure).
|
|
145
|
+
- **`allowNonTransactional` now actually works at runtime.** The flag
|
|
146
|
+
silenced the boot capability gate but `MongoUnitOfWork.withTransaction`
|
|
147
|
+
still opened a real transaction unconditionally — on standalone Mongo
|
|
148
|
+
every `earnPoints`/`redeem` unit failed with "does not support retryable
|
|
149
|
+
writes" despite the documented escape hatch. The unit of work now
|
|
150
|
+
delegates to mongokit's `withTransaction` with `allowFallback` so the
|
|
151
|
+
transactions-unsupported error re-runs the unit on a plain session
|
|
152
|
+
(partial-write risk accepted, exactly what the flag opts into).
|
|
153
|
+
- Day arithmetic (tier-downgrade grace expiry, referral rate-limit window)
|
|
154
|
+
now uses `addDays` from `@classytic/primitives/calendar` — exact 24h × n
|
|
155
|
+
durations instead of local `setDate`, which shifted with the deploy
|
|
156
|
+
machine's `TZ` env. Primitives peer bumped to `>=0.9.0`.
|
|
157
|
+
|
|
158
|
+
## [0.3.0] — 2026-06-13
|
|
159
|
+
|
|
160
|
+
Stack upgrade to mongokit 3.16 / repo-core 0.6 / primitives 0.7.2 plus
|
|
161
|
+
verified correctness + PACKAGE_RULES compliance fixes. No new features.
|
|
162
|
+
|
|
163
|
+
### Fixed
|
|
164
|
+
|
|
165
|
+
- **Stack actually resolves the new floor now (CRITICAL).** The committed
|
|
166
|
+
lockfile silently served `@classytic/mongokit@3.13.0` +
|
|
167
|
+
`@classytic/repo-core@0.4.0` despite the `^3.13.3` / `^0.4.2` pins, so the
|
|
168
|
+
whole test suite ran against the OLD stack. Clean reinstall + peer/dev bumps
|
|
169
|
+
pin the resolved tree to mongokit **3.16.0**, repo-core **0.6.0**, primitives
|
|
170
|
+
**0.7.2** (verified with `npm ls`).
|
|
171
|
+
- **§P8 outbox is now session-bound on the transactional verbs.** `earnPoints`,
|
|
172
|
+
`adjustPoints`, `reserve`, `confirm`, `release`, and `reverseConfirmed`
|
|
173
|
+
previously dispatched the event AFTER `withTransaction` committed, so
|
|
174
|
+
`outbox.save` ran OUTSIDE the unit of work — the business doc could commit
|
|
175
|
+
while the durable event row was lost (or vice-versa). Dispatch now runs
|
|
176
|
+
INSIDE the transaction (outbox row commits atomically with the write) and the
|
|
177
|
+
transport publish is deferred to a post-commit flush (`runLoyaltyUnit` +
|
|
178
|
+
`[PENDING_EVENTS]`), so in-process subscribers never see a state that may roll
|
|
179
|
+
back. A new integration test asserts the save is session-bound and that a
|
|
180
|
+
failing `outbox.save` rolls the `$inc` balance write back.
|
|
181
|
+
- **Redemption slot no longer permanently blocked after release.** The
|
|
182
|
+
`{ ownerType, ownerId }` unique index spanned ALL statuses, so a released /
|
|
183
|
+
reversed reservation E11000-blocked any retry on the same order (payment
|
|
184
|
+
retry, cart re-attempt) forever. It is now a **partial** unique index scoped
|
|
185
|
+
to active statuses (`reserved`, `confirmed`) — terminal states free the slot
|
|
186
|
+
(PACKAGE_RULES rule 32/38/39). Re-reserve-after-release and second-active-
|
|
187
|
+
reserve-blocked tests added.
|
|
188
|
+
|
|
189
|
+
### Added
|
|
190
|
+
|
|
191
|
+
- **`UnmanagedSessionError` guard (§P8).** When a host passes `ctx.session`
|
|
192
|
+
(it owns the outer transaction) without attaching a `[PENDING_EVENTS]` queue,
|
|
193
|
+
dispatch throws BEFORE any side effect instead of publishing / saving outside
|
|
194
|
+
the host's unit of work (which would leak ghost events on rollback).
|
|
195
|
+
`PENDING_EVENTS` and `flushPendingLoyaltyEvents` are exported for hosts that
|
|
196
|
+
manage their own transaction.
|
|
197
|
+
- **Capability boot gate.** `createLoyaltyEngine()` asserts the backend declares
|
|
198
|
+
the `transactions` capability (repo-core 0.6 / mongokit 3.16+) at engine
|
|
199
|
+
creation — fail loud at boot rather than with a cryptic driver error on the
|
|
200
|
+
first transactional call. `allowNonTransactional: true` opts out for local-dev
|
|
201
|
+
on standalone Mongo. New `LoyaltyCapabilityError`.
|
|
202
|
+
- **Cooperative cancellation in cron loops.** `processExpirations`, `awardBatch`,
|
|
203
|
+
`cleanupExpired` (redemption + referral), `evaluateAll`, and
|
|
204
|
+
`sweepExpiredOverrides` check `ctx.signal` via `throwIfAborted` between items —
|
|
205
|
+
abortable batches; each per-item write is its own committed unit of work, so
|
|
206
|
+
aborting never rolls back already-processed rows. `LoyaltyContext` gained
|
|
207
|
+
`signal?: AbortSignal`.
|
|
208
|
+
|
|
209
|
+
### Changed
|
|
210
|
+
|
|
211
|
+
- **Domain errors implement `HttpError`** from `@classytic/repo-core/errors`
|
|
212
|
+
(was a bespoke `httpStatus` shape). Clean break: `httpStatus` → `status`,
|
|
213
|
+
and codes are now hierarchical + lowercase (`loyalty.<entity>.<problem>`,
|
|
214
|
+
e.g. `loyalty.member.not_found`, `loyalty.points.insufficient`) so arc
|
|
215
|
+
serializes them to `ErrorContract` correctly. No deprecation alias.
|
|
216
|
+
- **Peer-dep ranges raised** — `@classytic/mongokit >=3.16.0`,
|
|
217
|
+
`@classytic/repo-core >=0.6.0` (`>=` style). devDeps bumped to match and
|
|
218
|
+
primitives devDep pinned to `^0.7.2`.
|
|
219
|
+
- **`exactOptionalPropertyTypes: true`** enabled in tsconfig (mandatory per
|
|
220
|
+
PACKAGE_RULES Build Hygiene / P10); fixed the three pre-existing optional-vs-
|
|
221
|
+
undefined violations it surfaced.
|
|
222
|
+
- **tsconfig no longer emits sourcemaps or declaration maps**
|
|
223
|
+
(`sourceMap: false`, `declarationMap: false`) — both leaked source. tsdown
|
|
224
|
+
was already correct; tsconfig now matches.
|
|
225
|
+
|
|
226
|
+
## [0.2.3] — 2026-05-08
|
|
227
|
+
|
|
228
|
+
### Fixed
|
|
229
|
+
|
|
230
|
+
- **§P8 dispatch — `outbox.save` failures now propagate.** [src/events/dispatch.ts](src/events/dispatch.ts)
|
|
231
|
+
previously caught `outbox.save` errors and swallowed them silently. Per
|
|
232
|
+
PACKAGE_RULES §P8 the outbox row is the durability anchor for the
|
|
233
|
+
transactional-outbox pattern: if the save fails, the caller's transaction
|
|
234
|
+
MUST roll back so the business doc and the event row land atomically.
|
|
235
|
+
Swallowing the failure meant a parent doc could commit while the event
|
|
236
|
+
vanished, with no relay row to recover from. Fix re-throws after logging.
|
|
237
|
+
`events.publish` failures continue to be swallowed (correctly, per §P8 —
|
|
238
|
+
the host's outbox relay re-publishes from the durable row), but now log
|
|
239
|
+
before swallowing so failures are observable.
|
|
240
|
+
- **`DispatchDeps.logger` field added.** Optional. Both arms of the
|
|
241
|
+
dispatch helper log through it when set, surfacing failures in the host's
|
|
242
|
+
observability stack without coupling the package to a specific logger.
|
|
243
|
+
|
|
244
|
+
### Changed
|
|
245
|
+
|
|
246
|
+
- **Peer-dep ranges tightened** — `@classytic/mongokit` `>=3.13.3`
|
|
247
|
+
(was `>=3.13.0`) and `@classytic/repo-core` `>=0.4.2` (was `>=0.4.0`).
|
|
248
|
+
3.13.3 preserves `errorLabels` on `MongoServerError` so concurrent
|
|
249
|
+
transactional writes (the `claim()`-based redemption + tier-eval paths)
|
|
250
|
+
retry correctly under contention; 0.4.2 adds the typed `FindAllOptions`
|
|
251
|
+
shape with `limit?` for callers that need a bounded non-paginated read.
|
|
252
|
+
|
|
253
|
+
### Internal
|
|
254
|
+
|
|
255
|
+
- Replaced 4 `arr.find()!` non-null assertions in [src/index.ts](src/index.ts)
|
|
256
|
+
+ scenario tests with explicit narrowing — TS now proves what the runtime
|
|
257
|
+
shape already guaranteed.
|
|
258
|
+
|
|
259
|
+
## [0.2.0]
|
|
260
|
+
|
|
261
|
+
Structural rewrite on top of the 0.1.0 shape. The public API is now a
|
|
262
|
+
smaller, sharper surface organized around seven subpath exports
|
|
263
|
+
(`/domain/enums`, `/models`, `/repositories`, `/events`, `/types`,
|
|
264
|
+
`/notifications`, plus the root engine factory). Legacy barrel + service
|
|
265
|
+
layers were removed; hosts wire business flows directly against the
|
|
266
|
+
repositories and the engine factory.
|
|
267
|
+
|
|
268
|
+
### Changed
|
|
269
|
+
|
|
270
|
+
- **Domain restructure.** Dropped the `src/domain/ports/` + `src/services/`
|
|
271
|
+
layer in favor of thin repositories that expose exactly the operations
|
|
272
|
+
hosts need — `earn`, `redeem`, `adjust`, `expire`, referral credit,
|
|
273
|
+
tier recomputation. No more `earning.service` / `redemption.service` /
|
|
274
|
+
`tier.service` / `referral.service` / `ledger.service` / `member.service`
|
|
275
|
+
/ `member-guard` indirections; the equivalent logic lives on the
|
|
276
|
+
repositories where it's naturally atomic.
|
|
277
|
+
- **Events subsystem simplified.** Removed `events/event-bus.ts` +
|
|
278
|
+
`events/event-types.ts` (both are internal indirection that the engine
|
|
279
|
+
didn't need). Event payloads are now colocated with the repository
|
|
280
|
+
that emits them.
|
|
281
|
+
- **Repositories updated to mongokit 3.11 + repo-core 0.2 conventions.**
|
|
282
|
+
Every repo extends `Repository<T>` directly, uses the tenant plugin via
|
|
283
|
+
`injectTenant`, and atomic state changes go through CAS guards.
|
|
284
|
+
- **Peer deps** unchanged in spirit, tightened in range:
|
|
285
|
+
- `@classytic/mongokit >= 3.11`
|
|
286
|
+
- `@classytic/primitives >= 0.1`
|
|
287
|
+
- `@classytic/repo-core >= 0.2`
|
|
288
|
+
- `mongoose >= 9.4`, `zod >= 4`
|
|
289
|
+
- **DevDeps:** `@classytic/primitives` devDep moved from `file:` to `>=0.1.0`
|
|
290
|
+
(now that primitives ships on npm); all `@classytic/*` dev ranges
|
|
291
|
+
standardized on `>=`.
|
|
292
|
+
|
|
293
|
+
### Removed
|
|
294
|
+
|
|
295
|
+
- `src/services/*` — logic folded into repositories.
|
|
296
|
+
- `src/domain/ports/*` barrels and per-port files — structural types
|
|
297
|
+
now live with the repositories they describe.
|
|
298
|
+
- Event-bus indirection — repositories emit via the engine's configured
|
|
299
|
+
transport directly.
|
|
300
|
+
|
|
301
|
+
### Added
|
|
302
|
+
|
|
303
|
+
- Subpath export `/notifications` — notification port + built-in
|
|
304
|
+
lifecycle payloads (tier up/down, points expiring, referral credit).
|
|
305
|
+
- Test restructure — 257 tests across unit + integration projects; the
|
|
306
|
+
deleted legacy suites were collapsed into focused scenario files that
|
|
307
|
+
exercise the new public API.
|
|
308
|
+
|
|
309
|
+
### Peer compatibility
|
|
310
|
+
|
|
311
|
+
Anyone on 0.1.0 wiring against the old `*.service.ts` modules will need
|
|
312
|
+
to migrate to the repository methods. Signatures are similar — earn /
|
|
313
|
+
redeem / adjust remain the nouns — but the call sites move from
|
|
314
|
+
`services.earning.earnPoints(...)` to `repositories.pointTransaction.earn(...)`
|
|
315
|
+
and similar. No behavior changes in the ledger semantics.
|
|
316
|
+
|
|
317
|
+
## [0.1.0]
|
|
318
|
+
|
|
319
|
+
Initial release. Loyalty points, tiers, earning rules, referrals, and
|
|
320
|
+
redemption engine for MongoDB. Tenant-scoped, event-emitting, atomic
|
|
321
|
+
point-ledger with CAS guards.
|