@classytic/loyalty 0.2.3 → 0.4.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 +114 -0
- package/README.md +69 -35
- package/dist/{config-96Sl6oNJ.d.mts → config-Dtun1duw.d.mts} +23 -0
- package/dist/events/index.d.mts +1 -1
- package/dist/events/index.mjs +2 -2
- package/dist/{events-BtZOdiPw.mjs → events-DN5_p6T0.mjs} +1 -1
- package/dist/{index-DArT4zkB.d.mts → index-D_5Ojc2c.d.mts} +1 -1
- package/dist/{index-Cl-WGBFJ.d.mts → index-Dngaj21_.d.mts} +19 -5
- package/dist/{index-DWarXsmw.d.mts → index-YSsi4e_G.d.mts} +15 -0
- package/dist/index.d.mts +154 -6
- package/dist/index.mjs +29 -29
- package/dist/{loyalty-events-Cyt9TwX6.mjs → loyalty-events-C38JA3cU.mjs} +4 -2
- package/dist/models/index.d.mts +497 -497
- package/dist/models/index.mjs +1 -1
- package/dist/{models-0_OQIqBy.mjs → models-Ddh62lgO.mjs} +4 -1
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-BMwidCp-.mjs → repositories-DEwCbLCh.mjs} +325 -160
- package/dist/types/index.d.mts +2 -2
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,120 @@
|
|
|
3
3
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.4.0] — addendum (2026-07-05)
|
|
7
|
+
|
|
8
|
+
### Changed (behavioral)
|
|
9
|
+
|
|
10
|
+
- **§P8.1 relay-only session guard** — `UnmanagedSessionError` now throws
|
|
11
|
+
only when a host session arrives with NEITHER a `[PENDING_EVENTS]`
|
|
12
|
+
queue NOR an outbox. Session + outbox + no queue is legal
|
|
13
|
+
durable-relay-only mode: the row commits with the host transaction, the
|
|
14
|
+
relay delivers post-commit, the immediate transport publish is skipped.
|
|
15
|
+
(Previously the guard was stricter than spec and threw even with an
|
|
16
|
+
outbox wired.) Aligns loyalty with invoice / catalog / order /
|
|
17
|
+
purchase / transfer / promo / payrun.
|
|
18
|
+
- **Stack floors raised** — peers `@classytic/mongokit >=3.18.0`,
|
|
19
|
+
`@classytic/repo-core >=0.7.0`; full suite verified against those
|
|
20
|
+
versions.
|
|
21
|
+
|
|
22
|
+
## [0.4.0] — 2026-07-03
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **`evaluateAction` is now idempotent per recurrence key.** The order path
|
|
27
|
+
dedupes with `${orderId}:${ruleId}`, but the action path (signup /
|
|
28
|
+
referral / birthday / review) never set an `idempotencyKey` — a retried
|
|
29
|
+
sweep or double-fired event double-awarded points. When `referenceId` is
|
|
30
|
+
supplied, each rule award now carries `${referenceId}:${ruleId}`; callers
|
|
31
|
+
encode the recurrence period in `referenceId` (e.g.
|
|
32
|
+
`birthday:2026:<memberId>`) and the unique partial index on
|
|
33
|
+
`idempotencyKey` becomes the authoritative once-only guard.
|
|
34
|
+
- **Action rules honor `conditions` now.** `evaluateAction` skipped
|
|
35
|
+
`matchesConditions` entirely, silently ignoring admin-authored
|
|
36
|
+
`tiers` / `dateRange` / `dayOfWeek` on action-type rules ("only gold
|
|
37
|
+
members get birthday points" did nothing). Conditions now apply;
|
|
38
|
+
`minOrderAmount` is skipped for actions (no order to measure).
|
|
39
|
+
- **`allowNonTransactional` now actually works at runtime.** The flag
|
|
40
|
+
silenced the boot capability gate but `MongoUnitOfWork.withTransaction`
|
|
41
|
+
still opened a real transaction unconditionally — on standalone Mongo
|
|
42
|
+
every `earnPoints`/`redeem` unit failed with "does not support retryable
|
|
43
|
+
writes" despite the documented escape hatch. The unit of work now
|
|
44
|
+
delegates to mongokit's `withTransaction` with `allowFallback` so the
|
|
45
|
+
transactions-unsupported error re-runs the unit on a plain session
|
|
46
|
+
(partial-write risk accepted, exactly what the flag opts into).
|
|
47
|
+
- Day arithmetic (tier-downgrade grace expiry, referral rate-limit window)
|
|
48
|
+
now uses `addDays` from `@classytic/primitives/calendar` — exact 24h × n
|
|
49
|
+
durations instead of local `setDate`, which shifted with the deploy
|
|
50
|
+
machine's `TZ` env. Primitives peer bumped to `>=0.9.0`.
|
|
51
|
+
|
|
52
|
+
## [0.3.0] — 2026-06-13
|
|
53
|
+
|
|
54
|
+
Stack upgrade to mongokit 3.16 / repo-core 0.6 / primitives 0.7.2 plus
|
|
55
|
+
verified correctness + PACKAGE_RULES compliance fixes. No new features.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **Stack actually resolves the new floor now (CRITICAL).** The committed
|
|
60
|
+
lockfile silently served `@classytic/mongokit@3.13.0` +
|
|
61
|
+
`@classytic/repo-core@0.4.0` despite the `^3.13.3` / `^0.4.2` pins, so the
|
|
62
|
+
whole test suite ran against the OLD stack. Clean reinstall + peer/dev bumps
|
|
63
|
+
pin the resolved tree to mongokit **3.16.0**, repo-core **0.6.0**, primitives
|
|
64
|
+
**0.7.2** (verified with `npm ls`).
|
|
65
|
+
- **§P8 outbox is now session-bound on the transactional verbs.** `earnPoints`,
|
|
66
|
+
`adjustPoints`, `reserve`, `confirm`, `release`, and `reverseConfirmed`
|
|
67
|
+
previously dispatched the event AFTER `withTransaction` committed, so
|
|
68
|
+
`outbox.save` ran OUTSIDE the unit of work — the business doc could commit
|
|
69
|
+
while the durable event row was lost (or vice-versa). Dispatch now runs
|
|
70
|
+
INSIDE the transaction (outbox row commits atomically with the write) and the
|
|
71
|
+
transport publish is deferred to a post-commit flush (`runLoyaltyUnit` +
|
|
72
|
+
`[PENDING_EVENTS]`), so in-process subscribers never see a state that may roll
|
|
73
|
+
back. A new integration test asserts the save is session-bound and that a
|
|
74
|
+
failing `outbox.save` rolls the `$inc` balance write back.
|
|
75
|
+
- **Redemption slot no longer permanently blocked after release.** The
|
|
76
|
+
`{ ownerType, ownerId }` unique index spanned ALL statuses, so a released /
|
|
77
|
+
reversed reservation E11000-blocked any retry on the same order (payment
|
|
78
|
+
retry, cart re-attempt) forever. It is now a **partial** unique index scoped
|
|
79
|
+
to active statuses (`reserved`, `confirmed`) — terminal states free the slot
|
|
80
|
+
(PACKAGE_RULES rule 32/38/39). Re-reserve-after-release and second-active-
|
|
81
|
+
reserve-blocked tests added.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- **`UnmanagedSessionError` guard (§P8).** When a host passes `ctx.session`
|
|
86
|
+
(it owns the outer transaction) without attaching a `[PENDING_EVENTS]` queue,
|
|
87
|
+
dispatch throws BEFORE any side effect instead of publishing / saving outside
|
|
88
|
+
the host's unit of work (which would leak ghost events on rollback).
|
|
89
|
+
`PENDING_EVENTS` and `flushPendingLoyaltyEvents` are exported for hosts that
|
|
90
|
+
manage their own transaction.
|
|
91
|
+
- **Capability boot gate.** `createLoyaltyEngine()` asserts the backend declares
|
|
92
|
+
the `transactions` capability (repo-core 0.6 / mongokit 3.16+) at engine
|
|
93
|
+
creation — fail loud at boot rather than with a cryptic driver error on the
|
|
94
|
+
first transactional call. `allowNonTransactional: true` opts out for local-dev
|
|
95
|
+
on standalone Mongo. New `LoyaltyCapabilityError`.
|
|
96
|
+
- **Cooperative cancellation in cron loops.** `processExpirations`, `awardBatch`,
|
|
97
|
+
`cleanupExpired` (redemption + referral), `evaluateAll`, and
|
|
98
|
+
`sweepExpiredOverrides` check `ctx.signal` via `throwIfAborted` between items —
|
|
99
|
+
abortable batches; each per-item write is its own committed unit of work, so
|
|
100
|
+
aborting never rolls back already-processed rows. `LoyaltyContext` gained
|
|
101
|
+
`signal?: AbortSignal`.
|
|
102
|
+
|
|
103
|
+
### Changed
|
|
104
|
+
|
|
105
|
+
- **Domain errors implement `HttpError`** from `@classytic/repo-core/errors`
|
|
106
|
+
(was a bespoke `httpStatus` shape). Clean break: `httpStatus` → `status`,
|
|
107
|
+
and codes are now hierarchical + lowercase (`loyalty.<entity>.<problem>`,
|
|
108
|
+
e.g. `loyalty.member.not_found`, `loyalty.points.insufficient`) so arc
|
|
109
|
+
serializes them to `ErrorContract` correctly. No deprecation alias.
|
|
110
|
+
- **Peer-dep ranges raised** — `@classytic/mongokit >=3.16.0`,
|
|
111
|
+
`@classytic/repo-core >=0.6.0` (`>=` style). devDeps bumped to match and
|
|
112
|
+
primitives devDep pinned to `^0.7.2`.
|
|
113
|
+
- **`exactOptionalPropertyTypes: true`** enabled in tsconfig (mandatory per
|
|
114
|
+
PACKAGE_RULES Build Hygiene / P10); fixed the three pre-existing optional-vs-
|
|
115
|
+
undefined violations it surfaced.
|
|
116
|
+
- **tsconfig no longer emits sourcemaps or declaration maps**
|
|
117
|
+
(`sourceMap: false`, `declarationMap: false`) — both leaked source. tsdown
|
|
118
|
+
was already correct; tsconfig now matches.
|
|
119
|
+
|
|
6
120
|
## [0.2.3] — 2026-05-08
|
|
7
121
|
|
|
8
122
|
### Fixed
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ Framework-agnostic. Works with Fastify (Arc), Express, NestJS, Next.js, or any N
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install @classytic/loyalty
|
|
11
11
|
# Peer deps:
|
|
12
|
-
npm install mongoose@^9
|
|
12
|
+
npm install mongoose@^9 zod@^4 \
|
|
13
|
+
@classytic/mongokit@^3.16 @classytic/repo-core@^0.6 @classytic/primitives@^0.7
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
## Quick Start
|
|
@@ -26,15 +27,15 @@ const engine = createLoyaltyEngine({
|
|
|
26
27
|
referral: { referrerRewardPoints: 200, refereeRewardPoints: 100 },
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
//
|
|
30
|
-
const member = await engine.
|
|
30
|
+
// Domain verbs live on the repositories (no service layer — PACKAGE_RULES §1–§3)
|
|
31
|
+
const member = await engine.repositories.member.enroll(
|
|
31
32
|
{ externalId: 'cust_1', externalType: 'customer', cardId: 'MBR-001' },
|
|
32
|
-
{ actorId: 'admin' },
|
|
33
|
+
{ actorId: 'admin', organizationId: 'org_1' },
|
|
33
34
|
);
|
|
34
35
|
|
|
35
|
-
await engine.
|
|
36
|
+
await engine.repositories.pointTransaction.earnPoints(
|
|
36
37
|
{ memberId: member._id, points: 500, description: 'Order #123' },
|
|
37
|
-
{ actorId: 'system' },
|
|
38
|
+
{ actorId: 'system', organizationId: 'org_1' },
|
|
38
39
|
);
|
|
39
40
|
```
|
|
40
41
|
|
|
@@ -42,21 +43,29 @@ await engine.services.ledger.earnPoints(
|
|
|
42
43
|
|
|
43
44
|
```
|
|
44
45
|
createLoyaltyEngine(config)
|
|
45
|
-
-> models 6 Mongoose models (auto-created,
|
|
46
|
-
-> repositories 6 repos (tenant-scoped CRUD
|
|
47
|
-
->
|
|
48
|
-
->
|
|
46
|
+
-> models 6 Mongoose models (auto-created, collision-safe)
|
|
47
|
+
-> repositories 6 repos = the public API (tenant-scoped CRUD + domain verbs)
|
|
48
|
+
-> events Arc-compatible transport (in-process fallback, or plug your own)
|
|
49
|
+
-> syncIndexes() deploy-time index builder
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
The capability boot gate asserts the backend supports multi-document
|
|
53
|
+
`transactions` (MongoDB replica set / sharded cluster) — the point-mutation
|
|
54
|
+
verbs are transactional. For local-dev on standalone Mongo, pass
|
|
55
|
+
`allowNonTransactional: true` (accepts partial-write risk).
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
57
|
+
### Repositories (the public API)
|
|
58
|
+
|
|
59
|
+
Domain verbs live on the repositories themselves — there is no service layer
|
|
60
|
+
(PACKAGE_RULES §1–§3). Access via `engine.repositories.<name>`.
|
|
61
|
+
|
|
62
|
+
| Repository | Purpose |
|
|
63
|
+
|------------|---------|
|
|
64
|
+
| `member` | Enroll, deactivate, reactivate, suspend |
|
|
65
|
+
| `pointTransaction` | Earn points, adjust points, `awardBatch`, process expirations |
|
|
66
|
+
| `earningRule` | CRUD earning rules (order, action, category, tier_bonus); `evaluateOrder` / `evaluateAction` |
|
|
67
|
+
| `tierDefinition` | CRUD tier definitions, evaluate members, set/clear/sweep overrides |
|
|
68
|
+
| `redemption` | Validate, reserve, confirm, release, reverse, cleanup expired reservations |
|
|
60
69
|
| `referral` | Generate codes, record referrals, approve/reject, rate limiting |
|
|
61
70
|
|
|
62
71
|
### Key Design Decisions
|
|
@@ -148,42 +157,67 @@ as `PlatformConfig.membership.pointsPerAmount` or `amountPerPoint`. See
|
|
|
148
157
|
|
|
149
158
|
## Events
|
|
150
159
|
|
|
160
|
+
`engine.events` is an arc-compatible `EventTransport` — subscribe glob-style.
|
|
161
|
+
Drop in any arc transport (Memory/Redis/Kafka) via `eventTransport` in config.
|
|
162
|
+
|
|
151
163
|
```typescript
|
|
152
164
|
import { LoyaltyEvents } from '@classytic/loyalty/events';
|
|
153
165
|
|
|
154
|
-
engine.events.
|
|
155
|
-
engine.events.
|
|
156
|
-
engine.events.
|
|
166
|
+
await engine.events.subscribe(LoyaltyEvents.POINTS_EARNED, (e) => { /* sync projection */ });
|
|
167
|
+
await engine.events.subscribe('loyalty.tier.*', (e) => { /* notify customer */ });
|
|
168
|
+
await engine.events.subscribe(LoyaltyEvents.REFERRAL_REWARDED, (e) => { /* notify both */ });
|
|
157
169
|
```
|
|
158
170
|
|
|
171
|
+
For durable delivery, wire a host-owned `outbox` (PACKAGE_RULES §5.5 + §P8):
|
|
172
|
+
the engine saves the event row inside the same transaction as the business
|
|
173
|
+
write (atomic), then publishes after commit. Hosts that own the outer
|
|
174
|
+
transaction must attach a `[PENDING_EVENTS]` queue and call
|
|
175
|
+
`flushPendingLoyaltyEvents(transport, queue)` after their commit, or a bare
|
|
176
|
+
`ctx.session` throws `UnmanagedSessionError`.
|
|
177
|
+
|
|
159
178
|
## Production Cron Jobs
|
|
160
179
|
|
|
180
|
+
All cron loops accept `ctx.signal` (an `AbortSignal`) and check it between
|
|
181
|
+
items, so a shutdown / timeout aborts cleanly between committed units of work.
|
|
182
|
+
|
|
161
183
|
```typescript
|
|
184
|
+
const ctx = { actorId: 'cron', organizationId: 'org_1', signal: ac.signal };
|
|
185
|
+
|
|
162
186
|
// Every 5 min — release expired point reservations
|
|
163
|
-
await engine.
|
|
187
|
+
await engine.repositories.redemption.cleanupExpired(ctx);
|
|
164
188
|
|
|
165
189
|
// Every hour — expire points past their expiresAt
|
|
166
|
-
await engine.
|
|
190
|
+
await engine.repositories.pointTransaction.processExpirations(ctx);
|
|
167
191
|
|
|
168
192
|
// Every 24h — re-evaluate all member tiers
|
|
169
|
-
await engine.
|
|
193
|
+
await engine.repositories.tierDefinition.evaluateAll(ctx);
|
|
194
|
+
|
|
195
|
+
// Hourly — clear expired tier overrides
|
|
196
|
+
await engine.repositories.tierDefinition.sweepExpiredOverrides(ctx);
|
|
170
197
|
```
|
|
171
198
|
|
|
172
199
|
## Error Codes
|
|
173
200
|
|
|
201
|
+
All domain errors implement `HttpError` from `@classytic/repo-core/errors`
|
|
202
|
+
(arc serializes them to the canonical `ErrorContract`). Codes are hierarchical
|
|
203
|
+
and lowercase.
|
|
204
|
+
|
|
174
205
|
| Code | HTTP | When |
|
|
175
206
|
|------|------|------|
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
207
|
+
| `loyalty.member.not_found` | 404 | Member doesn't exist |
|
|
208
|
+
| `loyalty.member.already_enrolled` | 409 | Duplicate enrollment |
|
|
209
|
+
| `loyalty.member.inactive` | 422 | Operating on inactive member |
|
|
210
|
+
| `loyalty.points.insufficient` | 400 | Not enough points to deduct |
|
|
211
|
+
| `loyalty.earning_rule.not_found` | 404 | Earning rule doesn't exist |
|
|
212
|
+
| `loyalty.tier.not_found` | 404 | Tier definition doesn't exist |
|
|
213
|
+
| `loyalty.redemption.expired` | 410 | Reservation TTL exceeded |
|
|
214
|
+
| `loyalty.referral.duplicate` | 409 | Referee already referred |
|
|
215
|
+
| `loyalty.referral.self` | 422 | Referring yourself |
|
|
216
|
+
| `loyalty.referral.circular` | 422 | A↔B circular referral |
|
|
217
|
+
| `loyalty.referral.limit_exceeded` | 429 | Max referrals per period |
|
|
218
|
+
| `loyalty.tenant.isolation` | 403 | Missing tenant context |
|
|
219
|
+
| `loyalty.transaction.unmanaged_session` | 400 | `ctx.session` passed without a `[PENDING_EVENTS]` queue |
|
|
220
|
+
| `loyalty.engine.missing_capabilities` | 500 | Backend lacks `transactions` at boot |
|
|
187
221
|
|
|
188
222
|
## AI Agent Skill
|
|
189
223
|
|
|
@@ -24,6 +24,17 @@ interface RepositoryPlugins {
|
|
|
24
24
|
interface LoyaltyConfig {
|
|
25
25
|
mongoose: Connection;
|
|
26
26
|
tenant?: TenantConfig$1 | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* IANA timezone name (e.g. `'Asia/Dhaka'`, `'America/New_York'`) used to
|
|
29
|
+
* evaluate wall-clock rule conditions — currently `EarningConditions.dayOfWeek`
|
|
30
|
+
* ("2× points on Fridays"). Resolved DST-exact per instant via
|
|
31
|
+
* `@classytic/primitives/timezone`. Validated at boot: an unknown zone throws
|
|
32
|
+
* `ValidationError`, never a silent mis-evaluation.
|
|
33
|
+
*
|
|
34
|
+
* Omitted → **UTC** (the day boundary is the UTC calendar day). Either way the
|
|
35
|
+
* result NEVER depends on the deploy machine's `TZ` env. PACKAGE_RULES P12.
|
|
36
|
+
*/
|
|
37
|
+
timezone?: string | undefined;
|
|
27
38
|
program?: {
|
|
28
39
|
id?: string | undefined;
|
|
29
40
|
conversionRate?: number | undefined;
|
|
@@ -99,6 +110,16 @@ interface LoyaltyConfig {
|
|
|
99
110
|
outbox?: OutboxStore | undefined;
|
|
100
111
|
notifications?: NotificationAdapter | undefined;
|
|
101
112
|
plugins?: RepositoryPlugins | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Skip the boot-time capability gate that asserts the backend supports
|
|
115
|
+
* multi-document `transactions`. Loyalty's point-mutation verbs (earn,
|
|
116
|
+
* adjust, reserve, confirm, release, reverse, processExpirations) run inside
|
|
117
|
+
* `withTransaction`; without a replica set those writes are NOT atomic.
|
|
118
|
+
* Leave `false` (default) in production — set `true` only for local-dev on
|
|
119
|
+
* standalone Mongo, accepting partial-write risk. See PACKAGE_RULES
|
|
120
|
+
* "Runtime capabilities".
|
|
121
|
+
*/
|
|
122
|
+
allowNonTransactional?: boolean | undefined;
|
|
102
123
|
}
|
|
103
124
|
interface ResolvedTenant {
|
|
104
125
|
enabled: boolean;
|
|
@@ -130,6 +151,8 @@ interface ResolvedConfig {
|
|
|
130
151
|
enabled: boolean;
|
|
131
152
|
defaultDays?: number;
|
|
132
153
|
};
|
|
154
|
+
/** Resolved IANA zone for wall-clock rule evaluation; `undefined` = UTC. */
|
|
155
|
+
timezone?: string;
|
|
133
156
|
tenant: ResolvedTenant;
|
|
134
157
|
}
|
|
135
158
|
//#endregion
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, D as MultiTierReferralAwarded, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, I as RedemptionReversed, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, U as TierOverrideCleared, V as TierDowngraded, W as TierOverrideExpired, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, h as RedemptionReversedPayload, i as CampaignAwardedPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, n as EventHandler, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, r as EventTransport, s as LoyaltyEventPayload, t as DomainEvent, u as MultiTierReferralAwardedPayload, v as TierOverrideLifecyclePayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, y as CampaignAwarded, z as ReferralRejected } from "../index-
|
|
1
|
+
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, D as MultiTierReferralAwarded, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, I as RedemptionReversed, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, U as TierOverrideCleared, V as TierDowngraded, W as TierOverrideExpired, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, h as RedemptionReversedPayload, i as CampaignAwardedPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, n as EventHandler, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, r as EventTransport, s as LoyaltyEventPayload, t as DomainEvent, u as MultiTierReferralAwardedPayload, v as TierOverrideLifecyclePayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, y as CampaignAwarded, z as ReferralRejected } from "../index-D_5Ojc2c.mjs";
|
|
2
2
|
export { CampaignAwarded, CampaignAwardedPayload, DomainEvent, EventHandler, EventTransport, InProcessLoyaltyBus, InProcessLoyaltyBusOptions, LoyaltyEventDefinition, LoyaltyEventMap, LoyaltyEventName, LoyaltyEventPayload, LoyaltyEventPayloadOf, LoyaltyEventSchema, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberLifecyclePayload, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, MultiTierReferralAwardedPayload, PointsAdjusted, PointsAdjustedPayload, PointsEarned, PointsEarnedPayload, PointsExpired, PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionPayload, RedemptionReleased, RedemptionReserved, RedemptionReversed, RedemptionReversedPayload, ReferralApproved, ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRewarded, TierChangedPayload, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierOverrideLifecyclePayload, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-
|
|
2
|
-
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-
|
|
1
|
+
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-DN5_p6T0.mjs";
|
|
2
|
+
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-C38JA3cU.mjs";
|
|
3
3
|
export { CampaignAwarded, InProcessLoyaltyBus, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, PointsAdjusted, PointsEarned, PointsExpired, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionReleased, RedemptionReserved, RedemptionReversed, ReferralApproved, ReferralRecorded, ReferralRejected, ReferralRewarded, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as LoyaltyEvents } from "./loyalty-events-
|
|
1
|
+
import { t as LoyaltyEvents } from "./loyalty-events-C38JA3cU.mjs";
|
|
2
2
|
import { createEvent, matchEventPattern } from "@classytic/primitives/events";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/events/in-process-bus.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as LoyaltyContext } from "./index-
|
|
1
|
+
import { o as LoyaltyContext } from "./index-YSsi4e_G.mjs";
|
|
2
2
|
import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventHandler as EventHandler$1, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { i as ReferralStatus, r as RedemptionStatus } from "./index-DMK8a98J.mjs";
|
|
2
|
-
import { S as EarningRule, _ as TierEvaluationBatch, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, o as LoyaltyContext, v as PointTransaction, x as TierDefinition, y as Member } from "./index-
|
|
2
|
+
import { S as EarningRule, _ as TierEvaluationBatch, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, o as LoyaltyContext, v as PointTransaction, x as TierDefinition, y as Member } from "./index-YSsi4e_G.mjs";
|
|
3
3
|
import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
|
|
4
|
-
import { c as ResolvedTenant, o as RepositoryPlugins, s as ResolvedConfig } from "./config-
|
|
4
|
+
import { c as ResolvedTenant, o as RepositoryPlugins, s as ResolvedConfig } from "./config-Dtun1duw.mjs";
|
|
5
5
|
import { LoyaltyModels } from "./models/index.mjs";
|
|
6
|
+
import { PluginType, Repository } from "@classytic/mongokit";
|
|
6
7
|
import { EventTransport } from "@classytic/primitives/events";
|
|
7
8
|
import { ClientSession, Model } from "mongoose";
|
|
8
|
-
import { PluginType, Repository } from "@classytic/mongokit";
|
|
9
9
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
10
10
|
import { ApprovalChain } from "@classytic/primitives/approval";
|
|
11
11
|
|
|
@@ -65,6 +65,7 @@ declare class PointTransactionRepository extends Repository<PointTransaction> {
|
|
|
65
65
|
constructor(model: Model<PointTransaction>, plugins?: PluginType[]);
|
|
66
66
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
67
67
|
private _dispatch;
|
|
68
|
+
private get _dispatchDeps();
|
|
68
69
|
/**
|
|
69
70
|
* Auto-inject tenant id from options into doc payload. Matches
|
|
70
71
|
* `@classytic/order` / `@classytic/promo` — see MemberRepository.
|
|
@@ -158,7 +159,19 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
158
159
|
action: string;
|
|
159
160
|
referenceId?: string;
|
|
160
161
|
}, ctx: LoyaltyContext): Promise<EarningResult>;
|
|
162
|
+
/**
|
|
163
|
+
* `orderTotal` is optional — action-type evaluations have no order, so
|
|
164
|
+
* `minOrderAmount` only applies when a total is supplied (order paths).
|
|
165
|
+
*/
|
|
161
166
|
private matchesConditions;
|
|
167
|
+
/**
|
|
168
|
+
* The wall-clock weekday of `instant` in JS `getDay()` convention
|
|
169
|
+
* (0=Sun … 6=Sat — what `EarningConditions.dayOfWeek` is authored in),
|
|
170
|
+
* evaluated in the engine's configured `timezone` (DST-exact) or UTC when
|
|
171
|
+
* unset. NEVER server-local `Date.getDay()`, which shifts with the deploy
|
|
172
|
+
* machine's `TZ` env (PACKAGE_RULES P12).
|
|
173
|
+
*/
|
|
174
|
+
private weekday;
|
|
162
175
|
private calculateOrderPoints;
|
|
163
176
|
private calculateCategoryPoints;
|
|
164
177
|
private applyRounding;
|
|
@@ -224,6 +237,7 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
224
237
|
createMany(docs: Parameters<Repository<Redemption>['createMany']>[0], options?: Parameters<Repository<Redemption>['createMany']>[1]): Promise<Redemption[]>;
|
|
225
238
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
226
239
|
private _dispatch;
|
|
240
|
+
private get _dispatchDeps();
|
|
227
241
|
/**
|
|
228
242
|
* Atomic compare-and-set status transition.
|
|
229
243
|
* Returns null if the current status doesn't match `expectedStatus` (CAS mismatch).
|
|
@@ -366,8 +380,8 @@ interface LoyaltyRepositories {
|
|
|
366
380
|
}
|
|
367
381
|
interface CreateRepositoriesInput {
|
|
368
382
|
models: LoyaltyModels;
|
|
369
|
-
plugins?: RepositoryPlugins;
|
|
370
|
-
tenant?: ResolvedTenant;
|
|
383
|
+
plugins?: RepositoryPlugins | undefined;
|
|
384
|
+
tenant?: ResolvedTenant | undefined;
|
|
371
385
|
config: ResolvedConfig;
|
|
372
386
|
events: EventTransport;
|
|
373
387
|
outbox?: OutboxStore | undefined;
|
|
@@ -8,6 +8,12 @@ interface EarningConditions {
|
|
|
8
8
|
categories?: string[];
|
|
9
9
|
tiers?: string[];
|
|
10
10
|
actions?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Days the rule applies, JS `getDay()` convention: 0=Sun … 6=Sat. Empty /
|
|
13
|
+
* omitted = every day. Evaluated against the wall-clock weekday in the
|
|
14
|
+
* engine's configured `timezone` (UTC when unset), never the server's local
|
|
15
|
+
* clock. See `LoyaltyConfig.timezone`.
|
|
16
|
+
*/
|
|
11
17
|
dayOfWeek?: number[];
|
|
12
18
|
dateRange?: {
|
|
13
19
|
start?: Date;
|
|
@@ -217,6 +223,15 @@ interface LoyaltyContext extends OperationContext {
|
|
|
217
223
|
organizationId?: string;
|
|
218
224
|
/** Override program ID for multi-program setups. */
|
|
219
225
|
programId?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Cooperative cancellation for long-running cron loops (point expiry,
|
|
228
|
+
* reservation cleanup, tier evaluation, override sweeps, campaign award).
|
|
229
|
+
* Checked at each loop boundary via `throwIfAborted` — aborting between
|
|
230
|
+
* iterations never rolls back an already-committed transaction, and each
|
|
231
|
+
* per-item write is its own unit of work. Threaded to mongokit round-trips
|
|
232
|
+
* by hosts that pass it; mongokit (3.16+) honours it at every op boundary.
|
|
233
|
+
*/
|
|
234
|
+
signal?: AbortSignal | undefined;
|
|
220
235
|
/** Dynamic tenant key support — e.g. `{ companyId: '...' }` — when a host
|
|
221
236
|
* configured a custom `contextKey`. */
|
|
222
237
|
[key: string]: unknown;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,159 @@
|
|
|
1
|
-
import { S as EarningRule, a as EnrollInput, b as MemberBalance, c as UpdateTierInput, d as EarningResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, n as CreateEarningRuleInput, o as LoyaltyContext, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, v as PointTransaction, x as TierDefinition, y as Member } from "./index-
|
|
1
|
+
import { S as EarningRule, a as EnrollInput, b as MemberBalance, c as UpdateTierInput, d as EarningResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, n as CreateEarningRuleInput, o as LoyaltyContext, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, v as PointTransaction, x as TierDefinition, y as Member } from "./index-YSsi4e_G.mjs";
|
|
2
2
|
import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
|
|
3
|
-
import { c as ResolvedTenant, n as LoyaltyConfig, r as LoyaltyModelName, s as ResolvedConfig } from "./config-
|
|
4
|
-
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, V as TierDowngraded, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, s as LoyaltyEventPayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, z as ReferralRejected } from "./index-
|
|
3
|
+
import { c as ResolvedTenant, n as LoyaltyConfig, r as LoyaltyModelName, s as ResolvedConfig } from "./config-Dtun1duw.mjs";
|
|
4
|
+
import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, V as TierDowngraded, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, s as LoyaltyEventPayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, z as ReferralRejected } from "./index-D_5Ojc2c.mjs";
|
|
5
5
|
import { LoyaltyModels } from "./models/index.mjs";
|
|
6
|
-
import { a as ReferralRepository, c as Redemption, d as MemberRepository, f as UnitOfWork, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, o as Referral, s as RedemptionRepository, u as PointTransactionRepository } from "./index-
|
|
7
|
-
import { DomainEvent, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
|
|
6
|
+
import { a as ReferralRepository, c as Redemption, d as MemberRepository, f as UnitOfWork, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, o as Referral, s as RedemptionRepository, u as PointTransactionRepository } from "./index-Dngaj21_.mjs";
|
|
7
|
+
import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
|
|
8
|
+
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
9
|
+
import { HttpError } from "@classytic/repo-core/errors";
|
|
8
10
|
|
|
11
|
+
//#region src/domain/errors/base.d.ts
|
|
12
|
+
/**
|
|
13
|
+
* Base for every loyalty throwable. Implements `HttpError` from
|
|
14
|
+
* `@classytic/repo-core/errors` (the org-canonical throwable contract) so arc
|
|
15
|
+
* catches and serializes them to `ErrorContract` via `toErrorContract()` with
|
|
16
|
+
* the right HTTP `status` and a stable, machine-readable `code`.
|
|
17
|
+
*
|
|
18
|
+
* Codes are hierarchical + lowercase per PACKAGE_RULES ("Errors and wire
|
|
19
|
+
* envelopes"): `loyalty.<entity>.<problem>`. The `status` field (not the old
|
|
20
|
+
* `httpStatus`) is what `HttpError` / arc read — a clean break, no alias.
|
|
21
|
+
*/
|
|
22
|
+
declare abstract class LoyaltyError extends Error implements HttpError {
|
|
23
|
+
abstract readonly code: string;
|
|
24
|
+
abstract readonly status: number;
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
toJSON(): {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
name: string;
|
|
30
|
+
status: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/domain/errors/domain-errors.d.ts
|
|
35
|
+
declare class InsufficientPointsError extends LoyaltyError {
|
|
36
|
+
readonly code = "loyalty.points.insufficient";
|
|
37
|
+
readonly status = 400;
|
|
38
|
+
constructor(available: number, requested: number);
|
|
39
|
+
}
|
|
40
|
+
declare class MemberNotFoundError extends LoyaltyError {
|
|
41
|
+
readonly code = "loyalty.member.not_found";
|
|
42
|
+
readonly status = 404;
|
|
43
|
+
constructor(id: string);
|
|
44
|
+
}
|
|
45
|
+
declare class MemberAlreadyEnrolledError extends LoyaltyError {
|
|
46
|
+
readonly code = "loyalty.member.already_enrolled";
|
|
47
|
+
readonly status = 409;
|
|
48
|
+
constructor(externalId: string);
|
|
49
|
+
}
|
|
50
|
+
declare class MemberInactiveError extends LoyaltyError {
|
|
51
|
+
readonly code = "loyalty.member.inactive";
|
|
52
|
+
readonly status = 422;
|
|
53
|
+
constructor(id: string);
|
|
54
|
+
}
|
|
55
|
+
declare class RuleNotFoundError extends LoyaltyError {
|
|
56
|
+
readonly code = "loyalty.earning_rule.not_found";
|
|
57
|
+
readonly status = 404;
|
|
58
|
+
constructor(id: string);
|
|
59
|
+
}
|
|
60
|
+
declare class TierNotFoundError extends LoyaltyError {
|
|
61
|
+
readonly code = "loyalty.tier.not_found";
|
|
62
|
+
readonly status = 404;
|
|
63
|
+
constructor(id: string);
|
|
64
|
+
}
|
|
65
|
+
declare class RedemptionNotFoundError extends LoyaltyError {
|
|
66
|
+
readonly code = "loyalty.redemption.not_found";
|
|
67
|
+
readonly status = 404;
|
|
68
|
+
constructor(id: string);
|
|
69
|
+
}
|
|
70
|
+
declare class RedemptionExpiredError extends LoyaltyError {
|
|
71
|
+
readonly code = "loyalty.redemption.expired";
|
|
72
|
+
readonly status = 410;
|
|
73
|
+
constructor(id: string);
|
|
74
|
+
}
|
|
75
|
+
declare class ReferralNotFoundError extends LoyaltyError {
|
|
76
|
+
readonly code = "loyalty.referral.not_found";
|
|
77
|
+
readonly status = 404;
|
|
78
|
+
constructor(id: string);
|
|
79
|
+
}
|
|
80
|
+
declare class DuplicateReferralError extends LoyaltyError {
|
|
81
|
+
readonly code = "loyalty.referral.duplicate";
|
|
82
|
+
readonly status = 409;
|
|
83
|
+
constructor(refereeId: string);
|
|
84
|
+
}
|
|
85
|
+
declare class ReferralLimitExceededError extends LoyaltyError {
|
|
86
|
+
readonly code = "loyalty.referral.limit_exceeded";
|
|
87
|
+
readonly status = 429;
|
|
88
|
+
constructor(limit: number, periodDays: number);
|
|
89
|
+
}
|
|
90
|
+
declare class SelfReferralError extends LoyaltyError {
|
|
91
|
+
readonly code = "loyalty.referral.self";
|
|
92
|
+
readonly status = 422;
|
|
93
|
+
constructor();
|
|
94
|
+
}
|
|
95
|
+
declare class CircularReferralError extends LoyaltyError {
|
|
96
|
+
readonly code = "loyalty.referral.circular";
|
|
97
|
+
readonly status = 422;
|
|
98
|
+
constructor();
|
|
99
|
+
}
|
|
100
|
+
declare class ValidationError extends LoyaltyError {
|
|
101
|
+
readonly code = "loyalty.validation.invalid";
|
|
102
|
+
readonly status = 400;
|
|
103
|
+
}
|
|
104
|
+
declare class TenantIsolationError extends LoyaltyError {
|
|
105
|
+
readonly code = "loyalty.tenant.isolation";
|
|
106
|
+
readonly status = 403;
|
|
107
|
+
constructor();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A host passed `ctx.session` (it owns the outer transaction) but did NOT
|
|
111
|
+
* attach a `[PENDING_EVENTS]` queue. Publishing to in-process subscribers
|
|
112
|
+
* BEFORE the host's commit would leak ghost events on rollback, and the
|
|
113
|
+
* outbox save would not be coordinated with the host's unit of work.
|
|
114
|
+
*
|
|
115
|
+
* Two safe patterns: (1) let loyalty own the transaction — call the verb
|
|
116
|
+
* WITHOUT `ctx.session`; or (2) attach a fresh `[PENDING_EVENTS]` array via
|
|
117
|
+
* `{ ...ctx, [PENDING_EVENTS]: queue }` and call `flushPendingLoyaltyEvents`
|
|
118
|
+
* after your transaction commits. Both are exported from the package root.
|
|
119
|
+
*/
|
|
120
|
+
declare class UnmanagedSessionError extends LoyaltyError {
|
|
121
|
+
readonly code = "loyalty.transaction.unmanaged_session";
|
|
122
|
+
readonly status = 400;
|
|
123
|
+
constructor();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The backend doesn't declare the runtime capabilities loyalty's
|
|
127
|
+
* transactional verbs require (earn / adjust / reserve / confirm / release /
|
|
128
|
+
* reverse all run inside `withTransaction`). MongoDB must run as a replica set
|
|
129
|
+
* or sharded cluster. Thrown at engine creation — fail loud at boot, not with
|
|
130
|
+
* a cryptic driver error on the first transactional call.
|
|
131
|
+
*/
|
|
132
|
+
declare class LoyaltyCapabilityError extends LoyaltyError {
|
|
133
|
+
readonly code = "loyalty.engine.missing_capabilities";
|
|
134
|
+
readonly status = 500;
|
|
135
|
+
constructor(missing: readonly string[]);
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/events/dispatch.d.ts
|
|
139
|
+
/**
|
|
140
|
+
* Internal key under which the post-commit event-publish queue is threaded
|
|
141
|
+
* through the context. When set, `dispatchLoyaltyEvent` APPENDS to this queue
|
|
142
|
+
* instead of publishing immediately — the verb that owns the transaction
|
|
143
|
+
* flushes it after commit. A `Symbol.for` so it never collides with
|
|
144
|
+
* host-supplied context fields and never appears in the public type.
|
|
145
|
+
*/
|
|
146
|
+
declare const PENDING_EVENTS: unique symbol;
|
|
147
|
+
/**
|
|
148
|
+
* Drains a queue of deferred events (the one attached under `[PENDING_EVENTS]`)
|
|
149
|
+
* through the transport, swallowing per-event errors. Called by transactional
|
|
150
|
+
* verbs AFTER `withTransaction` commits, and exported so hosts that own the
|
|
151
|
+
* outer transaction can flush after their own commit.
|
|
152
|
+
*/
|
|
153
|
+
declare function flushPendingLoyaltyEvents(events: EventTransport$1 | undefined, queue: DomainEvent$1[], logger?: {
|
|
154
|
+
error(message: string, ...args: unknown[]): void;
|
|
155
|
+
} | undefined): Promise<void>;
|
|
156
|
+
//#endregion
|
|
9
157
|
//#region src/index.d.ts
|
|
10
158
|
declare function resolveConfig(config: LoyaltyConfig): ResolvedConfig;
|
|
11
159
|
/**
|
|
@@ -34,4 +182,4 @@ interface LoyaltyEngine {
|
|
|
34
182
|
}
|
|
35
183
|
declare function createLoyaltyEngine(config: LoyaltyConfig): LoyaltyEngine;
|
|
36
184
|
//#endregion
|
|
37
|
-
export { type AdjustPointsInput, type BalanceSummary, type CreateEarningRuleInput, type CreateTierInput, type DomainEvent, type EarnPointsInput, type EarningResult, type EarningRule, EarningRuleRepository, type EnrollInput, type EventHandler, type EventTransport, InProcessLoyaltyBus, type InProcessLoyaltyBusOptions, type LoyaltyConfig, type LoyaltyContext, LoyaltyEngine, type LoyaltyEventDefinition, type LoyaltyEventMap, type LoyaltyEventName, type LoyaltyEventPayload, type LoyaltyEventPayloadOf, type LoyaltyEventSchema, LoyaltyEvents, type LoyaltyModelName, type LoyaltyModels, type LoyaltyRepositories, type Member, type MemberBalance, MemberDeactivated, MemberEnrolled, type MemberLifecyclePayload, MemberReactivated, MemberRepository, MemberSuspended, type NotificationAdapter, type PointTransaction, PointTransactionRepository, PointsAdjusted, type PointsAdjustedPayload, PointsEarned, type PointsEarnedPayload, PointsExpired, type PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, type Redemption, RedemptionConfirmed, type RedemptionPayload, RedemptionReleased, RedemptionRepository, RedemptionReserved, type RedemptionValidation, type Referral, ReferralApproved, type ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRepository, ReferralRewarded, type ResolvedConfig, type ResolvedTenant, type TierChangedPayload, type TierDefinition, TierDefinitionRepository, TierDowngraded, type TierEvaluation, TierGraceStarted, TierUpgraded, type UnitOfWork, type UpdateEarningRuleInput, type UpdateTierInput, createEvent, createLoyaltyEngine, loyaltyEventDefinitions, resolveConfig };
|
|
185
|
+
export { type AdjustPointsInput, type BalanceSummary, CircularReferralError, type CreateEarningRuleInput, type CreateTierInput, type DomainEvent, DuplicateReferralError, type EarnPointsInput, type EarningResult, type EarningRule, EarningRuleRepository, type EnrollInput, type EventHandler, type EventTransport, InProcessLoyaltyBus, type InProcessLoyaltyBusOptions, InsufficientPointsError, LoyaltyCapabilityError, type LoyaltyConfig, type LoyaltyContext, LoyaltyEngine, type LoyaltyEventDefinition, type LoyaltyEventMap, type LoyaltyEventName, type LoyaltyEventPayload, type LoyaltyEventPayloadOf, type LoyaltyEventSchema, LoyaltyEvents, type LoyaltyModelName, type LoyaltyModels, type LoyaltyRepositories, type Member, MemberAlreadyEnrolledError, type MemberBalance, MemberDeactivated, MemberEnrolled, MemberInactiveError, type MemberLifecyclePayload, MemberNotFoundError, MemberReactivated, MemberRepository, MemberSuspended, type NotificationAdapter, PENDING_EVENTS, type PointTransaction, PointTransactionRepository, PointsAdjusted, type PointsAdjustedPayload, PointsEarned, type PointsEarnedPayload, PointsExpired, type PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, type Redemption, RedemptionConfirmed, RedemptionExpiredError, RedemptionNotFoundError, type RedemptionPayload, RedemptionReleased, RedemptionRepository, RedemptionReserved, type RedemptionValidation, type Referral, ReferralApproved, ReferralLimitExceededError, ReferralNotFoundError, type ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRepository, ReferralRewarded, type ResolvedConfig, type ResolvedTenant, RuleNotFoundError, SelfReferralError, TenantIsolationError, type TierChangedPayload, type TierDefinition, TierDefinitionRepository, TierDowngraded, type TierEvaluation, TierGraceStarted, TierNotFoundError, TierUpgraded, type UnitOfWork, UnmanagedSessionError, type UpdateEarningRuleInput, type UpdateTierInput, ValidationError, createEvent, createLoyaltyEngine, flushPendingLoyaltyEvents, loyaltyEventDefinitions, resolveConfig };
|