@classytic/loyalty 0.6.0 → 0.7.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 +46 -0
- package/dist/{index-CR2k7jQ8.d.mts → index-BIPHUsfR.d.mts} +12 -1
- package/dist/{index-D6GvuvmF.d.mts → index-BIhVuwIZ.d.mts} +44 -44
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +5 -1
- package/dist/models/index.d.mts +1 -1
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-B676z4bE.mjs → repositories-DErtHQou.mjs} +17 -3
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,52 @@
|
|
|
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.7.0
|
|
7
|
+
|
|
8
|
+
### Fixed — a `UnitOfWork` yielding a non-transactional session ran the body anyway
|
|
9
|
+
|
|
10
|
+
`runLoyaltyUnit` asserted `inTransaction()` on the HOST-session branch and not on
|
|
11
|
+
the `unitOfWork.withTransaction` branch, so only one of the two ways a body gets
|
|
12
|
+
a session was guarded. The comment above that check already stated the rule —
|
|
13
|
+
"guard before the body runs, not at the first emit: a verb can mutate before it
|
|
14
|
+
dispatches" — and it was applied to one branch.
|
|
15
|
+
|
|
16
|
+
It matters because the balance verbs compensate by THROWING to roll an `$inc`
|
|
17
|
+
back. Without a real transaction that buys nothing: `redeem` decrements a
|
|
18
|
+
member's points, sees the balance go negative, throws — and the points stay
|
|
19
|
+
gone while the caller is told the redemption failed. A lying `withTransaction`
|
|
20
|
+
was previously caught at the first event DISPATCH, by which time the write had
|
|
21
|
+
landed.
|
|
22
|
+
|
|
23
|
+
3 cases in `tests/unit/unit-of-work-transaction-guard.test.ts`, asserting the
|
|
24
|
+
body did NOT run — the rejection alone would pass even if it had mutated first.
|
|
25
|
+
Falsified: removing the guard fails 2 of 3.
|
|
26
|
+
|
|
27
|
+
### Fixed — the transaction guard must not override `allowNonTransactional`
|
|
28
|
+
|
|
29
|
+
The fix above asserted `inTransaction()` unconditionally, which also made it
|
|
30
|
+
wrong: `allowNonTransactional` is an explicit host opt-in (loyalty already
|
|
31
|
+
warns about it at boot) for a standalone-Mongo deployment where
|
|
32
|
+
`withTransaction({ allowFallback })` runs the callback with no transaction ON
|
|
33
|
+
PURPOSE. The guard turned that documented degradation into a hard failure, so
|
|
34
|
+
a standalone-Mongo host lost point earn / redemption / reversal entirely — a
|
|
35
|
+
general rule silently defeating a specific decision, the inverse of what the
|
|
36
|
+
guard exists to catch.
|
|
37
|
+
|
|
38
|
+
`UnitOfWork` now states whether it may degrade, and the consent travels on the
|
|
39
|
+
internal context because the same guard also runs from `dispatchLoyaltyEvent`,
|
|
40
|
+
which never sees the unit of work. Absent still means no: an implementation
|
|
41
|
+
that yields a bare session without saying so is still refused, and a
|
|
42
|
+
host-supplied `ctx.session` has no opt-in and always asserts. Falsified both
|
|
43
|
+
ways in `unit-of-work-transaction-guard.test.ts`.
|
|
44
|
+
|
|
45
|
+
### Changed — peer floors raised to current
|
|
46
|
+
|
|
47
|
+
- `@classytic/mongokit` `>=3.33.0` → **`>=3.38.1`**
|
|
48
|
+
- `@classytic/primitives` `>=0.23.0` → **`>=0.27.0`**
|
|
49
|
+
- `@classytic/repo-core` `>=0.24.0` → **`>=0.27.1`**
|
|
50
|
+
- `@classytic/validation` `>=0.2.0` → **`>=0.5.0`**
|
|
51
|
+
|
|
6
52
|
## [0.6.0] — 2026-08-17
|
|
7
53
|
|
|
8
54
|
### Added
|
|
@@ -2,7 +2,7 @@ import { i as ReferralStatus, r as RedemptionStatus } from "./index-D_Oe3aDk.mjs
|
|
|
2
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-Cp_sXNpG.mjs";
|
|
3
3
|
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
4
4
|
import { c as ResolvedConfig, s as RepositoryPlugins } from "./config-CtkGj3xZ.mjs";
|
|
5
|
-
import { n as LoyaltyModels } from "./index-
|
|
5
|
+
import { n as LoyaltyModels } from "./index-BIhVuwIZ.mjs";
|
|
6
6
|
import { PluginType, Repository } from "@classytic/mongokit";
|
|
7
7
|
import { ResolvedTenantConfig } from "@classytic/repo-core/tenant";
|
|
8
8
|
import { ClientSession, Model } from "mongoose";
|
|
@@ -12,6 +12,17 @@ import { ApprovalChain } from "@classytic/primitives/approval";
|
|
|
12
12
|
//#region src/domain/ports/unit-of-work.port.d.ts
|
|
13
13
|
interface UnitOfWork {
|
|
14
14
|
withTransaction<T>(callback: (session: ClientSession) => Promise<T>): Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* This unit of work MAY hand the callback a session with no live transaction.
|
|
17
|
+
*
|
|
18
|
+
* True only where the host opted into `allowNonTransactional` — a standalone
|
|
19
|
+
* mongod accepts the degradation knowingly, and loyalty logs it at boot. The
|
|
20
|
+
* guard that otherwise refuses a transaction-less session reads this, so an
|
|
21
|
+
* accepted degradation is not re-refused one layer down. Absent means NO:
|
|
22
|
+
* an implementation that yields a bare session without saying so is a defect,
|
|
23
|
+
* and stays one.
|
|
24
|
+
*/
|
|
25
|
+
readonly allowsNonTransactional?: boolean;
|
|
15
26
|
}
|
|
16
27
|
//#endregion
|
|
17
28
|
//#region src/repositories/member.repository.d.ts
|
|
@@ -11,6 +11,7 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
11
11
|
name: string;
|
|
12
12
|
status: "active" | "paused" | "expired";
|
|
13
13
|
priority: number;
|
|
14
|
+
metadata?: any;
|
|
14
15
|
description?: string | null;
|
|
15
16
|
conditions?: {
|
|
16
17
|
categories: string[];
|
|
@@ -34,13 +35,13 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
34
35
|
} | null;
|
|
35
36
|
startsAt?: NativeDate | null;
|
|
36
37
|
endsAt?: NativeDate | null;
|
|
37
|
-
metadata?: any;
|
|
38
38
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
39
39
|
programId: string;
|
|
40
40
|
type: "order" | "action" | "category" | "tier_bonus";
|
|
41
41
|
name: string;
|
|
42
42
|
status: "active" | "paused" | "expired";
|
|
43
43
|
priority: number;
|
|
44
|
+
metadata?: any;
|
|
44
45
|
description?: string | null;
|
|
45
46
|
conditions?: {
|
|
46
47
|
categories: string[];
|
|
@@ -64,7 +65,6 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
64
65
|
} | null;
|
|
65
66
|
startsAt?: NativeDate | null;
|
|
66
67
|
endsAt?: NativeDate | null;
|
|
67
|
-
metadata?: any;
|
|
68
68
|
} & mongoose.DefaultTimestampProps, {
|
|
69
69
|
id: string;
|
|
70
70
|
}, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
|
|
@@ -75,6 +75,7 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
75
75
|
name: string;
|
|
76
76
|
status: "active" | "paused" | "expired";
|
|
77
77
|
priority: number;
|
|
78
|
+
metadata?: any;
|
|
78
79
|
description?: string | null;
|
|
79
80
|
conditions?: {
|
|
80
81
|
categories: string[];
|
|
@@ -98,7 +99,6 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
98
99
|
} | null;
|
|
99
100
|
startsAt?: NativeDate | null;
|
|
100
101
|
endsAt?: NativeDate | null;
|
|
101
|
-
metadata?: any;
|
|
102
102
|
} & mongoose.DefaultTimestampProps & {
|
|
103
103
|
_id: mongoose.Types.ObjectId;
|
|
104
104
|
} & {
|
|
@@ -111,6 +111,7 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
111
111
|
name: string;
|
|
112
112
|
status: "active" | "paused" | "expired";
|
|
113
113
|
priority: number;
|
|
114
|
+
metadata?: any;
|
|
114
115
|
description?: string | null;
|
|
115
116
|
conditions?: {
|
|
116
117
|
categories: string[];
|
|
@@ -134,7 +135,6 @@ declare function createEarningRuleSchema(): mongoose.Schema<any, mongoose.Model<
|
|
|
134
135
|
} | null;
|
|
135
136
|
startsAt?: NativeDate | null;
|
|
136
137
|
endsAt?: NativeDate | null;
|
|
137
|
-
metadata?: any;
|
|
138
138
|
createdAt: NativeDate;
|
|
139
139
|
updatedAt: NativeDate;
|
|
140
140
|
} & {
|
|
@@ -153,8 +153,8 @@ declare function createMemberSchema(): mongoose.Schema<any, mongoose.Model<any,
|
|
|
153
153
|
externalType: string;
|
|
154
154
|
enrolledAt: NativeDate;
|
|
155
155
|
metadata?: any;
|
|
156
|
-
cardId?: string | null;
|
|
157
156
|
referralCode?: string | null;
|
|
157
|
+
cardId?: string | null;
|
|
158
158
|
tier?: string | null;
|
|
159
159
|
tierOverride?: string | null;
|
|
160
160
|
tierOverrideReason?: string | null;
|
|
@@ -176,8 +176,8 @@ declare function createMemberSchema(): mongoose.Schema<any, mongoose.Model<any,
|
|
|
176
176
|
externalType: string;
|
|
177
177
|
enrolledAt: NativeDate;
|
|
178
178
|
metadata?: any;
|
|
179
|
-
cardId?: string | null;
|
|
180
179
|
referralCode?: string | null;
|
|
180
|
+
cardId?: string | null;
|
|
181
181
|
tier?: string | null;
|
|
182
182
|
tierOverride?: string | null;
|
|
183
183
|
tierOverrideReason?: string | null;
|
|
@@ -203,8 +203,8 @@ declare function createMemberSchema(): mongoose.Schema<any, mongoose.Model<any,
|
|
|
203
203
|
externalType: string;
|
|
204
204
|
enrolledAt: NativeDate;
|
|
205
205
|
metadata?: any;
|
|
206
|
-
cardId?: string | null;
|
|
207
206
|
referralCode?: string | null;
|
|
207
|
+
cardId?: string | null;
|
|
208
208
|
tier?: string | null;
|
|
209
209
|
tierOverride?: string | null;
|
|
210
210
|
tierOverrideReason?: string | null;
|
|
@@ -232,8 +232,8 @@ declare function createMemberSchema(): mongoose.Schema<any, mongoose.Model<any,
|
|
|
232
232
|
externalType: string;
|
|
233
233
|
enrolledAt: NativeDate;
|
|
234
234
|
metadata?: any;
|
|
235
|
-
cardId?: string | null;
|
|
236
235
|
referralCode?: string | null;
|
|
236
|
+
cardId?: string | null;
|
|
237
237
|
tier?: string | null;
|
|
238
238
|
tierOverride?: string | null;
|
|
239
239
|
tierOverrideReason?: string | null;
|
|
@@ -271,15 +271,15 @@ declare function createPointTransactionSchema(): mongoose.Schema<any, mongoose.M
|
|
|
271
271
|
balanceAfter: number;
|
|
272
272
|
actorId: string;
|
|
273
273
|
metadata?: any;
|
|
274
|
-
expiresAt?: NativeDate | null;
|
|
275
|
-
approvals?: any;
|
|
276
|
-
approvalPolicyId?: string | null;
|
|
277
|
-
approvalPolicyVersion?: number | null;
|
|
278
274
|
referenceType?: string | null;
|
|
279
275
|
referenceId?: string | null;
|
|
280
276
|
idempotencyKey?: string | null;
|
|
281
277
|
earningRuleId?: mongoose.Types.ObjectId | null;
|
|
278
|
+
expiresAt?: NativeDate | null;
|
|
282
279
|
expiredAt?: NativeDate | null;
|
|
280
|
+
approvals?: any;
|
|
281
|
+
approvalPolicyId?: string | null;
|
|
282
|
+
approvalPolicyVersion?: number | null;
|
|
283
283
|
createdAt: NativeDate;
|
|
284
284
|
}, mongoose.Document<unknown, {}, {
|
|
285
285
|
programId: string;
|
|
@@ -290,15 +290,15 @@ declare function createPointTransactionSchema(): mongoose.Schema<any, mongoose.M
|
|
|
290
290
|
balanceAfter: number;
|
|
291
291
|
actorId: string;
|
|
292
292
|
metadata?: any;
|
|
293
|
-
expiresAt?: NativeDate | null;
|
|
294
|
-
approvals?: any;
|
|
295
|
-
approvalPolicyId?: string | null;
|
|
296
|
-
approvalPolicyVersion?: number | null;
|
|
297
293
|
referenceType?: string | null;
|
|
298
294
|
referenceId?: string | null;
|
|
299
295
|
idempotencyKey?: string | null;
|
|
300
296
|
earningRuleId?: mongoose.Types.ObjectId | null;
|
|
297
|
+
expiresAt?: NativeDate | null;
|
|
301
298
|
expiredAt?: NativeDate | null;
|
|
299
|
+
approvals?: any;
|
|
300
|
+
approvalPolicyId?: string | null;
|
|
301
|
+
approvalPolicyVersion?: number | null;
|
|
302
302
|
createdAt: NativeDate;
|
|
303
303
|
}, {
|
|
304
304
|
id: string;
|
|
@@ -316,15 +316,15 @@ declare function createPointTransactionSchema(): mongoose.Schema<any, mongoose.M
|
|
|
316
316
|
balanceAfter: number;
|
|
317
317
|
actorId: string;
|
|
318
318
|
metadata?: any;
|
|
319
|
-
expiresAt?: NativeDate | null;
|
|
320
|
-
approvals?: any;
|
|
321
|
-
approvalPolicyId?: string | null;
|
|
322
|
-
approvalPolicyVersion?: number | null;
|
|
323
319
|
referenceType?: string | null;
|
|
324
320
|
referenceId?: string | null;
|
|
325
321
|
idempotencyKey?: string | null;
|
|
326
322
|
earningRuleId?: mongoose.Types.ObjectId | null;
|
|
323
|
+
expiresAt?: NativeDate | null;
|
|
327
324
|
expiredAt?: NativeDate | null;
|
|
325
|
+
approvals?: any;
|
|
326
|
+
approvalPolicyId?: string | null;
|
|
327
|
+
approvalPolicyVersion?: number | null;
|
|
328
328
|
createdAt: NativeDate;
|
|
329
329
|
} & {
|
|
330
330
|
_id: mongoose.Types.ObjectId;
|
|
@@ -341,15 +341,15 @@ declare function createPointTransactionSchema(): mongoose.Schema<any, mongoose.M
|
|
|
341
341
|
balanceAfter: number;
|
|
342
342
|
actorId: string;
|
|
343
343
|
metadata?: any;
|
|
344
|
-
expiresAt?: NativeDate | null;
|
|
345
|
-
approvals?: any;
|
|
346
|
-
approvalPolicyId?: string | null;
|
|
347
|
-
approvalPolicyVersion?: number | null;
|
|
348
344
|
referenceType?: string | null;
|
|
349
345
|
referenceId?: string | null;
|
|
350
346
|
idempotencyKey?: string | null;
|
|
351
347
|
earningRuleId?: mongoose.Types.ObjectId | null;
|
|
348
|
+
expiresAt?: NativeDate | null;
|
|
352
349
|
expiredAt?: NativeDate | null;
|
|
350
|
+
approvals?: any;
|
|
351
|
+
approvalPolicyId?: string | null;
|
|
352
|
+
approvalPolicyVersion?: number | null;
|
|
353
353
|
createdAt: NativeDate;
|
|
354
354
|
} & {
|
|
355
355
|
_id: mongoose.Types.ObjectId;
|
|
@@ -371,6 +371,9 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
371
371
|
ownerType: string;
|
|
372
372
|
ownerId: string;
|
|
373
373
|
expiresAt?: NativeDate | null;
|
|
374
|
+
approvals?: any;
|
|
375
|
+
approvalPolicyId?: string | null;
|
|
376
|
+
approvalPolicyVersion?: number | null;
|
|
374
377
|
confirmedAt?: NativeDate | null;
|
|
375
378
|
releasedAt?: NativeDate | null;
|
|
376
379
|
reversedAt?: NativeDate | null;
|
|
@@ -379,9 +382,6 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
379
382
|
transactionId?: mongoose.Types.ObjectId | null;
|
|
380
383
|
initialBalance?: number | null;
|
|
381
384
|
remainingBalance?: number | null;
|
|
382
|
-
approvals?: any;
|
|
383
|
-
approvalPolicyId?: string | null;
|
|
384
|
-
approvalPolicyVersion?: number | null;
|
|
385
385
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
386
386
|
programId: string;
|
|
387
387
|
status: "expired" | "reserved" | "confirmed" | "released" | "reversed";
|
|
@@ -393,6 +393,9 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
393
393
|
ownerType: string;
|
|
394
394
|
ownerId: string;
|
|
395
395
|
expiresAt?: NativeDate | null;
|
|
396
|
+
approvals?: any;
|
|
397
|
+
approvalPolicyId?: string | null;
|
|
398
|
+
approvalPolicyVersion?: number | null;
|
|
396
399
|
confirmedAt?: NativeDate | null;
|
|
397
400
|
releasedAt?: NativeDate | null;
|
|
398
401
|
reversedAt?: NativeDate | null;
|
|
@@ -401,9 +404,6 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
401
404
|
transactionId?: mongoose.Types.ObjectId | null;
|
|
402
405
|
initialBalance?: number | null;
|
|
403
406
|
remainingBalance?: number | null;
|
|
404
|
-
approvals?: any;
|
|
405
|
-
approvalPolicyId?: string | null;
|
|
406
|
-
approvalPolicyVersion?: number | null;
|
|
407
407
|
} & mongoose.DefaultTimestampProps, {
|
|
408
408
|
id: string;
|
|
409
409
|
}, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
|
|
@@ -419,6 +419,9 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
419
419
|
ownerType: string;
|
|
420
420
|
ownerId: string;
|
|
421
421
|
expiresAt?: NativeDate | null;
|
|
422
|
+
approvals?: any;
|
|
423
|
+
approvalPolicyId?: string | null;
|
|
424
|
+
approvalPolicyVersion?: number | null;
|
|
422
425
|
confirmedAt?: NativeDate | null;
|
|
423
426
|
releasedAt?: NativeDate | null;
|
|
424
427
|
reversedAt?: NativeDate | null;
|
|
@@ -427,9 +430,6 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
427
430
|
transactionId?: mongoose.Types.ObjectId | null;
|
|
428
431
|
initialBalance?: number | null;
|
|
429
432
|
remainingBalance?: number | null;
|
|
430
|
-
approvals?: any;
|
|
431
|
-
approvalPolicyId?: string | null;
|
|
432
|
-
approvalPolicyVersion?: number | null;
|
|
433
433
|
} & mongoose.DefaultTimestampProps & {
|
|
434
434
|
_id: mongoose.Types.ObjectId;
|
|
435
435
|
} & {
|
|
@@ -447,6 +447,9 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
447
447
|
ownerType: string;
|
|
448
448
|
ownerId: string;
|
|
449
449
|
expiresAt?: NativeDate | null;
|
|
450
|
+
approvals?: any;
|
|
451
|
+
approvalPolicyId?: string | null;
|
|
452
|
+
approvalPolicyVersion?: number | null;
|
|
450
453
|
confirmedAt?: NativeDate | null;
|
|
451
454
|
releasedAt?: NativeDate | null;
|
|
452
455
|
reversedAt?: NativeDate | null;
|
|
@@ -455,9 +458,6 @@ declare function createRedemptionSchema(): mongoose.Schema<any, mongoose.Model<a
|
|
|
455
458
|
transactionId?: mongoose.Types.ObjectId | null;
|
|
456
459
|
initialBalance?: number | null;
|
|
457
460
|
remainingBalance?: number | null;
|
|
458
|
-
approvals?: any;
|
|
459
|
-
approvalPolicyId?: string | null;
|
|
460
|
-
approvalPolicyVersion?: number | null;
|
|
461
461
|
createdAt: NativeDate;
|
|
462
462
|
updatedAt: NativeDate;
|
|
463
463
|
} & {
|
|
@@ -472,9 +472,9 @@ declare function createReferralSchema(): mongoose.Schema<any, mongoose.Model<any
|
|
|
472
472
|
}, {
|
|
473
473
|
programId: string;
|
|
474
474
|
status: "expired" | "pending" | "approved" | "rejected";
|
|
475
|
-
referralCode: string;
|
|
476
475
|
referrerId: mongoose.Types.ObjectId;
|
|
477
476
|
refereeId: mongoose.Types.ObjectId;
|
|
477
|
+
referralCode: string;
|
|
478
478
|
metadata?: any;
|
|
479
479
|
expiresAt?: NativeDate | null;
|
|
480
480
|
referrerRewardPoints?: number | null;
|
|
@@ -487,9 +487,9 @@ declare function createReferralSchema(): mongoose.Schema<any, mongoose.Model<any
|
|
|
487
487
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
488
488
|
programId: string;
|
|
489
489
|
status: "expired" | "pending" | "approved" | "rejected";
|
|
490
|
-
referralCode: string;
|
|
491
490
|
referrerId: mongoose.Types.ObjectId;
|
|
492
491
|
refereeId: mongoose.Types.ObjectId;
|
|
492
|
+
referralCode: string;
|
|
493
493
|
metadata?: any;
|
|
494
494
|
expiresAt?: NativeDate | null;
|
|
495
495
|
referrerRewardPoints?: number | null;
|
|
@@ -506,9 +506,9 @@ declare function createReferralSchema(): mongoose.Schema<any, mongoose.Model<any
|
|
|
506
506
|
}> & Omit<{
|
|
507
507
|
programId: string;
|
|
508
508
|
status: "expired" | "pending" | "approved" | "rejected";
|
|
509
|
-
referralCode: string;
|
|
510
509
|
referrerId: mongoose.Types.ObjectId;
|
|
511
510
|
refereeId: mongoose.Types.ObjectId;
|
|
511
|
+
referralCode: string;
|
|
512
512
|
metadata?: any;
|
|
513
513
|
expiresAt?: NativeDate | null;
|
|
514
514
|
referrerRewardPoints?: number | null;
|
|
@@ -527,9 +527,9 @@ declare function createReferralSchema(): mongoose.Schema<any, mongoose.Model<any
|
|
|
527
527
|
}>, unknown, {
|
|
528
528
|
programId: string;
|
|
529
529
|
status: "expired" | "pending" | "approved" | "rejected";
|
|
530
|
-
referralCode: string;
|
|
531
530
|
referrerId: mongoose.Types.ObjectId;
|
|
532
531
|
refereeId: mongoose.Types.ObjectId;
|
|
532
|
+
referralCode: string;
|
|
533
533
|
metadata?: any;
|
|
534
534
|
expiresAt?: NativeDate | null;
|
|
535
535
|
referrerRewardPoints?: number | null;
|
|
@@ -554,7 +554,6 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
554
554
|
programId: string;
|
|
555
555
|
name: string;
|
|
556
556
|
rank: number;
|
|
557
|
-
metadata?: any;
|
|
558
557
|
qualificationCriteria?: {
|
|
559
558
|
minLifetimePoints?: number | null;
|
|
560
559
|
minLifetimeSpend?: number | null;
|
|
@@ -571,11 +570,11 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
571
570
|
gracePeriodDays: number;
|
|
572
571
|
} | null;
|
|
573
572
|
color?: string | null;
|
|
573
|
+
metadata?: any;
|
|
574
574
|
} & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, {
|
|
575
575
|
programId: string;
|
|
576
576
|
name: string;
|
|
577
577
|
rank: number;
|
|
578
|
-
metadata?: any;
|
|
579
578
|
qualificationCriteria?: {
|
|
580
579
|
minLifetimePoints?: number | null;
|
|
581
580
|
minLifetimeSpend?: number | null;
|
|
@@ -592,6 +591,7 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
592
591
|
gracePeriodDays: number;
|
|
593
592
|
} | null;
|
|
594
593
|
color?: string | null;
|
|
594
|
+
metadata?: any;
|
|
595
595
|
} & mongoose.DefaultTimestampProps, {
|
|
596
596
|
id: string;
|
|
597
597
|
}, Omit<mongoose.DefaultSchemaOptions, "timestamps"> & {
|
|
@@ -600,7 +600,6 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
600
600
|
programId: string;
|
|
601
601
|
name: string;
|
|
602
602
|
rank: number;
|
|
603
|
-
metadata?: any;
|
|
604
603
|
qualificationCriteria?: {
|
|
605
604
|
minLifetimePoints?: number | null;
|
|
606
605
|
minLifetimeSpend?: number | null;
|
|
@@ -617,6 +616,7 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
617
616
|
gracePeriodDays: number;
|
|
618
617
|
} | null;
|
|
619
618
|
color?: string | null;
|
|
619
|
+
metadata?: any;
|
|
620
620
|
} & mongoose.DefaultTimestampProps & {
|
|
621
621
|
_id: mongoose.Types.ObjectId;
|
|
622
622
|
} & {
|
|
@@ -627,7 +627,6 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
627
627
|
programId: string;
|
|
628
628
|
name: string;
|
|
629
629
|
rank: number;
|
|
630
|
-
metadata?: any;
|
|
631
630
|
qualificationCriteria?: {
|
|
632
631
|
minLifetimePoints?: number | null;
|
|
633
632
|
minLifetimeSpend?: number | null;
|
|
@@ -644,6 +643,7 @@ declare function createTierDefinitionSchema(): mongoose.Schema<any, mongoose.Mod
|
|
|
644
643
|
gracePeriodDays: number;
|
|
645
644
|
} | null;
|
|
646
645
|
color?: string | null;
|
|
646
|
+
metadata?: any;
|
|
647
647
|
createdAt: NativeDate;
|
|
648
648
|
updatedAt: NativeDate;
|
|
649
649
|
} & {
|
package/dist/index.d.mts
CHANGED
|
@@ -2,8 +2,8 @@ import { S as EarningRule, a as EnrollInput, b as MemberBalance, c as UpdateTier
|
|
|
2
2
|
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
3
3
|
import { c as ResolvedConfig, i as LoyaltyShape, l as ResolvedTenant, n as LoyaltyModelName, r as LoyaltyRuntime } from "./config-CtkGj3xZ.mjs";
|
|
4
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-YUFamhDk.mjs";
|
|
5
|
-
import { n as LoyaltyModels, r as defineLoyaltyModels, t as DefineLoyaltyModelsConfig } 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-
|
|
5
|
+
import { n as LoyaltyModels, r as defineLoyaltyModels, t as DefineLoyaltyModelsConfig } from "./index-BIhVuwIZ.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-BIPHUsfR.mjs";
|
|
7
7
|
import { Connection } from "mongoose";
|
|
8
8
|
import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
|
|
9
9
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./constants-DOPBh6kt.mjs";
|
|
2
|
-
import { C as SessionNotInTransactionError, D as ValidationError, E as UnmanagedSessionError, S as SelfReferralError, T as TierNotFoundError, _ 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 TenantIsolationError, x as RuleNotFoundError, y as ReferralLimitExceededError } from "./repositories-
|
|
2
|
+
import { C as SessionNotInTransactionError, D as ValidationError, E as UnmanagedSessionError, S as SelfReferralError, T as TierNotFoundError, _ 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 TenantIsolationError, x as RuleNotFoundError, y as ReferralLimitExceededError } from "./repositories-DErtHQou.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-BLjg3uLA.mjs";
|
|
4
4
|
import { t as defineLoyaltyModels } from "./models-weWqS5Bc.mjs";
|
|
5
5
|
import { n as createEvent, t as LoyaltyEvents } from "./loyalty-events-DXirolM_.mjs";
|
|
@@ -49,6 +49,10 @@ var MongoUnitOfWork = class {
|
|
|
49
49
|
this.connection = connection;
|
|
50
50
|
this.allowFallback = allowFallback;
|
|
51
51
|
}
|
|
52
|
+
/** The fallback IS the transaction-less path — say so, don't make callers infer it. */
|
|
53
|
+
get allowsNonTransactional() {
|
|
54
|
+
return this.allowFallback;
|
|
55
|
+
}
|
|
52
56
|
async withTransaction(cb) {
|
|
53
57
|
return withTransaction(this.connection, cb, this.allowFallback ? { allowFallback: true } : {});
|
|
54
58
|
}
|
package/dist/models/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as LoyaltyModelName } from "../config-CtkGj3xZ.mjs";
|
|
2
|
-
import { n as LoyaltyModels, r as defineLoyaltyModels, t as DefineLoyaltyModelsConfig } from "../index-
|
|
2
|
+
import { n as LoyaltyModels, r as defineLoyaltyModels, t as DefineLoyaltyModelsConfig } from "../index-BIhVuwIZ.mjs";
|
|
3
3
|
export { DefineLoyaltyModelsConfig, type LoyaltyModelName, LoyaltyModels, defineLoyaltyModels };
|
|
@@ -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-BIPHUsfR.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-DErtHQou.mjs";
|
|
2
2
|
export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|
|
@@ -485,6 +485,16 @@ var EarningRuleRepository = class extends Repository {
|
|
|
485
485
|
*/
|
|
486
486
|
const PENDING_EVENTS = Symbol.for("@classytic/loyalty:pendingEvents");
|
|
487
487
|
/**
|
|
488
|
+
* Set when the unit of work was PERMITTED to run without a transaction
|
|
489
|
+
* (`allowNonTransactional`), so the session-in-transaction guard knows the
|
|
490
|
+
* degradation was chosen rather than stumbled into.
|
|
491
|
+
*
|
|
492
|
+
* Carried on the context because the guard also runs from
|
|
493
|
+
* `dispatchLoyaltyEvent`, which never sees the unit of work. Internal and
|
|
494
|
+
* `Symbol.for`, so no host can set it by accident or on purpose.
|
|
495
|
+
*/
|
|
496
|
+
const NON_TRANSACTIONAL_CONSENTED = Symbol.for("@classytic/loyalty:nonTransactionalConsented");
|
|
497
|
+
/**
|
|
488
498
|
* A caller-supplied `ctx.session` is only evidence of a unit of work if it
|
|
489
499
|
* has an ACTIVE transaction. A bare `startSession()` commits every write
|
|
490
500
|
* independently, so treating it as atomic is how a balance `$inc` lands with
|
|
@@ -497,6 +507,7 @@ const PENDING_EVENTS = Symbol.for("@classytic/loyalty:pendingEvents");
|
|
|
497
507
|
*/
|
|
498
508
|
function assertSessionInTransaction(ctx) {
|
|
499
509
|
if (!ctx.session) return;
|
|
510
|
+
if (ctx[NON_TRANSACTIONAL_CONSENTED]) return;
|
|
500
511
|
const session = ctx.session;
|
|
501
512
|
if (typeof session.inTransaction !== "function" || !session.inTransaction()) throw new SessionNotInTransactionError();
|
|
502
513
|
}
|
|
@@ -549,11 +560,14 @@ async function runLoyaltyUnit(unitOfWork, deps, ctx, body) {
|
|
|
549
560
|
const pending = [];
|
|
550
561
|
const result = await unitOfWork.withTransaction(async (session) => {
|
|
551
562
|
pending.length = 0;
|
|
552
|
-
|
|
563
|
+
const innerCtx = {
|
|
553
564
|
...internalCtx,
|
|
554
565
|
session,
|
|
555
|
-
[PENDING_EVENTS]: pending
|
|
556
|
-
|
|
566
|
+
[PENDING_EVENTS]: pending,
|
|
567
|
+
...unitOfWork.allowsNonTransactional ? { [NON_TRANSACTIONAL_CONSENTED]: true } : {}
|
|
568
|
+
};
|
|
569
|
+
assertSessionInTransaction(innerCtx);
|
|
570
|
+
return body(innerCtx);
|
|
557
571
|
});
|
|
558
572
|
await flushPendingLoyaltyEvents(deps.events, pending, deps.logger);
|
|
559
573
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/loyalty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Loyalty points, tiers, earning rules, referrals, and redemption engine for MongoDB",
|
|
5
5
|
"author": "Classytic",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@classytic/loyalty",
|
|
@@ -78,20 +78,20 @@
|
|
|
78
78
|
"release": "npm run push -- main && npm run release:tag && npm publish"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@classytic/mongokit": ">=3.
|
|
82
|
-
"@classytic/primitives": ">=0.
|
|
83
|
-
"@classytic/repo-core": ">=0.
|
|
84
|
-
"@classytic/validation": ">=0.
|
|
81
|
+
"@classytic/mongokit": ">=3.38.1",
|
|
82
|
+
"@classytic/primitives": ">=0.27.0",
|
|
83
|
+
"@classytic/repo-core": ">=0.27.1",
|
|
84
|
+
"@classytic/validation": ">=0.5.0",
|
|
85
85
|
"mongoose": ">=9.4.1",
|
|
86
86
|
"zod": ">=4.0.0"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@biomejs/biome": "^2.4.9",
|
|
90
90
|
"@classytic/dev-tools": "^0.2.0",
|
|
91
|
-
"@classytic/mongokit": "^3.
|
|
92
|
-
"@classytic/primitives": ">=0.
|
|
93
|
-
"@classytic/repo-core": ">=0.
|
|
94
|
-
"@classytic/validation": ">=0.
|
|
91
|
+
"@classytic/mongokit": "^3.38.1",
|
|
92
|
+
"@classytic/primitives": ">=0.27.0",
|
|
93
|
+
"@classytic/repo-core": ">=0.27.1",
|
|
94
|
+
"@classytic/validation": ">=0.5.0",
|
|
95
95
|
"@types/node": "^25.5.0",
|
|
96
96
|
"@vitest/coverage-v8": "^3.2.4",
|
|
97
97
|
"knip": "^6.3.0",
|