@classytic/loyalty 0.5.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 +367 -231
- package/LICENSE +75 -21
- package/README.md +2 -2
- package/dist/{config-Dtun1duw.d.mts → config-CtkGj3xZ.d.mts} +41 -22
- package/dist/{constants-DkQYkOK1.mjs → constants-DOPBh6kt.mjs} +1 -3
- package/dist/domain/enums/index.d.mts +1 -1
- package/dist/domain/enums/index.mjs +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +2 -2
- package/dist/{events-DN5_p6T0.mjs → events-BLjg3uLA.mjs} +22 -31
- package/dist/{index-Dngaj21_.d.mts → index-BIPHUsfR.d.mts} +92 -14
- package/dist/index-BIhVuwIZ.d.mts +692 -0
- package/dist/{index-YSsi4e_G.d.mts → index-Cp_sXNpG.d.mts} +35 -4
- package/dist/{index-D_5Ojc2c.d.mts → index-YUFamhDk.d.mts} +7 -17
- package/dist/index.d.mts +60 -29
- package/dist/index.mjs +103 -52
- package/dist/models/index.d.mts +3 -1273
- package/dist/models/index.mjs +2 -2
- package/dist/{models-Ddh62lgO.mjs → models-weWqS5Bc.mjs} +152 -70
- package/dist/notifications/index.d.mts +1 -2
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-CjWfUlrA.mjs → repositories-DErtHQou.mjs} +198 -69
- package/dist/types/index.d.mts +3 -3
- package/package.json +119 -116
- /package/dist/{index-DMK8a98J.d.mts → index-D_Oe3aDk.d.mts} +0 -0
- /package/dist/{loyalty-events-C38JA3cU.mjs → loyalty-events-DXirolM_.mjs} +0 -0
- /package/dist/{notification.port-CJ9nXj0k.d.mts → notification.port-wY4IKimz.d.mts} +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Classytic
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Classytic Source-Available License (Community & Commercial), Version 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Classytic LLC. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is source-available, not open source. Please read this License
|
|
6
|
+
before using it.
|
|
7
|
+
|
|
8
|
+
1. DEFINITIONS
|
|
9
|
+
"Software" means the contents of this package and any accompanying source
|
|
10
|
+
code, object code, or documentation, in any form.
|
|
11
|
+
"Licensor" means Classytic LLC, a limited liability company organized in the
|
|
12
|
+
United States.
|
|
13
|
+
"You" means the individual or legal entity exercising rights under this
|
|
14
|
+
License.
|
|
15
|
+
"Evaluation Use" means use for evaluation, development, testing,
|
|
16
|
+
experimentation, or internal non-commercial purposes only.
|
|
17
|
+
"Production Use" means any use of the Software that is live, commercial,
|
|
18
|
+
revenue-generating, or customer-facing, or any use other than Evaluation Use.
|
|
19
|
+
"Commercial License" means a separate written agreement executed with
|
|
20
|
+
Licensor that grants Production Use rights.
|
|
21
|
+
|
|
22
|
+
2. OWNERSHIP
|
|
23
|
+
The Software is licensed, not sold. Licensor retains all right, title, and
|
|
24
|
+
interest in and to the Software, including all intellectual property rights.
|
|
25
|
+
All rights not expressly granted in this License are reserved by Licensor.
|
|
26
|
+
|
|
27
|
+
3. EVALUATION GRANT
|
|
28
|
+
Subject to Your compliance with this License, Licensor grants You a
|
|
29
|
+
worldwide, royalty-free, non-exclusive, non-transferable, revocable license
|
|
30
|
+
to download, install, run, and modify the Software solely for Evaluation Use.
|
|
31
|
+
|
|
32
|
+
4. PRODUCTION USE REQUIRES A COMMERCIAL LICENSE
|
|
33
|
+
Production Use of the Software is NOT permitted under this License and
|
|
34
|
+
requires a Commercial License from Licensor. A Commercial License may be
|
|
35
|
+
granted at Licensor's discretion, with or without a fee, and may confer
|
|
36
|
+
perpetual, irrevocable, and transferable Production Use rights as set out in
|
|
37
|
+
that agreement. Contact licensing@classytic.com to obtain one.
|
|
38
|
+
|
|
39
|
+
5. RESTRICTIONS
|
|
40
|
+
You may not:
|
|
41
|
+
(a) make any Production Use of the Software without a Commercial License;
|
|
42
|
+
(b) redistribute, resell, rent, sublicense, or otherwise make the Software
|
|
43
|
+
(in whole or in part, original or modified) available to third parties as
|
|
44
|
+
a stand-alone product, framework, library, or hosted/managed service that
|
|
45
|
+
competes with the Software or with Licensor;
|
|
46
|
+
(c) remove, obscure, or alter any copyright, license, or attribution notice.
|
|
47
|
+
|
|
48
|
+
6. FEEDBACK AND CONTRIBUTIONS
|
|
49
|
+
Any feedback, suggestions, or contributions You provide relating to the
|
|
50
|
+
Software may be used by Licensor for any purpose without obligation to You.
|
|
51
|
+
|
|
52
|
+
7. TERMINATION
|
|
53
|
+
This License and the Evaluation Grant terminate automatically if You breach
|
|
54
|
+
any term. Upon termination You must cease all use of the Software and destroy
|
|
55
|
+
all copies in Your possession. Rights under a Commercial License are governed
|
|
56
|
+
by that agreement's own terms and survive termination of this License to the
|
|
57
|
+
extent stated there.
|
|
58
|
+
|
|
59
|
+
8. DISCLAIMER OF WARRANTY
|
|
60
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
61
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
62
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
63
|
+
|
|
64
|
+
9. LIMITATION OF LIABILITY
|
|
65
|
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL LICENSOR BE LIABLE
|
|
66
|
+
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
67
|
+
TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE
|
|
68
|
+
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
69
|
+
|
|
70
|
+
10. GOVERNING LAW
|
|
71
|
+
This License is governed by the laws of the State of Delaware, United States,
|
|
72
|
+
without regard to its conflict-of-laws principles. (Adjust to Your LLC's state
|
|
73
|
+
of formation.)
|
|
74
|
+
|
|
75
|
+
For commercial licensing, contact: licensing@classytic.com
|
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ import mongoose from 'mongoose';
|
|
|
22
22
|
const engine = createLoyaltyEngine({
|
|
23
23
|
mongoose: mongoose.connection,
|
|
24
24
|
tenant: false, // single-tenant (or { field: 'orgId' } for multi-tenant)
|
|
25
|
-
program: {
|
|
26
|
-
redemption: { minPoints: 100, maxRedeemPercent: 50 },
|
|
25
|
+
program: { pointValueMinor: 10 }, // 1 point redeems for 10 minor units (e.g. 10 paisa)
|
|
26
|
+
redemption: { minPoints: 100, maxRedeemPercent: 50 }, // money amounts are integer MINOR units
|
|
27
27
|
referral: { referrerRewardPoints: 200, refereeRewardPoints: 100 },
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { t as NotificationAdapter } from "./notification.port-
|
|
1
|
+
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
2
|
+
import { ResolvedTenantConfig, TenantConfig } from "@classytic/repo-core/tenant";
|
|
2
3
|
import { EventTransport } from "@classytic/primitives/events";
|
|
3
|
-
import { Connection } from "mongoose";
|
|
4
4
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
5
|
-
import { TenantConfig, TenantFieldType } from "@classytic/repo-core/tenant";
|
|
6
|
-
|
|
7
5
|
//#region src/types/config.d.ts
|
|
8
6
|
type LoyaltyModelName = 'Member' | 'PointTransaction' | 'EarningRule' | 'TierDefinition' | 'Redemption' | 'Referral';
|
|
9
|
-
type TenantConfig$1 =
|
|
7
|
+
type TenantConfig$1 = TenantConfig | boolean;
|
|
8
|
+
/** @deprecated Use `ResolvedTenantConfig` from `@classytic/repo-core/tenant`. */
|
|
9
|
+
type ResolvedTenant = ResolvedTenantConfig;
|
|
10
10
|
interface IndexDefinition {
|
|
11
11
|
fields: Record<string, 1 | -1>;
|
|
12
12
|
options?: Record<string, unknown> | undefined;
|
|
@@ -21,8 +21,7 @@ interface RepositoryPlugins {
|
|
|
21
21
|
redemption?: PluginType[] | undefined;
|
|
22
22
|
referral?: PluginType[] | undefined;
|
|
23
23
|
}
|
|
24
|
-
interface
|
|
25
|
-
mongoose: Connection;
|
|
24
|
+
interface LoyaltyShape {
|
|
26
25
|
tenant?: TenantConfig$1 | undefined;
|
|
27
26
|
/**
|
|
28
27
|
* IANA timezone name (e.g. `'Asia/Dhaka'`, `'America/New_York'`) used to
|
|
@@ -37,10 +36,22 @@ interface LoyaltyConfig {
|
|
|
37
36
|
timezone?: string | undefined;
|
|
38
37
|
program?: {
|
|
39
38
|
id?: string | undefined;
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* RATE: integer minor units ONE point redeems for (≥ 1) in the host's
|
|
41
|
+
* ambient currency. `pointValueMinor: 10` = 1 point → 10 paisa
|
|
42
|
+
* (৳0.10, i.e. the old `conversionRate: 10` "10 points per ৳1").
|
|
43
|
+
* Replaces `conversionRate` (0.5.x), whose implicit per-1-MAJOR-unit
|
|
44
|
+
* basis was ambiguous.
|
|
45
|
+
*/
|
|
46
|
+
pointValueMinor?: number | undefined;
|
|
41
47
|
} | undefined;
|
|
42
48
|
redemption?: {
|
|
43
49
|
minPoints?: number | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Minimum order total eligible for redemption — **integer minor
|
|
52
|
+
* units** (paisa for BDT). Compared against `validate()`'s
|
|
53
|
+
* `orderTotal` (also minor units).
|
|
54
|
+
*/
|
|
44
55
|
minOrderAmount?: number | undefined;
|
|
45
56
|
maxRedeemPercent?: number | undefined;
|
|
46
57
|
reservationTtlMinutes?: number | undefined;
|
|
@@ -85,15 +96,18 @@ interface LoyaltyConfig {
|
|
|
85
96
|
/**
|
|
86
97
|
* When true, existing Mongoose models with loyalty's names are deleted
|
|
87
98
|
* from the connection before re-registering. Hot-reload / test fixtures
|
|
88
|
-
* only. Default `false` — collision throws `
|
|
99
|
+
* only. Default `false` — collision throws mongokit's `ModelCollisionError`.
|
|
89
100
|
* Hosts that need two loyalty engines should use two Mongoose connections
|
|
90
101
|
* (`mongoose.createConnection(...)`). See PACKAGE_RULES.md §21.
|
|
91
102
|
*/
|
|
92
103
|
forceRecreate?: boolean | undefined;
|
|
104
|
+
}
|
|
105
|
+
interface LoyaltyRuntime {
|
|
93
106
|
/**
|
|
94
|
-
* Arc-compatible event transport. When omitted, the engine
|
|
95
|
-
* `InProcessLoyaltyBus` — a ~60-line structural
|
|
96
|
-
* `MemoryEventTransport`.
|
|
107
|
+
* Arc-compatible event transport. When omitted, the engine creates (and
|
|
108
|
+
* OWNS, so `close()` closes) an `InProcessLoyaltyBus` — a ~60-line structural
|
|
109
|
+
* match of arc's `MemoryEventTransport`. A host-supplied transport is the
|
|
110
|
+
* host's to close. See PACKAGE_RULES §13.
|
|
97
111
|
*/
|
|
98
112
|
eventTransport?: EventTransport | undefined;
|
|
99
113
|
/**
|
|
@@ -120,19 +134,24 @@ interface LoyaltyConfig {
|
|
|
120
134
|
* "Runtime capabilities".
|
|
121
135
|
*/
|
|
122
136
|
allowNonTransactional?: boolean | undefined;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Where the engine's boot warnings go — today, the `allowNonTransactional`
|
|
139
|
+
* downgrade notice. Defaults to `console`. A downgrade that carries
|
|
140
|
+
* partial-write risk on stored value must be VISIBLE, and a host that routes
|
|
141
|
+
* logs elsewhere would otherwise never see it.
|
|
142
|
+
*/
|
|
143
|
+
logger?: {
|
|
144
|
+
warn?(message: string, ...args: unknown[]): void;
|
|
145
|
+
error?(message: string, ...args: unknown[]): void;
|
|
146
|
+
} | undefined;
|
|
130
147
|
}
|
|
131
148
|
interface ResolvedConfig {
|
|
132
149
|
programId: string;
|
|
133
|
-
|
|
150
|
+
/** Integer minor units one point redeems for (≥ 1). See `LoyaltyShape.program.pointValueMinor`. */
|
|
151
|
+
pointValueMinor: number;
|
|
134
152
|
redemption: {
|
|
135
153
|
minPoints: number;
|
|
154
|
+
/** Integer minor units. */
|
|
136
155
|
minOrderAmount: number;
|
|
137
156
|
maxRedeemPercent: number;
|
|
138
157
|
reservationTtlMinutes: number;
|
|
@@ -153,7 +172,7 @@ interface ResolvedConfig {
|
|
|
153
172
|
};
|
|
154
173
|
/** Resolved IANA zone for wall-clock rule evaluation; `undefined` = UTC. */
|
|
155
174
|
timezone?: string;
|
|
156
|
-
tenant:
|
|
175
|
+
tenant: ResolvedTenantConfig;
|
|
157
176
|
}
|
|
158
177
|
//#endregion
|
|
159
|
-
export {
|
|
178
|
+
export { ModelName as a, ResolvedConfig as c, LoyaltyShape as i, ResolvedTenant as l, LoyaltyModelName as n, PluginType as o, LoyaltyRuntime as r, RepositoryPlugins as s, IndexDefinition as t, TenantConfig$1 as u };
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
//#region src/constants.ts
|
|
2
|
-
const DEFAULT_TENANT_FIELD = "organizationId";
|
|
3
|
-
const DEFAULT_TENANT_TYPE = "objectId";
|
|
4
2
|
const DEFAULT_PROGRAM_ID = "default";
|
|
5
3
|
const TRANSACTION_TYPES = Object.freeze([
|
|
6
4
|
"earn",
|
|
@@ -47,4 +45,4 @@ const EARNING_ACTIONS = Object.freeze([
|
|
|
47
45
|
"profile_complete"
|
|
48
46
|
]);
|
|
49
47
|
//#endregion
|
|
50
|
-
export {
|
|
48
|
+
export { REFERRAL_STATUSES as a, TRANSACTION_TYPES as c, REDEMPTION_STATUSES as i, EARNING_ACTIONS as n, RULE_STATUSES as o, MEMBER_STATUSES as r, RULE_TYPES as s, DEFAULT_PROGRAM_ID as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RuleStatus, c as EARNING_ACTIONS, d as REFERRAL_STATUSES, f as RULE_STATUSES, i as ReferralStatus, l as MEMBER_STATUSES, m as TRANSACTION_TYPES, n as MemberStatus, o as RuleType, p as RULE_TYPES, r as RedemptionStatus, s as TransactionType, t as EarningAction, u as REDEMPTION_STATUSES } from "../../index-
|
|
1
|
+
import { a as RuleStatus, c as EARNING_ACTIONS, d as REFERRAL_STATUSES, f as RULE_STATUSES, i as ReferralStatus, l as MEMBER_STATUSES, m as TRANSACTION_TYPES, n as MemberStatus, o as RuleType, p as RULE_TYPES, r as RedemptionStatus, s as TransactionType, t as EarningAction, u as REDEMPTION_STATUSES } from "../../index-D_Oe3aDk.mjs";
|
|
2
2
|
export { EARNING_ACTIONS, EarningAction, MEMBER_STATUSES, MemberStatus, REDEMPTION_STATUSES, REFERRAL_STATUSES, RULE_STATUSES, RULE_TYPES, RedemptionStatus, ReferralStatus, RuleStatus, RuleType, TRANSACTION_TYPES, TransactionType };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as REFERRAL_STATUSES, c as TRANSACTION_TYPES, i as REDEMPTION_STATUSES, n as EARNING_ACTIONS, o as RULE_STATUSES, r as MEMBER_STATUSES, s as RULE_TYPES } from "../../constants-DOPBh6kt.mjs";
|
|
2
2
|
export { EARNING_ACTIONS, MEMBER_STATUSES, REDEMPTION_STATUSES, REFERRAL_STATUSES, RULE_STATUSES, RULE_TYPES, TRANSACTION_TYPES };
|
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-
|
|
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
|
+
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-YUFamhDk.mjs";
|
|
2
|
+
export { CampaignAwarded, type CampaignAwardedPayload, type DomainEvent, type EventHandler, type EventTransport, InProcessLoyaltyBus, type InProcessLoyaltyBusOptions, type LoyaltyEventDefinition, type LoyaltyEventMap, type LoyaltyEventName, type LoyaltyEventPayload, type LoyaltyEventPayloadOf, type LoyaltyEventSchema, LoyaltyEvents, MemberDeactivated, MemberEnrolled, type MemberLifecyclePayload, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, type MultiTierReferralAwardedPayload, PointsAdjusted, type PointsAdjustedPayload, PointsEarned, type PointsEarnedPayload, PointsExpired, type PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, type RedemptionPayload, RedemptionReleased, RedemptionReserved, RedemptionReversed, type RedemptionReversedPayload, ReferralApproved, type ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRewarded, type TierChangedPayload, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, type TierOverrideLifecyclePayload, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-
|
|
2
|
-
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-
|
|
1
|
+
import { C as TierOverrideExpired, E as InProcessLoyaltyBus, S as TierOverrideCleared, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, h as RedemptionReversed, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, o as MultiTierReferralAwarded, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, t as CampaignAwarded, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "../events-BLjg3uLA.mjs";
|
|
2
|
+
import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-DXirolM_.mjs";
|
|
3
3
|
export { CampaignAwarded, InProcessLoyaltyBus, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, PointsAdjusted, PointsEarned, PointsExpired, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionReleased, RedemptionReserved, RedemptionReversed, ReferralApproved, ReferralRecorded, ReferralRejected, ReferralRewarded, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierUpgraded, createEvent, loyaltyEventDefinitions };
|
|
@@ -1,35 +1,26 @@
|
|
|
1
|
-
import { t as LoyaltyEvents } from "./loyalty-events-
|
|
2
|
-
import {
|
|
1
|
+
import { t as LoyaltyEvents } from "./loyalty-events-DXirolM_.mjs";
|
|
2
|
+
import { InProcessEventBus } from "@classytic/primitives/event-infra";
|
|
3
|
+
import { createEvent } from "@classytic/primitives/events";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
//#region src/events/in-process-bus.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.handlers.get(pattern)?.add(handler);
|
|
24
|
-
return () => {
|
|
25
|
-
const set = this.handlers.get(pattern);
|
|
26
|
-
if (!set) return;
|
|
27
|
-
set.delete(handler);
|
|
28
|
-
if (set.size === 0) this.handlers.delete(pattern);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async close() {
|
|
32
|
-
this.handlers.clear();
|
|
6
|
+
/**
|
|
7
|
+
* In-process fan-out transport — the default when the host injects none.
|
|
8
|
+
*
|
|
9
|
+
* A thin subclass of `@classytic/primitives/event-infra`'s shared
|
|
10
|
+
* `InProcessEventBus` (the single implementation every kernel now defaults
|
|
11
|
+
* to). Prior semantics preserved exactly: glob matching via
|
|
12
|
+
* `matchEventPattern` (exact / `*` / `prefix.*` / `prefix:*`), Set-dedup so a
|
|
13
|
+
* handler matched by multiple patterns fires once, per-handler error
|
|
14
|
+
* isolation (errors logged; default `console`), idempotent `close()`.
|
|
15
|
+
* Structurally identical to arc's `MemoryEventTransport`.
|
|
16
|
+
*/
|
|
17
|
+
var InProcessLoyaltyBus = class extends InProcessEventBus {
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
super({
|
|
20
|
+
name: "in-process-loyalty",
|
|
21
|
+
logLabel: "loyalty",
|
|
22
|
+
...options
|
|
23
|
+
});
|
|
33
24
|
}
|
|
34
25
|
};
|
|
35
26
|
//#endregion
|
|
@@ -99,7 +90,7 @@ const redemptionSchema = z.object({
|
|
|
99
90
|
externalId: z.string(),
|
|
100
91
|
redemptionId: z.string(),
|
|
101
92
|
points: z.number(),
|
|
102
|
-
discountAmount: z.number(),
|
|
93
|
+
discountAmount: z.number().int().nonnegative(),
|
|
103
94
|
ownerType: z.string(),
|
|
104
95
|
ownerId: z.string()
|
|
105
96
|
});
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import { i as ReferralStatus, r as RedemptionStatus } 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-
|
|
3
|
-
import { t as NotificationAdapter } from "./notification.port-
|
|
4
|
-
import { c as
|
|
5
|
-
import { LoyaltyModels } from "./
|
|
1
|
+
import { i as ReferralStatus, r as RedemptionStatus } from "./index-D_Oe3aDk.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-Cp_sXNpG.mjs";
|
|
3
|
+
import { t as NotificationAdapter } from "./notification.port-wY4IKimz.mjs";
|
|
4
|
+
import { c as ResolvedConfig, s as RepositoryPlugins } from "./config-CtkGj3xZ.mjs";
|
|
5
|
+
import { n as LoyaltyModels } from "./index-BIhVuwIZ.mjs";
|
|
6
6
|
import { PluginType, Repository } from "@classytic/mongokit";
|
|
7
|
-
import {
|
|
7
|
+
import { ResolvedTenantConfig } from "@classytic/repo-core/tenant";
|
|
8
8
|
import { ClientSession, Model } from "mongoose";
|
|
9
|
+
import { EventTransport } from "@classytic/primitives/events";
|
|
9
10
|
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
10
11
|
import { ApprovalChain } from "@classytic/primitives/approval";
|
|
11
|
-
|
|
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
|
|
@@ -138,12 +149,20 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
138
149
|
constructor(model: Model<EarningRule>, plugins?: PluginType[]);
|
|
139
150
|
private applyCreateDefaults;
|
|
140
151
|
/**
|
|
141
|
-
* Auto-injects `programId` from `
|
|
152
|
+
* Auto-injects `programId` from `LoyaltyShape.program.id` and the tenant id
|
|
142
153
|
* from repo options when omitted. This keeps custom-program engines aligned
|
|
143
154
|
* with `MemberRepository.enroll(...)`, which uses the same config default.
|
|
144
155
|
*/
|
|
145
156
|
create(data: Parameters<Repository<EarningRule>['create']>[0], options?: Parameters<Repository<EarningRule>['create']>[1]): Promise<EarningRule>;
|
|
146
157
|
createMany(docs: Parameters<Repository<EarningRule>['createMany']>[0], options?: Parameters<Repository<EarningRule>['createMany']>[1]): Promise<EarningRule[]>;
|
|
158
|
+
/**
|
|
159
|
+
* Evaluates every active order/category/tier_bonus rule against an order.
|
|
160
|
+
*
|
|
161
|
+
* `orderTotal` and `items[].amount` are **integers in the host's minor
|
|
162
|
+
* currency unit** (paisa for BDT) — `50000` = ৳500. Rates on the rule
|
|
163
|
+
* (`amountPerPoint`, `pointsPerAmount` + `rateBasis`) are quoted in the
|
|
164
|
+
* same minor units.
|
|
165
|
+
*/
|
|
147
166
|
evaluateOrder(input: {
|
|
148
167
|
memberId: string;
|
|
149
168
|
orderId: string;
|
|
@@ -172,7 +191,19 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
172
191
|
* machine's `TZ` env (PACKAGE_RULES P12).
|
|
173
192
|
*/
|
|
174
193
|
private weekday;
|
|
194
|
+
/**
|
|
195
|
+
* `orderTotal` is integer minor units. Two rate shapes:
|
|
196
|
+
* - `pointsPerAmount` + `rateBasis`: `points = pointsPerAmount × (orderTotal / rateBasis)`
|
|
197
|
+
* - `amountPerPoint` (minor units per point): `points = orderTotal / amountPerPoint`
|
|
198
|
+
* Rounding is applied ONCE at the end, per the rule's `roundingMode`.
|
|
199
|
+
*/
|
|
175
200
|
private calculateOrderPoints;
|
|
201
|
+
/**
|
|
202
|
+
* `pointsPerAmount` has NO implicit basis — a rule that carries it without
|
|
203
|
+
* an explicit integer minor-unit `rateBasis` is malformed (the schema
|
|
204
|
+
* enforces the pairing on write; this guards legacy/hand-written docs).
|
|
205
|
+
*/
|
|
206
|
+
private requireRateBasis;
|
|
176
207
|
private calculateCategoryPoints;
|
|
177
208
|
private applyRounding;
|
|
178
209
|
}
|
|
@@ -184,8 +215,18 @@ interface Redemption {
|
|
|
184
215
|
programId: string;
|
|
185
216
|
pointsReserved: number;
|
|
186
217
|
pointsConfirmed: number;
|
|
218
|
+
/**
|
|
219
|
+
* Discount granted for the redeemed points — **integer minor units** (paisa
|
|
220
|
+
* for BDT). Invariant: always exactly `pointsReserved × pointValueMinor`
|
|
221
|
+
* (no orphaned remainder — see `RedemptionRepository.validate`).
|
|
222
|
+
*/
|
|
187
223
|
discountAmount: number;
|
|
188
|
-
|
|
224
|
+
/**
|
|
225
|
+
* RATE snapshot at reserve time: integer minor units ONE point redeems for
|
|
226
|
+
* (≥ 1). `pointValueMinor: 10` = 1 point → 10 paisa (৳0.10).
|
|
227
|
+
* Replaces the old `conversionRate` (points per 1 MAJOR unit).
|
|
228
|
+
*/
|
|
229
|
+
pointValueMinor: number;
|
|
189
230
|
status: RedemptionStatus;
|
|
190
231
|
ownerType: string;
|
|
191
232
|
ownerId: string;
|
|
@@ -200,11 +241,13 @@ interface Redemption {
|
|
|
200
241
|
transactionId?: string;
|
|
201
242
|
/**
|
|
202
243
|
* Optional voucher-like partial-use tracking populated by hosts when
|
|
203
|
-
* the redemption is backed by a gift voucher / reward card.
|
|
204
|
-
*
|
|
244
|
+
* the redemption is backed by a gift voucher / reward card. Both are
|
|
245
|
+
* **integer minor units**. When present,
|
|
246
|
+
* `reverseConfirmed({ proportional: true })` credits back
|
|
205
247
|
* `(remainingBalance / initialBalance) × pointsConfirmed`.
|
|
206
248
|
*/
|
|
207
249
|
initialBalance?: number;
|
|
250
|
+
/** Integer minor units. See `initialBalance`. */
|
|
208
251
|
remainingBalance?: number;
|
|
209
252
|
/**
|
|
210
253
|
* Optional multi-step approval chain (P7 canonical — same shape as
|
|
@@ -240,14 +283,40 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
240
283
|
private get _dispatchDeps();
|
|
241
284
|
/**
|
|
242
285
|
* Atomic compare-and-set status transition.
|
|
243
|
-
*
|
|
286
|
+
*
|
|
287
|
+
* `null` means ONE thing and one thing only: the guarded update matched
|
|
288
|
+
* no document, i.e. the status was not `expectedStatus`. `throwOnNotFound:
|
|
289
|
+
* false` already produces that `null`, so there is nothing legitimate left
|
|
290
|
+
* for a `catch` to convert.
|
|
291
|
+
*
|
|
292
|
+
* There used to be a bare `} catch { return null; }` here. It laundered
|
|
293
|
+
* every real failure into a CAS miss, and callers map a CAS miss to
|
|
294
|
+
* `ValidationError("Redemption status must be 'reserved', got '<status>'")`
|
|
295
|
+
* — a NON-retryable 4xx built from the status read BEFORE the write. So a
|
|
296
|
+
* routine Mongo `WriteConflict` (a retryable transient, the normal outcome
|
|
297
|
+
* of two concurrent confirms) surfaced as a client error whose message
|
|
298
|
+
* could literally read "must be 'reserved', got 'reserved'": an operator
|
|
299
|
+
* chasing a contradiction while the actual fix was to retry.
|
|
300
|
+
*
|
|
301
|
+
* Anything that throws in here is a real error and must propagate.
|
|
244
302
|
*/
|
|
245
303
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Redemption | null>;
|
|
304
|
+
/**
|
|
305
|
+
* Validates a redemption request. `orderTotal` is an **integer in the
|
|
306
|
+
* host's minor currency unit** (paisa for BDT); the returned
|
|
307
|
+
* `discountAmount` is in the same minor units.
|
|
308
|
+
*/
|
|
246
309
|
validate(input: {
|
|
247
310
|
memberId: string;
|
|
248
311
|
pointsToRedeem: number;
|
|
249
312
|
orderTotal: number;
|
|
250
313
|
}, ctx: LoyaltyContext): Promise<RedemptionValidation>;
|
|
314
|
+
/**
|
|
315
|
+
* Reserves points against an order. `orderTotal` is an **integer in the
|
|
316
|
+
* host's minor currency unit** (paisa for BDT). The persisted
|
|
317
|
+
* `discountAmount` is minor units and the reserve-time `pointValueMinor`
|
|
318
|
+
* rate is snapshotted on the doc.
|
|
319
|
+
*/
|
|
251
320
|
reserve(input: {
|
|
252
321
|
memberId: string;
|
|
253
322
|
pointsToRedeem: number;
|
|
@@ -317,7 +386,16 @@ declare class ReferralRepository extends Repository<Referral> {
|
|
|
317
386
|
createMany(docs: Parameters<Repository<Referral>['createMany']>[0], options?: Parameters<Repository<Referral>['createMany']>[1]): Promise<Referral[]>;
|
|
318
387
|
/**
|
|
319
388
|
* Atomic compare-and-set status transition.
|
|
320
|
-
*
|
|
389
|
+
*
|
|
390
|
+
* `null` means ONE thing: the guarded update matched no document, i.e.
|
|
391
|
+
* the status was not `expectedStatus`. `throwOnNotFound: false` already
|
|
392
|
+
* returns that `null`, so the bare `} catch { return null; }` that used
|
|
393
|
+
* to sit here could only ever hide REAL errors — most damagingly a
|
|
394
|
+
* retryable Mongo `WriteConflict`, which callers then re-threw as a
|
|
395
|
+
* non-retryable `ValidationError` describing a status that had not
|
|
396
|
+
* actually gone stale.
|
|
397
|
+
*
|
|
398
|
+
* Anything that throws in here is a real error and must propagate.
|
|
321
399
|
*/
|
|
322
400
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Referral | null>;
|
|
323
401
|
generateCode(memberId: string, ctx: LoyaltyContext): Promise<string>;
|
|
@@ -381,7 +459,7 @@ interface LoyaltyRepositories {
|
|
|
381
459
|
interface CreateRepositoriesInput {
|
|
382
460
|
models: LoyaltyModels;
|
|
383
461
|
plugins?: RepositoryPlugins | undefined;
|
|
384
|
-
tenant?:
|
|
462
|
+
tenant?: ResolvedTenantConfig | undefined;
|
|
385
463
|
config: ResolvedConfig;
|
|
386
464
|
events: EventTransport;
|
|
387
465
|
outbox?: OutboxStore | undefined;
|