@classytic/loyalty 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +321 -231
- package/LICENSE +75 -21
- package/README.md +2 -2
- package/dist/{config-Dtun1duw.d.mts → config-CtkGj3xZ.d.mts} +41 -22
- package/dist/{constants-DkQYkOK1.mjs → constants-DOPBh6kt.mjs} +1 -3
- package/dist/domain/enums/index.d.mts +1 -1
- package/dist/domain/enums/index.mjs +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +2 -2
- package/dist/{events-DN5_p6T0.mjs → events-BLjg3uLA.mjs} +22 -31
- package/dist/{index-Dngaj21_.d.mts → index-CR2k7jQ8.d.mts} +81 -14
- package/dist/{index-YSsi4e_G.d.mts → index-Cp_sXNpG.d.mts} +35 -4
- package/dist/index-D6GvuvmF.d.mts +692 -0
- package/dist/{index-D_5Ojc2c.d.mts → index-YUFamhDk.d.mts} +7 -17
- package/dist/index.d.mts +60 -29
- package/dist/index.mjs +99 -52
- package/dist/models/index.d.mts +3 -1273
- package/dist/models/index.mjs +2 -2
- package/dist/{models-Ddh62lgO.mjs → models-weWqS5Bc.mjs} +152 -70
- package/dist/notifications/index.d.mts +1 -2
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-CjWfUlrA.mjs → repositories-B676z4bE.mjs} +181 -66
- package/dist/types/index.d.mts +3 -3
- package/package.json +119 -116
- /package/dist/{index-DMK8a98J.d.mts → index-D_Oe3aDk.d.mts} +0 -0
- /package/dist/{loyalty-events-C38JA3cU.mjs → loyalty-events-DXirolM_.mjs} +0 -0
- /package/dist/{notification.port-CJ9nXj0k.d.mts → notification.port-wY4IKimz.d.mts} +0 -0
package/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,14 +1,14 @@
|
|
|
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-D6GvuvmF.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>;
|
|
@@ -138,12 +138,20 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
138
138
|
constructor(model: Model<EarningRule>, plugins?: PluginType[]);
|
|
139
139
|
private applyCreateDefaults;
|
|
140
140
|
/**
|
|
141
|
-
* Auto-injects `programId` from `
|
|
141
|
+
* Auto-injects `programId` from `LoyaltyShape.program.id` and the tenant id
|
|
142
142
|
* from repo options when omitted. This keeps custom-program engines aligned
|
|
143
143
|
* with `MemberRepository.enroll(...)`, which uses the same config default.
|
|
144
144
|
*/
|
|
145
145
|
create(data: Parameters<Repository<EarningRule>['create']>[0], options?: Parameters<Repository<EarningRule>['create']>[1]): Promise<EarningRule>;
|
|
146
146
|
createMany(docs: Parameters<Repository<EarningRule>['createMany']>[0], options?: Parameters<Repository<EarningRule>['createMany']>[1]): Promise<EarningRule[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Evaluates every active order/category/tier_bonus rule against an order.
|
|
149
|
+
*
|
|
150
|
+
* `orderTotal` and `items[].amount` are **integers in the host's minor
|
|
151
|
+
* currency unit** (paisa for BDT) — `50000` = ৳500. Rates on the rule
|
|
152
|
+
* (`amountPerPoint`, `pointsPerAmount` + `rateBasis`) are quoted in the
|
|
153
|
+
* same minor units.
|
|
154
|
+
*/
|
|
147
155
|
evaluateOrder(input: {
|
|
148
156
|
memberId: string;
|
|
149
157
|
orderId: string;
|
|
@@ -172,7 +180,19 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
|
|
|
172
180
|
* machine's `TZ` env (PACKAGE_RULES P12).
|
|
173
181
|
*/
|
|
174
182
|
private weekday;
|
|
183
|
+
/**
|
|
184
|
+
* `orderTotal` is integer minor units. Two rate shapes:
|
|
185
|
+
* - `pointsPerAmount` + `rateBasis`: `points = pointsPerAmount × (orderTotal / rateBasis)`
|
|
186
|
+
* - `amountPerPoint` (minor units per point): `points = orderTotal / amountPerPoint`
|
|
187
|
+
* Rounding is applied ONCE at the end, per the rule's `roundingMode`.
|
|
188
|
+
*/
|
|
175
189
|
private calculateOrderPoints;
|
|
190
|
+
/**
|
|
191
|
+
* `pointsPerAmount` has NO implicit basis — a rule that carries it without
|
|
192
|
+
* an explicit integer minor-unit `rateBasis` is malformed (the schema
|
|
193
|
+
* enforces the pairing on write; this guards legacy/hand-written docs).
|
|
194
|
+
*/
|
|
195
|
+
private requireRateBasis;
|
|
176
196
|
private calculateCategoryPoints;
|
|
177
197
|
private applyRounding;
|
|
178
198
|
}
|
|
@@ -184,8 +204,18 @@ interface Redemption {
|
|
|
184
204
|
programId: string;
|
|
185
205
|
pointsReserved: number;
|
|
186
206
|
pointsConfirmed: number;
|
|
207
|
+
/**
|
|
208
|
+
* Discount granted for the redeemed points — **integer minor units** (paisa
|
|
209
|
+
* for BDT). Invariant: always exactly `pointsReserved × pointValueMinor`
|
|
210
|
+
* (no orphaned remainder — see `RedemptionRepository.validate`).
|
|
211
|
+
*/
|
|
187
212
|
discountAmount: number;
|
|
188
|
-
|
|
213
|
+
/**
|
|
214
|
+
* RATE snapshot at reserve time: integer minor units ONE point redeems for
|
|
215
|
+
* (≥ 1). `pointValueMinor: 10` = 1 point → 10 paisa (৳0.10).
|
|
216
|
+
* Replaces the old `conversionRate` (points per 1 MAJOR unit).
|
|
217
|
+
*/
|
|
218
|
+
pointValueMinor: number;
|
|
189
219
|
status: RedemptionStatus;
|
|
190
220
|
ownerType: string;
|
|
191
221
|
ownerId: string;
|
|
@@ -200,11 +230,13 @@ interface Redemption {
|
|
|
200
230
|
transactionId?: string;
|
|
201
231
|
/**
|
|
202
232
|
* Optional voucher-like partial-use tracking populated by hosts when
|
|
203
|
-
* the redemption is backed by a gift voucher / reward card.
|
|
204
|
-
*
|
|
233
|
+
* the redemption is backed by a gift voucher / reward card. Both are
|
|
234
|
+
* **integer minor units**. When present,
|
|
235
|
+
* `reverseConfirmed({ proportional: true })` credits back
|
|
205
236
|
* `(remainingBalance / initialBalance) × pointsConfirmed`.
|
|
206
237
|
*/
|
|
207
238
|
initialBalance?: number;
|
|
239
|
+
/** Integer minor units. See `initialBalance`. */
|
|
208
240
|
remainingBalance?: number;
|
|
209
241
|
/**
|
|
210
242
|
* Optional multi-step approval chain (P7 canonical — same shape as
|
|
@@ -240,14 +272,40 @@ declare class RedemptionRepository extends Repository<Redemption> {
|
|
|
240
272
|
private get _dispatchDeps();
|
|
241
273
|
/**
|
|
242
274
|
* Atomic compare-and-set status transition.
|
|
243
|
-
*
|
|
275
|
+
*
|
|
276
|
+
* `null` means ONE thing and one thing only: the guarded update matched
|
|
277
|
+
* no document, i.e. the status was not `expectedStatus`. `throwOnNotFound:
|
|
278
|
+
* false` already produces that `null`, so there is nothing legitimate left
|
|
279
|
+
* for a `catch` to convert.
|
|
280
|
+
*
|
|
281
|
+
* There used to be a bare `} catch { return null; }` here. It laundered
|
|
282
|
+
* every real failure into a CAS miss, and callers map a CAS miss to
|
|
283
|
+
* `ValidationError("Redemption status must be 'reserved', got '<status>'")`
|
|
284
|
+
* — a NON-retryable 4xx built from the status read BEFORE the write. So a
|
|
285
|
+
* routine Mongo `WriteConflict` (a retryable transient, the normal outcome
|
|
286
|
+
* of two concurrent confirms) surfaced as a client error whose message
|
|
287
|
+
* could literally read "must be 'reserved', got 'reserved'": an operator
|
|
288
|
+
* chasing a contradiction while the actual fix was to retry.
|
|
289
|
+
*
|
|
290
|
+
* Anything that throws in here is a real error and must propagate.
|
|
244
291
|
*/
|
|
245
292
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Redemption | null>;
|
|
293
|
+
/**
|
|
294
|
+
* Validates a redemption request. `orderTotal` is an **integer in the
|
|
295
|
+
* host's minor currency unit** (paisa for BDT); the returned
|
|
296
|
+
* `discountAmount` is in the same minor units.
|
|
297
|
+
*/
|
|
246
298
|
validate(input: {
|
|
247
299
|
memberId: string;
|
|
248
300
|
pointsToRedeem: number;
|
|
249
301
|
orderTotal: number;
|
|
250
302
|
}, ctx: LoyaltyContext): Promise<RedemptionValidation>;
|
|
303
|
+
/**
|
|
304
|
+
* Reserves points against an order. `orderTotal` is an **integer in the
|
|
305
|
+
* host's minor currency unit** (paisa for BDT). The persisted
|
|
306
|
+
* `discountAmount` is minor units and the reserve-time `pointValueMinor`
|
|
307
|
+
* rate is snapshotted on the doc.
|
|
308
|
+
*/
|
|
251
309
|
reserve(input: {
|
|
252
310
|
memberId: string;
|
|
253
311
|
pointsToRedeem: number;
|
|
@@ -317,7 +375,16 @@ declare class ReferralRepository extends Repository<Referral> {
|
|
|
317
375
|
createMany(docs: Parameters<Repository<Referral>['createMany']>[0], options?: Parameters<Repository<Referral>['createMany']>[1]): Promise<Referral[]>;
|
|
318
376
|
/**
|
|
319
377
|
* Atomic compare-and-set status transition.
|
|
320
|
-
*
|
|
378
|
+
*
|
|
379
|
+
* `null` means ONE thing: the guarded update matched no document, i.e.
|
|
380
|
+
* the status was not `expectedStatus`. `throwOnNotFound: false` already
|
|
381
|
+
* returns that `null`, so the bare `} catch { return null; }` that used
|
|
382
|
+
* to sit here could only ever hide REAL errors — most damagingly a
|
|
383
|
+
* retryable Mongo `WriteConflict`, which callers then re-threw as a
|
|
384
|
+
* non-retryable `ValidationError` describing a status that had not
|
|
385
|
+
* actually gone stale.
|
|
386
|
+
*
|
|
387
|
+
* Anything that throws in here is a real error and must propagate.
|
|
321
388
|
*/
|
|
322
389
|
atomicStatusTransition(id: string, expectedStatus: string, data: Record<string, unknown>, ctx?: Record<string, unknown>): Promise<Referral | null>;
|
|
323
390
|
generateCode(memberId: string, ctx: LoyaltyContext): Promise<string>;
|
|
@@ -381,7 +448,7 @@ interface LoyaltyRepositories {
|
|
|
381
448
|
interface CreateRepositoriesInput {
|
|
382
449
|
models: LoyaltyModels;
|
|
383
450
|
plugins?: RepositoryPlugins | undefined;
|
|
384
|
-
tenant?:
|
|
451
|
+
tenant?: ResolvedTenantConfig | undefined;
|
|
385
452
|
config: ResolvedConfig;
|
|
386
453
|
events: EventTransport;
|
|
387
454
|
outbox?: OutboxStore | undefined;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { a as RuleStatus, n as MemberStatus, o as RuleType, s as TransactionType } from "./index-
|
|
1
|
+
import { a as RuleStatus, n as MemberStatus, o as RuleType, s as TransactionType } from "./index-D_Oe3aDk.mjs";
|
|
2
|
+
import "./config-CtkGj3xZ.mjs";
|
|
2
3
|
import { OperationContext } from "@classytic/primitives/context";
|
|
3
4
|
import { ApprovalChain } from "@classytic/primitives/approval";
|
|
4
|
-
|
|
5
5
|
//#region src/domain/entities/earning-rule.d.ts
|
|
6
6
|
interface EarningConditions {
|
|
7
|
+
/**
|
|
8
|
+
* Minimum order total for the rule to apply — **integer minor units** of the
|
|
9
|
+
* host's ambient currency (paisa for BDT). `10000` = ৳100. Compared against
|
|
10
|
+
* `evaluateOrder`'s `orderTotal` (also minor units).
|
|
11
|
+
*/
|
|
7
12
|
minOrderAmount?: number;
|
|
8
13
|
categories?: string[];
|
|
9
14
|
tiers?: string[];
|
|
@@ -12,7 +17,7 @@ interface EarningConditions {
|
|
|
12
17
|
* Days the rule applies, JS `getDay()` convention: 0=Sun … 6=Sat. Empty /
|
|
13
18
|
* omitted = every day. Evaluated against the wall-clock weekday in the
|
|
14
19
|
* engine's configured `timezone` (UTC when unset), never the server's local
|
|
15
|
-
* clock. See `
|
|
20
|
+
* clock. See `LoyaltyShape.timezone`.
|
|
16
21
|
*/
|
|
17
22
|
dayOfWeek?: number[];
|
|
18
23
|
dateRange?: {
|
|
@@ -21,7 +26,24 @@ interface EarningConditions {
|
|
|
21
26
|
};
|
|
22
27
|
}
|
|
23
28
|
interface EarningReward {
|
|
29
|
+
/**
|
|
30
|
+
* Points awarded per `rateBasis` minor units of spend:
|
|
31
|
+
* `points = pointsPerAmount × (orderTotalMinor / rateBasis)` (rounded once
|
|
32
|
+
* per `roundingMode`). REQUIRES `rateBasis` — there is no implicit
|
|
33
|
+
* "per 1 major unit" basis anymore (that ambiguity was the bug).
|
|
34
|
+
* "5 points per ৳100" = `{ pointsPerAmount: 5, rateBasis: 10000 }`.
|
|
35
|
+
*/
|
|
24
36
|
pointsPerAmount?: number;
|
|
37
|
+
/**
|
|
38
|
+
* RATE: the explicit integer minor-unit basis `pointsPerAmount` is quoted
|
|
39
|
+
* against (≥ 1). Required whenever `pointsPerAmount` is set.
|
|
40
|
+
*/
|
|
41
|
+
rateBasis?: number;
|
|
42
|
+
/**
|
|
43
|
+
* RATE: integer minor units of spend per single point (≥ 1).
|
|
44
|
+
* `points = orderTotalMinor / amountPerPoint` (rounded per `roundingMode`).
|
|
45
|
+
* "1 point per ৳100" = `amountPerPoint: 10000`.
|
|
46
|
+
*/
|
|
25
47
|
amountPerPoint?: number;
|
|
26
48
|
fixedPoints?: number;
|
|
27
49
|
multiplier?: number;
|
|
@@ -48,6 +70,11 @@ interface EarningRule {
|
|
|
48
70
|
//#region src/domain/entities/tier-definition.d.ts
|
|
49
71
|
interface TierQualification {
|
|
50
72
|
minLifetimePoints?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Minimum lifetime spend — **integer minor units** (paisa for BDT).
|
|
75
|
+
* Declared for host-side qualification logic only: the in-package tier
|
|
76
|
+
* evaluation checks lifetime POINTS exclusively and never reads this field.
|
|
77
|
+
*/
|
|
51
78
|
minLifetimeSpend?: number;
|
|
52
79
|
evaluationPeriodDays?: number;
|
|
53
80
|
}
|
|
@@ -178,6 +205,10 @@ interface RedemptionValidation {
|
|
|
178
205
|
valid: boolean;
|
|
179
206
|
error?: string;
|
|
180
207
|
pointsToRedeem: number;
|
|
208
|
+
/**
|
|
209
|
+
* Discount in **integer minor units** (paisa for BDT) — always exactly
|
|
210
|
+
* `pointsToRedeem × pointValueMinor`.
|
|
211
|
+
*/
|
|
181
212
|
discountAmount: number;
|
|
182
213
|
maxAllowedPoints: number;
|
|
183
214
|
}
|
|
@@ -278,7 +309,7 @@ interface CreateEarningRuleInput {
|
|
|
278
309
|
* Loyalty program this rule belongs to.
|
|
279
310
|
*
|
|
280
311
|
* Optional for repository-created rules: `EarningRuleRepository.create(...)`
|
|
281
|
-
* and `createMany(...)` fill this from `
|
|
312
|
+
* and `createMany(...)` fill this from `LoyaltyShape.program.id`, matching
|
|
282
313
|
* the default used by `MemberRepository.enroll(...)`. Pass explicitly only
|
|
283
314
|
* when creating a rule for a different program.
|
|
284
315
|
*/
|