@classytic/loyalty 0.3.0 → 0.5.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 +231 -169
- package/dist/{config-BdSyAdnF.d.mts → config-Dtun1duw.d.mts} +13 -0
- package/dist/events/index.d.mts +1 -1
- package/dist/{index-CnESLipu.d.mts → index-D_5Ojc2c.d.mts} +1 -1
- package/dist/{index-BN96EDac.d.mts → index-Dngaj21_.d.mts} +15 -3
- package/dist/{index-D20Cn4KB.d.mts → index-YSsi4e_G.d.mts} +6 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +9 -15
- package/dist/models/index.d.mts +25 -25
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-C_p8asCn.mjs → repositories-CjWfUlrA.mjs} +31 -12
- package/dist/types/index.d.mts +2 -2
- package/package.json +116 -116
package/CHANGELOG.md
CHANGED
|
@@ -1,169 +1,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.
|
|
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
|
-
|
|
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
|
-
the
|
|
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
|
-
|
|
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.5.0] — unpublished (2026-07-15)
|
|
7
|
+
|
|
8
|
+
### Changed — mongokit 3.22 adopted (peer floors: mongokit >=3.22.0, repo-core >=0.13.0, primitives >=0.11.0)
|
|
9
|
+
|
|
10
|
+
- **Points ledger fence enforced** — `PointTransaction` rows are the
|
|
11
|
+
balance audit trail; `appendOnlyPlugin` now refuses every mutation
|
|
12
|
+
path (405 `APPEND_ONLY_VIOLATION`). Previously nothing enforced it —
|
|
13
|
+
a stray `update()` could rewrite an earn/burn row and silently
|
|
14
|
+
desync the audit trail from member balances. The expiry sweeper's
|
|
15
|
+
`expiredAt` stamp is the one engine-owned lifecycle write and passes
|
|
16
|
+
the audited `bypassAppendOnly` escape.
|
|
17
|
+
- No FSM boilerplate existed (no assert+claim sites, no
|
|
18
|
+
read-modify-write history) — loyalty was already clean there; this
|
|
19
|
+
release is the fence + kit refresh (clean registry install, no
|
|
20
|
+
legacy flags).
|
|
21
|
+
|
|
22
|
+
## [0.4.0] — addendum (2026-07-05)
|
|
23
|
+
|
|
24
|
+
### Changed (behavioral)
|
|
25
|
+
|
|
26
|
+
- **§P8.1 relay-only session guard** — `UnmanagedSessionError` now throws
|
|
27
|
+
only when a host session arrives with NEITHER a `[PENDING_EVENTS]`
|
|
28
|
+
queue NOR an outbox. Session + outbox + no queue is legal
|
|
29
|
+
durable-relay-only mode: the row commits with the host transaction, the
|
|
30
|
+
relay delivers post-commit, the immediate transport publish is skipped.
|
|
31
|
+
(Previously the guard was stricter than spec and threw even with an
|
|
32
|
+
outbox wired.) Aligns loyalty with invoice / catalog / order /
|
|
33
|
+
purchase / transfer / promo / payrun.
|
|
34
|
+
- **Stack floors raised** — peers `@classytic/mongokit >=3.18.0`,
|
|
35
|
+
`@classytic/repo-core >=0.7.0`; full suite verified against those
|
|
36
|
+
versions.
|
|
37
|
+
|
|
38
|
+
## [0.4.0] — 2026-07-03
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **`evaluateAction` is now idempotent per recurrence key.** The order path
|
|
43
|
+
dedupes with `${orderId}:${ruleId}`, but the action path (signup /
|
|
44
|
+
referral / birthday / review) never set an `idempotencyKey` — a retried
|
|
45
|
+
sweep or double-fired event double-awarded points. When `referenceId` is
|
|
46
|
+
supplied, each rule award now carries `${referenceId}:${ruleId}`; callers
|
|
47
|
+
encode the recurrence period in `referenceId` (e.g.
|
|
48
|
+
`birthday:2026:<memberId>`) and the unique partial index on
|
|
49
|
+
`idempotencyKey` becomes the authoritative once-only guard.
|
|
50
|
+
- **Action rules honor `conditions` now.** `evaluateAction` skipped
|
|
51
|
+
`matchesConditions` entirely, silently ignoring admin-authored
|
|
52
|
+
`tiers` / `dateRange` / `dayOfWeek` on action-type rules ("only gold
|
|
53
|
+
members get birthday points" did nothing). Conditions now apply;
|
|
54
|
+
`minOrderAmount` is skipped for actions (no order to measure).
|
|
55
|
+
- **`allowNonTransactional` now actually works at runtime.** The flag
|
|
56
|
+
silenced the boot capability gate but `MongoUnitOfWork.withTransaction`
|
|
57
|
+
still opened a real transaction unconditionally — on standalone Mongo
|
|
58
|
+
every `earnPoints`/`redeem` unit failed with "does not support retryable
|
|
59
|
+
writes" despite the documented escape hatch. The unit of work now
|
|
60
|
+
delegates to mongokit's `withTransaction` with `allowFallback` so the
|
|
61
|
+
transactions-unsupported error re-runs the unit on a plain session
|
|
62
|
+
(partial-write risk accepted, exactly what the flag opts into).
|
|
63
|
+
- Day arithmetic (tier-downgrade grace expiry, referral rate-limit window)
|
|
64
|
+
now uses `addDays` from `@classytic/primitives/calendar` — exact 24h × n
|
|
65
|
+
durations instead of local `setDate`, which shifted with the deploy
|
|
66
|
+
machine's `TZ` env. Primitives peer bumped to `>=0.9.0`.
|
|
67
|
+
|
|
68
|
+
## [0.3.0] — 2026-06-13
|
|
69
|
+
|
|
70
|
+
Stack upgrade to mongokit 3.16 / repo-core 0.6 / primitives 0.7.2 plus
|
|
71
|
+
verified correctness + PACKAGE_RULES compliance fixes. No new features.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **Stack actually resolves the new floor now (CRITICAL).** The committed
|
|
76
|
+
lockfile silently served `@classytic/mongokit@3.13.0` +
|
|
77
|
+
`@classytic/repo-core@0.4.0` despite the `^3.13.3` / `^0.4.2` pins, so the
|
|
78
|
+
whole test suite ran against the OLD stack. Clean reinstall + peer/dev bumps
|
|
79
|
+
pin the resolved tree to mongokit **3.16.0**, repo-core **0.6.0**, primitives
|
|
80
|
+
**0.7.2** (verified with `npm ls`).
|
|
81
|
+
- **§P8 outbox is now session-bound on the transactional verbs.** `earnPoints`,
|
|
82
|
+
`adjustPoints`, `reserve`, `confirm`, `release`, and `reverseConfirmed`
|
|
83
|
+
previously dispatched the event AFTER `withTransaction` committed, so
|
|
84
|
+
`outbox.save` ran OUTSIDE the unit of work — the business doc could commit
|
|
85
|
+
while the durable event row was lost (or vice-versa). Dispatch now runs
|
|
86
|
+
INSIDE the transaction (outbox row commits atomically with the write) and the
|
|
87
|
+
transport publish is deferred to a post-commit flush (`runLoyaltyUnit` +
|
|
88
|
+
`[PENDING_EVENTS]`), so in-process subscribers never see a state that may roll
|
|
89
|
+
back. A new integration test asserts the save is session-bound and that a
|
|
90
|
+
failing `outbox.save` rolls the `$inc` balance write back.
|
|
91
|
+
- **Redemption slot no longer permanently blocked after release.** The
|
|
92
|
+
`{ ownerType, ownerId }` unique index spanned ALL statuses, so a released /
|
|
93
|
+
reversed reservation E11000-blocked any retry on the same order (payment
|
|
94
|
+
retry, cart re-attempt) forever. It is now a **partial** unique index scoped
|
|
95
|
+
to active statuses (`reserved`, `confirmed`) — terminal states free the slot
|
|
96
|
+
(PACKAGE_RULES rule 32/38/39). Re-reserve-after-release and second-active-
|
|
97
|
+
reserve-blocked tests added.
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **`UnmanagedSessionError` guard (§P8).** When a host passes `ctx.session`
|
|
102
|
+
(it owns the outer transaction) without attaching a `[PENDING_EVENTS]` queue,
|
|
103
|
+
dispatch throws BEFORE any side effect instead of publishing / saving outside
|
|
104
|
+
the host's unit of work (which would leak ghost events on rollback).
|
|
105
|
+
`PENDING_EVENTS` and `flushPendingLoyaltyEvents` are exported for hosts that
|
|
106
|
+
manage their own transaction.
|
|
107
|
+
- **Capability boot gate.** `createLoyaltyEngine()` asserts the backend declares
|
|
108
|
+
the `transactions` capability (repo-core 0.6 / mongokit 3.16+) at engine
|
|
109
|
+
creation — fail loud at boot rather than with a cryptic driver error on the
|
|
110
|
+
first transactional call. `allowNonTransactional: true` opts out for local-dev
|
|
111
|
+
on standalone Mongo. New `LoyaltyCapabilityError`.
|
|
112
|
+
- **Cooperative cancellation in cron loops.** `processExpirations`, `awardBatch`,
|
|
113
|
+
`cleanupExpired` (redemption + referral), `evaluateAll`, and
|
|
114
|
+
`sweepExpiredOverrides` check `ctx.signal` via `throwIfAborted` between items —
|
|
115
|
+
abortable batches; each per-item write is its own committed unit of work, so
|
|
116
|
+
aborting never rolls back already-processed rows. `LoyaltyContext` gained
|
|
117
|
+
`signal?: AbortSignal`.
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- **Domain errors implement `HttpError`** from `@classytic/repo-core/errors`
|
|
122
|
+
(was a bespoke `httpStatus` shape). Clean break: `httpStatus` → `status`,
|
|
123
|
+
and codes are now hierarchical + lowercase (`loyalty.<entity>.<problem>`,
|
|
124
|
+
e.g. `loyalty.member.not_found`, `loyalty.points.insufficient`) so arc
|
|
125
|
+
serializes them to `ErrorContract` correctly. No deprecation alias.
|
|
126
|
+
- **Peer-dep ranges raised** — `@classytic/mongokit >=3.16.0`,
|
|
127
|
+
`@classytic/repo-core >=0.6.0` (`>=` style). devDeps bumped to match and
|
|
128
|
+
primitives devDep pinned to `^0.7.2`.
|
|
129
|
+
- **`exactOptionalPropertyTypes: true`** enabled in tsconfig (mandatory per
|
|
130
|
+
PACKAGE_RULES Build Hygiene / P10); fixed the three pre-existing optional-vs-
|
|
131
|
+
undefined violations it surfaced.
|
|
132
|
+
- **tsconfig no longer emits sourcemaps or declaration maps**
|
|
133
|
+
(`sourceMap: false`, `declarationMap: false`) — both leaked source. tsdown
|
|
134
|
+
was already correct; tsconfig now matches.
|
|
135
|
+
|
|
136
|
+
## [0.2.3] — 2026-05-08
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
|
|
140
|
+
- **§P8 dispatch — `outbox.save` failures now propagate.** [src/events/dispatch.ts](src/events/dispatch.ts)
|
|
141
|
+
previously caught `outbox.save` errors and swallowed them silently. Per
|
|
142
|
+
PACKAGE_RULES §P8 the outbox row is the durability anchor for the
|
|
143
|
+
transactional-outbox pattern: if the save fails, the caller's transaction
|
|
144
|
+
MUST roll back so the business doc and the event row land atomically.
|
|
145
|
+
Swallowing the failure meant a parent doc could commit while the event
|
|
146
|
+
vanished, with no relay row to recover from. Fix re-throws after logging.
|
|
147
|
+
`events.publish` failures continue to be swallowed (correctly, per §P8 —
|
|
148
|
+
the host's outbox relay re-publishes from the durable row), but now log
|
|
149
|
+
before swallowing so failures are observable.
|
|
150
|
+
- **`DispatchDeps.logger` field added.** Optional. Both arms of the
|
|
151
|
+
dispatch helper log through it when set, surfacing failures in the host's
|
|
152
|
+
observability stack without coupling the package to a specific logger.
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- **Peer-dep ranges tightened** — `@classytic/mongokit` `>=3.13.3`
|
|
157
|
+
(was `>=3.13.0`) and `@classytic/repo-core` `>=0.4.2` (was `>=0.4.0`).
|
|
158
|
+
3.13.3 preserves `errorLabels` on `MongoServerError` so concurrent
|
|
159
|
+
transactional writes (the `claim()`-based redemption + tier-eval paths)
|
|
160
|
+
retry correctly under contention; 0.4.2 adds the typed `FindAllOptions`
|
|
161
|
+
shape with `limit?` for callers that need a bounded non-paginated read.
|
|
162
|
+
|
|
163
|
+
### Internal
|
|
164
|
+
|
|
165
|
+
- Replaced 4 `arr.find()!` non-null assertions in [src/index.ts](src/index.ts)
|
|
166
|
+
+ scenario tests with explicit narrowing — TS now proves what the runtime
|
|
167
|
+
shape already guaranteed.
|
|
168
|
+
|
|
169
|
+
## [0.2.0]
|
|
170
|
+
|
|
171
|
+
Structural rewrite on top of the 0.1.0 shape. The public API is now a
|
|
172
|
+
smaller, sharper surface organized around seven subpath exports
|
|
173
|
+
(`/domain/enums`, `/models`, `/repositories`, `/events`, `/types`,
|
|
174
|
+
`/notifications`, plus the root engine factory). Legacy barrel + service
|
|
175
|
+
layers were removed; hosts wire business flows directly against the
|
|
176
|
+
repositories and the engine factory.
|
|
177
|
+
|
|
178
|
+
### Changed
|
|
179
|
+
|
|
180
|
+
- **Domain restructure.** Dropped the `src/domain/ports/` + `src/services/`
|
|
181
|
+
layer in favor of thin repositories that expose exactly the operations
|
|
182
|
+
hosts need — `earn`, `redeem`, `adjust`, `expire`, referral credit,
|
|
183
|
+
tier recomputation. No more `earning.service` / `redemption.service` /
|
|
184
|
+
`tier.service` / `referral.service` / `ledger.service` / `member.service`
|
|
185
|
+
/ `member-guard` indirections; the equivalent logic lives on the
|
|
186
|
+
repositories where it's naturally atomic.
|
|
187
|
+
- **Events subsystem simplified.** Removed `events/event-bus.ts` +
|
|
188
|
+
`events/event-types.ts` (both are internal indirection that the engine
|
|
189
|
+
didn't need). Event payloads are now colocated with the repository
|
|
190
|
+
that emits them.
|
|
191
|
+
- **Repositories updated to mongokit 3.11 + repo-core 0.2 conventions.**
|
|
192
|
+
Every repo extends `Repository<T>` directly, uses the tenant plugin via
|
|
193
|
+
`injectTenant`, and atomic state changes go through CAS guards.
|
|
194
|
+
- **Peer deps** unchanged in spirit, tightened in range:
|
|
195
|
+
- `@classytic/mongokit >= 3.11`
|
|
196
|
+
- `@classytic/primitives >= 0.1`
|
|
197
|
+
- `@classytic/repo-core >= 0.2`
|
|
198
|
+
- `mongoose >= 9.4`, `zod >= 4`
|
|
199
|
+
- **DevDeps:** `@classytic/primitives` devDep moved from `file:` to `>=0.1.0`
|
|
200
|
+
(now that primitives ships on npm); all `@classytic/*` dev ranges
|
|
201
|
+
standardized on `>=`.
|
|
202
|
+
|
|
203
|
+
### Removed
|
|
204
|
+
|
|
205
|
+
- `src/services/*` — logic folded into repositories.
|
|
206
|
+
- `src/domain/ports/*` barrels and per-port files — structural types
|
|
207
|
+
now live with the repositories they describe.
|
|
208
|
+
- Event-bus indirection — repositories emit via the engine's configured
|
|
209
|
+
transport directly.
|
|
210
|
+
|
|
211
|
+
### Added
|
|
212
|
+
|
|
213
|
+
- Subpath export `/notifications` — notification port + built-in
|
|
214
|
+
lifecycle payloads (tier up/down, points expiring, referral credit).
|
|
215
|
+
- Test restructure — 257 tests across unit + integration projects; the
|
|
216
|
+
deleted legacy suites were collapsed into focused scenario files that
|
|
217
|
+
exercise the new public API.
|
|
218
|
+
|
|
219
|
+
### Peer compatibility
|
|
220
|
+
|
|
221
|
+
Anyone on 0.1.0 wiring against the old `*.service.ts` modules will need
|
|
222
|
+
to migrate to the repository methods. Signatures are similar — earn /
|
|
223
|
+
redeem / adjust remain the nouns — but the call sites move from
|
|
224
|
+
`services.earning.earnPoints(...)` to `repositories.pointTransaction.earn(...)`
|
|
225
|
+
and similar. No behavior changes in the ledger semantics.
|
|
226
|
+
|
|
227
|
+
## [0.1.0]
|
|
228
|
+
|
|
229
|
+
Initial release. Loyalty points, tiers, earning rules, referrals, and
|
|
230
|
+
redemption engine for MongoDB. Tenant-scoped, event-emitting, atomic
|
|
231
|
+
point-ledger with CAS guards.
|
|
@@ -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;
|
|
@@ -140,6 +151,8 @@ interface ResolvedConfig {
|
|
|
140
151
|
enabled: boolean;
|
|
141
152
|
defaultDays?: number;
|
|
142
153
|
};
|
|
154
|
+
/** Resolved IANA zone for wall-clock rule evaluation; `undefined` = UTC. */
|
|
155
|
+
timezone?: string;
|
|
143
156
|
tenant: ResolvedTenant;
|
|
144
157
|
}
|
|
145
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 };
|
|
@@ -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
|
|
|
@@ -159,7 +159,19 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
159
159
|
action: string;
|
|
160
160
|
referenceId?: string;
|
|
161
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
|
+
*/
|
|
162
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;
|
|
163
175
|
private calculateOrderPoints;
|
|
164
176
|
private calculateCategoryPoints;
|
|
165
177
|
private applyRounding;
|
|
@@ -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;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-
|
|
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
7
|
import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
|
|
8
8
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
9
9
|
import { HttpError } from "@classytic/repo-core/errors";
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { n as DEFAULT_TENANT_FIELD, r as DEFAULT_TENANT_TYPE } from "./constants-DkQYkOK1.mjs";
|
|
2
|
-
import { C as TenantIsolationError, E as ValidationError, S as SelfReferralError, T as UnmanagedSessionError, _ as RedemptionExpiredError, a as PointTransactionRepository, b as ReferralNotFoundError, c as flushPendingLoyaltyEvents, d as DuplicateReferralError, f as InsufficientPointsError, g as MemberNotFoundError, h as MemberInactiveError, i as RedemptionRepository, l as EarningRuleRepository, m as MemberAlreadyEnrolledError, n as TierDefinitionRepository, o as MemberRepository, p as LoyaltyCapabilityError, r as ReferralRepository, s as PENDING_EVENTS, t as createRepositories, u as CircularReferralError, v as RedemptionNotFoundError, w as TierNotFoundError, x as RuleNotFoundError, y as ReferralLimitExceededError } from "./repositories-
|
|
2
|
+
import { C as TenantIsolationError, E as ValidationError, S as SelfReferralError, T as UnmanagedSessionError, _ as RedemptionExpiredError, a as PointTransactionRepository, b as ReferralNotFoundError, c as flushPendingLoyaltyEvents, d as DuplicateReferralError, f as InsufficientPointsError, g as MemberNotFoundError, h as MemberInactiveError, i as RedemptionRepository, l as EarningRuleRepository, m as MemberAlreadyEnrolledError, n as TierDefinitionRepository, o as MemberRepository, p as LoyaltyCapabilityError, r as ReferralRepository, s as PENDING_EVENTS, t as createRepositories, u as CircularReferralError, v as RedemptionNotFoundError, w as TierNotFoundError, x as RuleNotFoundError, y as ReferralLimitExceededError } from "./repositories-CjWfUlrA.mjs";
|
|
3
3
|
import { E as InProcessLoyaltyBus, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "./events-DN5_p6T0.mjs";
|
|
4
4
|
import { n as createModels } from "./models-Ddh62lgO.mjs";
|
|
5
5
|
import { n as createEvent, t as LoyaltyEvents } from "./loyalty-events-C38JA3cU.mjs";
|
|
6
|
+
import { withTransaction } from "@classytic/mongokit";
|
|
7
|
+
import { isValidTimeZone } from "@classytic/primitives/timezone";
|
|
6
8
|
//#region src/index.ts
|
|
7
9
|
function resolveConfig(config) {
|
|
8
10
|
let tenant;
|
|
@@ -48,36 +50,28 @@ function resolveConfig(config) {
|
|
|
48
50
|
enabled: config.expiry?.enabled ?? false,
|
|
49
51
|
...config.expiry?.defaultDays !== void 0 ? { defaultDays: config.expiry.defaultDays } : {}
|
|
50
52
|
},
|
|
53
|
+
...config.timezone !== void 0 ? { timezone: config.timezone } : {},
|
|
51
54
|
tenant
|
|
52
55
|
};
|
|
56
|
+
if (config.timezone !== void 0 && !isValidTimeZone(config.timezone)) throw new ValidationError(`Invalid IANA timezone: '${config.timezone}'`);
|
|
53
57
|
if (resolved.conversionRate <= 0) throw new ValidationError("conversionRate must be positive");
|
|
54
58
|
if (resolved.referral.codeLength < 4) throw new ValidationError("referral codeLength must be >= 4");
|
|
55
59
|
if (resolved.redemption.maxRedeemPercent < 0 || resolved.redemption.maxRedeemPercent > 100) throw new ValidationError("maxRedeemPercent must be between 0 and 100");
|
|
56
60
|
return resolved;
|
|
57
61
|
}
|
|
58
62
|
var MongoUnitOfWork = class {
|
|
59
|
-
constructor(connection) {
|
|
63
|
+
constructor(connection, allowFallback) {
|
|
60
64
|
this.connection = connection;
|
|
65
|
+
this.allowFallback = allowFallback;
|
|
61
66
|
}
|
|
62
67
|
async withTransaction(cb) {
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const slot = { assigned: false };
|
|
66
|
-
await session.withTransaction(async () => {
|
|
67
|
-
slot.value = await cb(session);
|
|
68
|
-
slot.assigned = true;
|
|
69
|
-
});
|
|
70
|
-
if (!slot.assigned) throw new Error("withTransaction: callback did not assign a result");
|
|
71
|
-
return slot.value;
|
|
72
|
-
} finally {
|
|
73
|
-
await session.endSession();
|
|
74
|
-
}
|
|
68
|
+
return withTransaction(this.connection, cb, this.allowFallback ? { allowFallback: true } : {});
|
|
75
69
|
}
|
|
76
70
|
};
|
|
77
71
|
function createLoyaltyEngine(config) {
|
|
78
72
|
const resolvedConfig = resolveConfig(config);
|
|
79
73
|
const models = createModels(config.mongoose, resolvedConfig.tenant, config.indexes, config.autoIndex, config.collectionPrefix, config.forceRecreate);
|
|
80
|
-
const unitOfWork = new MongoUnitOfWork(config.mongoose);
|
|
74
|
+
const unitOfWork = new MongoUnitOfWork(config.mongoose, config.allowNonTransactional ?? false);
|
|
81
75
|
const events = config.eventTransport ?? new InProcessLoyaltyBus();
|
|
82
76
|
const repositories = createRepositories({
|
|
83
77
|
models,
|
package/dist/models/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as ResolvedTenant, i as ModelName, r as LoyaltyModelName, t as IndexDefinition } from "../config-
|
|
1
|
+
import { c as ResolvedTenant, i as ModelName, r as LoyaltyModelName, t as IndexDefinition } from "../config-Dtun1duw.mjs";
|
|
2
2
|
import * as _$mongoose from "mongoose";
|
|
3
3
|
import { Connection, Model } from "mongoose";
|
|
4
4
|
|
|
@@ -86,9 +86,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
86
86
|
_id: _$mongoose.Types.ObjectId;
|
|
87
87
|
} & {
|
|
88
88
|
__v: number;
|
|
89
|
-
}, "id"> & {
|
|
89
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
90
90
|
id: string;
|
|
91
|
-
}
|
|
91
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
92
92
|
timestamps: true;
|
|
93
93
|
}, {
|
|
94
94
|
programId: string;
|
|
@@ -167,9 +167,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
167
167
|
_id: _$mongoose.Types.ObjectId;
|
|
168
168
|
} & {
|
|
169
169
|
__v: number;
|
|
170
|
-
}, "id"> & {
|
|
170
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
171
171
|
id: string;
|
|
172
|
-
}
|
|
172
|
+
}>, unknown, {
|
|
173
173
|
programId: string;
|
|
174
174
|
externalId: string;
|
|
175
175
|
externalType: string;
|
|
@@ -298,9 +298,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
298
298
|
_id: _$mongoose.Types.ObjectId;
|
|
299
299
|
} & {
|
|
300
300
|
__v: number;
|
|
301
|
-
}, "id"> & {
|
|
301
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
302
302
|
id: string;
|
|
303
|
-
}
|
|
303
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
304
304
|
timestamps: {
|
|
305
305
|
createdAt: true;
|
|
306
306
|
updatedAt: false;
|
|
@@ -373,9 +373,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
373
373
|
_id: _$mongoose.Types.ObjectId;
|
|
374
374
|
} & {
|
|
375
375
|
__v: number;
|
|
376
|
-
}, "id"> & {
|
|
376
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
377
377
|
id: string;
|
|
378
|
-
}
|
|
378
|
+
}>, unknown, {
|
|
379
379
|
programId: string;
|
|
380
380
|
type: "earn" | "redeem" | "adjust" | "expire" | "transfer" | "referral_bonus";
|
|
381
381
|
description: string;
|
|
@@ -519,9 +519,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
519
519
|
_id: _$mongoose.Types.ObjectId;
|
|
520
520
|
} & {
|
|
521
521
|
__v: number;
|
|
522
|
-
}, "id"> & {
|
|
522
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
523
523
|
id: string;
|
|
524
|
-
}
|
|
524
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
525
525
|
timestamps: true;
|
|
526
526
|
}, {
|
|
527
527
|
programId: string;
|
|
@@ -618,9 +618,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
618
618
|
_id: _$mongoose.Types.ObjectId;
|
|
619
619
|
} & {
|
|
620
620
|
__v: number;
|
|
621
|
-
}, "id"> & {
|
|
621
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
622
622
|
id: string;
|
|
623
|
-
}
|
|
623
|
+
}>, unknown, {
|
|
624
624
|
programId: string;
|
|
625
625
|
type: "order" | "action" | "category" | "tier_bonus";
|
|
626
626
|
name: string;
|
|
@@ -764,9 +764,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
764
764
|
_id: _$mongoose.Types.ObjectId;
|
|
765
765
|
} & {
|
|
766
766
|
__v: number;
|
|
767
|
-
}, "id"> & {
|
|
767
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
768
768
|
id: string;
|
|
769
|
-
}
|
|
769
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
770
770
|
timestamps: true;
|
|
771
771
|
}, {
|
|
772
772
|
programId: string;
|
|
@@ -839,9 +839,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
839
839
|
_id: _$mongoose.Types.ObjectId;
|
|
840
840
|
} & {
|
|
841
841
|
__v: number;
|
|
842
|
-
}, "id"> & {
|
|
842
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
843
843
|
id: string;
|
|
844
|
-
}
|
|
844
|
+
}>, unknown, {
|
|
845
845
|
programId: string;
|
|
846
846
|
name: string;
|
|
847
847
|
rank: number;
|
|
@@ -972,9 +972,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
972
972
|
_id: _$mongoose.Types.ObjectId;
|
|
973
973
|
} & {
|
|
974
974
|
__v: number;
|
|
975
|
-
}, "id"> & {
|
|
975
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
976
976
|
id: string;
|
|
977
|
-
}
|
|
977
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
978
978
|
timestamps: true;
|
|
979
979
|
}, {
|
|
980
980
|
programId: string;
|
|
@@ -1050,9 +1050,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
1050
1050
|
_id: _$mongoose.Types.ObjectId;
|
|
1051
1051
|
} & {
|
|
1052
1052
|
__v: number;
|
|
1053
|
-
}, "id"> & {
|
|
1053
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
1054
1054
|
id: string;
|
|
1055
|
-
}
|
|
1055
|
+
}>, unknown, {
|
|
1056
1056
|
programId: string;
|
|
1057
1057
|
status: "expired" | "reserved" | "confirmed" | "released" | "reversed";
|
|
1058
1058
|
memberId: _$mongoose.Types.ObjectId;
|
|
@@ -1164,9 +1164,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
1164
1164
|
_id: _$mongoose.Types.ObjectId;
|
|
1165
1165
|
} & {
|
|
1166
1166
|
__v: number;
|
|
1167
|
-
}, "id"> & {
|
|
1167
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
1168
1168
|
id: string;
|
|
1169
|
-
}
|
|
1169
|
+
}>, _$mongoose.Schema<any, Model<any, any, any, any, any, any, any>, {}, {}, {}, {}, {
|
|
1170
1170
|
timestamps: true;
|
|
1171
1171
|
}, {
|
|
1172
1172
|
programId: string;
|
|
@@ -1221,9 +1221,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
|
|
|
1221
1221
|
_id: _$mongoose.Types.ObjectId;
|
|
1222
1222
|
} & {
|
|
1223
1223
|
__v: number;
|
|
1224
|
-
}, "id"> & {
|
|
1224
|
+
}, "id"> & _$mongoose.HydratedDocumentOverrides<{
|
|
1225
1225
|
id: string;
|
|
1226
|
-
}
|
|
1226
|
+
}>, unknown, {
|
|
1227
1227
|
programId: string;
|
|
1228
1228
|
status: "expired" | "pending" | "approved" | "rejected";
|
|
1229
1229
|
referralCode: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ReferralRepository, d as MemberRepository, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, r as createRepositories, s as RedemptionRepository, t as CreateRepositoriesInput, u as PointTransactionRepository } from "../index-
|
|
1
|
+
import { a as ReferralRepository, d as MemberRepository, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, r as createRepositories, s as RedemptionRepository, t as CreateRepositoriesInput, u as PointTransactionRepository } from "../index-Dngaj21_.mjs";
|
|
2
2
|
export { CreateRepositoriesInput, EarningRuleRepository, LoyaltyRepositories, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as PointTransactionRepository, i as RedemptionRepository, l as EarningRuleRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, t as createRepositories } from "../repositories-
|
|
1
|
+
import { a as PointTransactionRepository, i as RedemptionRepository, l as EarningRuleRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, t as createRepositories } from "../repositories-CjWfUlrA.mjs";
|
|
2
2
|
export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { n as createEvent, t as LoyaltyEvents } from "./loyalty-events-C38JA3cU.mjs";
|
|
2
|
-
import { Repository, multiTenantPlugin } from "@classytic/mongokit";
|
|
2
|
+
import { Repository, appendOnlyPlugin, multiTenantPlugin } from "@classytic/mongokit";
|
|
3
|
+
import { localTimeParts } from "@classytic/primitives/timezone";
|
|
4
|
+
import { addDays, isoWeekday } from "@classytic/primitives/calendar";
|
|
3
5
|
import { randomBytes } from "node:crypto";
|
|
4
6
|
import { throwIfAborted } from "@classytic/repo-core/repository";
|
|
5
7
|
//#region src/domain/errors/base.ts
|
|
@@ -333,6 +335,7 @@ var EarningRuleRepository = class extends Repository {
|
|
|
333
335
|
const transactions = [];
|
|
334
336
|
let totalPoints = 0;
|
|
335
337
|
for (const rule of actionRules) {
|
|
338
|
+
if (!this.matchesConditions(rule, member)) continue;
|
|
336
339
|
const points = rule.reward.fixedPoints ?? 0;
|
|
337
340
|
if (points <= 0) continue;
|
|
338
341
|
const tx = await this.txRepo.earnPoints({
|
|
@@ -341,7 +344,10 @@ var EarningRuleRepository = class extends Repository {
|
|
|
341
344
|
description: `Action: ${input.action} (${rule.name})`,
|
|
342
345
|
referenceType: "action",
|
|
343
346
|
earningRuleId: rule._id,
|
|
344
|
-
...input.referenceId !== void 0 ? {
|
|
347
|
+
...input.referenceId !== void 0 ? {
|
|
348
|
+
referenceId: input.referenceId,
|
|
349
|
+
idempotencyKey: `${input.referenceId}:${rule._id}`
|
|
350
|
+
} : {}
|
|
345
351
|
}, ctx);
|
|
346
352
|
totalPoints += points;
|
|
347
353
|
breakdown.push({
|
|
@@ -357,10 +363,14 @@ var EarningRuleRepository = class extends Repository {
|
|
|
357
363
|
transactions
|
|
358
364
|
};
|
|
359
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* `orderTotal` is optional — action-type evaluations have no order, so
|
|
368
|
+
* `minOrderAmount` only applies when a total is supplied (order paths).
|
|
369
|
+
*/
|
|
360
370
|
matchesConditions(rule, member, orderTotal) {
|
|
361
371
|
const { conditions } = rule;
|
|
362
372
|
const now = /* @__PURE__ */ new Date();
|
|
363
|
-
if (conditions.minOrderAmount && orderTotal < conditions.minOrderAmount) return false;
|
|
373
|
+
if (conditions.minOrderAmount && orderTotal !== void 0 && orderTotal < conditions.minOrderAmount) return false;
|
|
364
374
|
if (conditions.tiers && conditions.tiers.length > 0) {
|
|
365
375
|
const memberTier = member.tierOverride ?? member.tier;
|
|
366
376
|
if (!memberTier || !conditions.tiers.includes(memberTier)) return false;
|
|
@@ -370,10 +380,20 @@ var EarningRuleRepository = class extends Repository {
|
|
|
370
380
|
if (conditions.dateRange.end && now > conditions.dateRange.end) return false;
|
|
371
381
|
}
|
|
372
382
|
if (conditions.dayOfWeek && conditions.dayOfWeek.length > 0) {
|
|
373
|
-
if (!conditions.dayOfWeek.includes(
|
|
383
|
+
if (!conditions.dayOfWeek.includes(this.weekday(now))) return false;
|
|
374
384
|
}
|
|
375
385
|
return true;
|
|
376
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* The wall-clock weekday of `instant` in JS `getDay()` convention
|
|
389
|
+
* (0=Sun … 6=Sat — what `EarningConditions.dayOfWeek` is authored in),
|
|
390
|
+
* evaluated in the engine's configured `timezone` (DST-exact) or UTC when
|
|
391
|
+
* unset. NEVER server-local `Date.getDay()`, which shifts with the deploy
|
|
392
|
+
* machine's `TZ` env (PACKAGE_RULES P12).
|
|
393
|
+
*/
|
|
394
|
+
weekday(instant) {
|
|
395
|
+
return (this.config.timezone ? localTimeParts(instant, this.config.timezone).isoWeekday : isoWeekday(instant)) % 7;
|
|
396
|
+
}
|
|
377
397
|
calculateOrderPoints(rule, orderTotal) {
|
|
378
398
|
const { reward } = rule;
|
|
379
399
|
let points = 0;
|
|
@@ -410,7 +430,6 @@ var EarningRuleRepository = class extends Repository {
|
|
|
410
430
|
const PENDING_EVENTS = Symbol.for("@classytic/loyalty:pendingEvents");
|
|
411
431
|
async function dispatchLoyaltyEvent(deps, event, ctx) {
|
|
412
432
|
const pending = ctx[PENDING_EVENTS];
|
|
413
|
-
if (ctx.session && !pending) throw new UnmanagedSessionError();
|
|
414
433
|
if (deps.outbox) try {
|
|
415
434
|
const saveOptions = {};
|
|
416
435
|
if (ctx.session !== void 0) saveOptions.session = ctx.session;
|
|
@@ -419,11 +438,12 @@ async function dispatchLoyaltyEvent(deps, event, ctx) {
|
|
|
419
438
|
deps.logger?.error(`[loyalty] outbox.save failed for ${event.type}:`, err);
|
|
420
439
|
throw err;
|
|
421
440
|
}
|
|
441
|
+
else if (ctx.session && !pending) throw new UnmanagedSessionError();
|
|
422
442
|
if (pending) {
|
|
423
443
|
pending.push(event);
|
|
424
444
|
return;
|
|
425
445
|
}
|
|
426
|
-
if (deps.events) try {
|
|
446
|
+
if (deps.events && !ctx.session) try {
|
|
427
447
|
await deps.events.publish(event);
|
|
428
448
|
} catch (err) {
|
|
429
449
|
deps.logger?.error(`[loyalty] events.publish failed for ${event.type}:`, err);
|
|
@@ -689,7 +709,7 @@ var PointTransactionRepository = class extends Repository {
|
|
|
689
709
|
unitOfWork;
|
|
690
710
|
config;
|
|
691
711
|
constructor(model, plugins = []) {
|
|
692
|
-
super(model, plugins);
|
|
712
|
+
super(model, [...plugins, appendOnlyPlugin()]);
|
|
693
713
|
}
|
|
694
714
|
/** Session-bound outbox save + transport publish (PACKAGE_RULES §5.5 + P8). */
|
|
695
715
|
async _dispatch(event, ctx) {
|
|
@@ -981,7 +1001,8 @@ var PointTransactionRepository = class extends Repository {
|
|
|
981
1001
|
});
|
|
982
1002
|
await this.update(tx._id, { expiredAt: now }, {
|
|
983
1003
|
session,
|
|
984
|
-
...tc
|
|
1004
|
+
...tc,
|
|
1005
|
+
bypassAppendOnly: true
|
|
985
1006
|
});
|
|
986
1007
|
await this.memberRepo.updateBalance(tx.memberId, {
|
|
987
1008
|
current: -tx.points,
|
|
@@ -1524,8 +1545,7 @@ var ReferralRepository = class extends Repository {
|
|
|
1524
1545
|
...tc
|
|
1525
1546
|
});
|
|
1526
1547
|
if (reverseReferral && String(reverseReferral.referrerId) === String(referee._id)) throw new CircularReferralError();
|
|
1527
|
-
const periodStart = /* @__PURE__ */ new Date();
|
|
1528
|
-
periodStart.setDate(periodStart.getDate() - this.config.referral.periodDays);
|
|
1548
|
+
const periodStart = addDays(/* @__PURE__ */ new Date(), -this.config.referral.periodDays);
|
|
1529
1549
|
if (await this.count({
|
|
1530
1550
|
referrerId: referrer._id,
|
|
1531
1551
|
createdAt: { $gte: periodStart }
|
|
@@ -1956,8 +1976,7 @@ var TierDefinitionRepository = class extends Repository {
|
|
|
1956
1976
|
action: "unchanged"
|
|
1957
1977
|
};
|
|
1958
1978
|
if (downgrade.gracePeriodDays > 0 && !member.tierExpiresAt) {
|
|
1959
|
-
const graceExpiresAt = /* @__PURE__ */ new Date();
|
|
1960
|
-
graceExpiresAt.setDate(graceExpiresAt.getDate() + downgrade.gracePeriodDays);
|
|
1979
|
+
const graceExpiresAt = addDays(/* @__PURE__ */ new Date(), downgrade.gracePeriodDays);
|
|
1961
1980
|
await this.memberRepo.update(memberId, { tierExpiresAt: graceExpiresAt }, {
|
|
1962
1981
|
lean: true,
|
|
1963
1982
|
...tc
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as TierEvaluationBatch, a as EnrollInput, c as UpdateTierInput, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, m as PaginatedResult, n as CreateEarningRuleInput, o as LoyaltyContext, p as ListQuery, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, u as EarningBreakdown } from "../index-
|
|
2
|
-
import { a as PluginType, c as ResolvedTenant, i as ModelName, l as TenantConfig, n as LoyaltyConfig, o as RepositoryPlugins, s as ResolvedConfig, t as IndexDefinition } from "../config-
|
|
1
|
+
import { _ as TierEvaluationBatch, a as EnrollInput, c as UpdateTierInput, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, m as PaginatedResult, n as CreateEarningRuleInput, o as LoyaltyContext, p as ListQuery, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, u as EarningBreakdown } from "../index-YSsi4e_G.mjs";
|
|
2
|
+
import { a as PluginType, c as ResolvedTenant, i as ModelName, l as TenantConfig, n as LoyaltyConfig, o as RepositoryPlugins, s as ResolvedConfig, t as IndexDefinition } from "../config-Dtun1duw.mjs";
|
|
3
3
|
export { AdjustPointsInput, BalanceSummary, CreateEarningRuleInput, CreateTierInput, EarnPointsInput, EarningBreakdown, EarningResult, EnrollInput, ExpirationResult, IndexDefinition, ListQuery, LoyaltyConfig, LoyaltyContext, ModelName, PaginatedResult, PluginType, RedemptionValidation, RepositoryPlugins, ResolvedConfig, ResolvedTenant, TenantConfig, TierEvaluation, TierEvaluationBatch, UpdateEarningRuleInput, UpdateTierInput };
|
package/package.json
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@classytic/loyalty",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Loyalty points, tiers, earning rules, referrals, and redemption engine for MongoDB",
|
|
5
|
-
"author": "Classytic",
|
|
6
|
-
"homepage": "https://www.npmjs.com/package/@classytic/loyalty",
|
|
7
|
-
"bugs": {
|
|
8
|
-
"url": "https://github.com/classytic/loyalty/issues"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/classytic/loyalty.git"
|
|
13
|
-
},
|
|
14
|
-
"publishConfig": {
|
|
15
|
-
"access": "public"
|
|
16
|
-
},
|
|
17
|
-
"type": "module",
|
|
18
|
-
"sideEffects": false,
|
|
19
|
-
"engines": {
|
|
20
|
-
"node": ">=22"
|
|
21
|
-
},
|
|
22
|
-
"exports": {
|
|
23
|
-
".": {
|
|
24
|
-
"types": "./dist/index.d.mts",
|
|
25
|
-
"default": "./dist/index.mjs"
|
|
26
|
-
},
|
|
27
|
-
"./domain/enums": {
|
|
28
|
-
"types": "./dist/domain/enums/index.d.mts",
|
|
29
|
-
"default": "./dist/domain/enums/index.mjs"
|
|
30
|
-
},
|
|
31
|
-
"./models": {
|
|
32
|
-
"types": "./dist/models/index.d.mts",
|
|
33
|
-
"default": "./dist/models/index.mjs"
|
|
34
|
-
},
|
|
35
|
-
"./repositories": {
|
|
36
|
-
"types": "./dist/repositories/index.d.mts",
|
|
37
|
-
"default": "./dist/repositories/index.mjs"
|
|
38
|
-
},
|
|
39
|
-
"./events": {
|
|
40
|
-
"types": "./dist/events/index.d.mts",
|
|
41
|
-
"default": "./dist/events/index.mjs"
|
|
42
|
-
},
|
|
43
|
-
"./types": {
|
|
44
|
-
"types": "./dist/types/index.d.mts",
|
|
45
|
-
"default": "./dist/types/index.mjs"
|
|
46
|
-
},
|
|
47
|
-
"./notifications": {
|
|
48
|
-
"types": "./dist/notifications/index.d.mts",
|
|
49
|
-
"default": "./dist/notifications/index.mjs"
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
"main": "./dist/index.mjs",
|
|
53
|
-
"types": "./dist/index.d.mts",
|
|
54
|
-
"files": [
|
|
55
|
-
"dist",
|
|
56
|
-
"README.md",
|
|
57
|
-
"CHANGELOG.md",
|
|
58
|
-
"LICENSE"
|
|
59
|
-
],
|
|
60
|
-
"scripts": {
|
|
61
|
-
"build": "tsdown",
|
|
62
|
-
"dev": "tsdown --watch",
|
|
63
|
-
"test": "vitest run --project unit --project integration",
|
|
64
|
-
"test:unit": "vitest run --project unit",
|
|
65
|
-
"test:integration": "vitest run --project integration",
|
|
66
|
-
"test:scenarios": "vitest run --project scenarios",
|
|
67
|
-
"test:all": "vitest run",
|
|
68
|
-
"test:watch": "vitest --project unit --project integration",
|
|
69
|
-
"test:coverage": "vitest run --coverage",
|
|
70
|
-
"typecheck": "tsc --noEmit",
|
|
71
|
-
"lint": "biome check src/ tests/",
|
|
72
|
-
"lint:fix": "biome check --write src/ tests/",
|
|
73
|
-
"format": "biome format --write src/ tests/",
|
|
74
|
-
"check": "biome check --write --unsafe src/ tests/",
|
|
75
|
-
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build",
|
|
76
|
-
"push": "classytic-push",
|
|
77
|
-
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
78
|
-
"release": "npm run push -- main && npm run release:tag && npm publish"
|
|
79
|
-
},
|
|
80
|
-
"peerDependencies": {
|
|
81
|
-
"@classytic/mongokit": ">=3.
|
|
82
|
-
"@classytic/primitives": ">=0.
|
|
83
|
-
"@classytic/repo-core": ">=0.
|
|
84
|
-
"mongoose": ">=9.4.1",
|
|
85
|
-
"zod": ">=4.0.0"
|
|
86
|
-
},
|
|
87
|
-
"devDependencies": {
|
|
88
|
-
"@biomejs/biome": "^2.4.9",
|
|
89
|
-
"@classytic/dev-tools": "^0.2.0",
|
|
90
|
-
"@classytic/mongokit": "^3.
|
|
91
|
-
"@classytic/primitives": "^0.
|
|
92
|
-
"@classytic/repo-core": "^0.
|
|
93
|
-
"@types/node": "^25.5.0",
|
|
94
|
-
"@vitest/coverage-v8": "^3.2.4",
|
|
95
|
-
"knip": "^6.3.0",
|
|
96
|
-
"mongodb-memory-server": "^10.2.3",
|
|
97
|
-
"mongoose": "^9.4.1",
|
|
98
|
-
"tsdown": "^0.21.5",
|
|
99
|
-
"typescript": "^5.7.0",
|
|
100
|
-
"vitest": "^3.0.0",
|
|
101
|
-
"zod": "^4.3.6"
|
|
102
|
-
},
|
|
103
|
-
"keywords": [
|
|
104
|
-
"loyalty",
|
|
105
|
-
"points",
|
|
106
|
-
"rewards",
|
|
107
|
-
"tiers",
|
|
108
|
-
"referral",
|
|
109
|
-
"membership",
|
|
110
|
-
"earning-rules",
|
|
111
|
-
"redemption",
|
|
112
|
-
"mongodb",
|
|
113
|
-
"mongoose"
|
|
114
|
-
],
|
|
115
|
-
"license": "MIT"
|
|
116
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@classytic/loyalty",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Loyalty points, tiers, earning rules, referrals, and redemption engine for MongoDB",
|
|
5
|
+
"author": "Classytic",
|
|
6
|
+
"homepage": "https://www.npmjs.com/package/@classytic/loyalty",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/classytic/loyalty/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/classytic/loyalty.git"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"default": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./domain/enums": {
|
|
28
|
+
"types": "./dist/domain/enums/index.d.mts",
|
|
29
|
+
"default": "./dist/domain/enums/index.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./models": {
|
|
32
|
+
"types": "./dist/models/index.d.mts",
|
|
33
|
+
"default": "./dist/models/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./repositories": {
|
|
36
|
+
"types": "./dist/repositories/index.d.mts",
|
|
37
|
+
"default": "./dist/repositories/index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./events": {
|
|
40
|
+
"types": "./dist/events/index.d.mts",
|
|
41
|
+
"default": "./dist/events/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./types": {
|
|
44
|
+
"types": "./dist/types/index.d.mts",
|
|
45
|
+
"default": "./dist/types/index.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./notifications": {
|
|
48
|
+
"types": "./dist/notifications/index.d.mts",
|
|
49
|
+
"default": "./dist/notifications/index.mjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"main": "./dist/index.mjs",
|
|
53
|
+
"types": "./dist/index.d.mts",
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"README.md",
|
|
57
|
+
"CHANGELOG.md",
|
|
58
|
+
"LICENSE"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"dev": "tsdown --watch",
|
|
63
|
+
"test": "vitest run --project unit --project integration",
|
|
64
|
+
"test:unit": "vitest run --project unit",
|
|
65
|
+
"test:integration": "vitest run --project integration",
|
|
66
|
+
"test:scenarios": "vitest run --project scenarios",
|
|
67
|
+
"test:all": "vitest run",
|
|
68
|
+
"test:watch": "vitest --project unit --project integration",
|
|
69
|
+
"test:coverage": "vitest run --coverage",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"lint": "biome check src/ tests/",
|
|
72
|
+
"lint:fix": "biome check --write src/ tests/",
|
|
73
|
+
"format": "biome format --write src/ tests/",
|
|
74
|
+
"check": "biome check --write --unsafe src/ tests/",
|
|
75
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build",
|
|
76
|
+
"push": "classytic-push",
|
|
77
|
+
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
78
|
+
"release": "npm run push -- main && npm run release:tag && npm publish"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@classytic/mongokit": ">=3.22.0",
|
|
82
|
+
"@classytic/primitives": ">=0.11.0",
|
|
83
|
+
"@classytic/repo-core": ">=0.13.0",
|
|
84
|
+
"mongoose": ">=9.4.1",
|
|
85
|
+
"zod": ">=4.0.0"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@biomejs/biome": "^2.4.9",
|
|
89
|
+
"@classytic/dev-tools": "^0.2.0",
|
|
90
|
+
"@classytic/mongokit": "^3.22.0",
|
|
91
|
+
"@classytic/primitives": "^0.11.0",
|
|
92
|
+
"@classytic/repo-core": "^0.13.0",
|
|
93
|
+
"@types/node": "^25.5.0",
|
|
94
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
95
|
+
"knip": "^6.3.0",
|
|
96
|
+
"mongodb-memory-server": "^10.2.3",
|
|
97
|
+
"mongoose": "^9.4.1",
|
|
98
|
+
"tsdown": "^0.21.5",
|
|
99
|
+
"typescript": "^5.7.0",
|
|
100
|
+
"vitest": "^3.0.0",
|
|
101
|
+
"zod": "^4.3.6"
|
|
102
|
+
},
|
|
103
|
+
"keywords": [
|
|
104
|
+
"loyalty",
|
|
105
|
+
"points",
|
|
106
|
+
"rewards",
|
|
107
|
+
"tiers",
|
|
108
|
+
"referral",
|
|
109
|
+
"membership",
|
|
110
|
+
"earning-rules",
|
|
111
|
+
"redemption",
|
|
112
|
+
"mongodb",
|
|
113
|
+
"mongoose"
|
|
114
|
+
],
|
|
115
|
+
"license": "MIT"
|
|
116
|
+
}
|