@classytic/loyalty 0.1.0 → 0.2.1

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/config-DoZPGBfW.d.mts +136 -0
  3. package/dist/{constants-C75GvizO.mjs → constants-DkQYkOK1.mjs} +3 -2
  4. package/dist/domain/enums/index.d.mts +1 -1
  5. package/dist/domain/enums/index.mjs +1 -1
  6. package/dist/events/index.d.mts +2 -2
  7. package/dist/events/index.mjs +3 -3
  8. package/dist/events-BtZOdiPw.mjs +311 -0
  9. package/dist/index-BOWqBSWx.d.mts +281 -0
  10. package/dist/index-C4jFHS7m.d.mts +400 -0
  11. package/dist/index-ClPKmtxh.d.mts +361 -0
  12. package/dist/{index-CaH4GhSF.d.mts → index-DMK8a98J.d.mts} +1 -1
  13. package/dist/index.d.mts +28 -12
  14. package/dist/index.mjs +30 -26
  15. package/dist/loyalty-events-Cyt9TwX6.mjs +50 -0
  16. package/dist/models/index.d.mts +1225 -2
  17. package/dist/models/index.mjs +2 -2
  18. package/dist/{models-CrZPmVxt.mjs → models-uhh9c-7k.mjs} +104 -41
  19. package/dist/notifications/index.d.mts +1 -1
  20. package/dist/repositories/index.d.mts +2 -2
  21. package/dist/repositories/index.mjs +1 -1
  22. package/dist/repositories-DBSxe9st.mjs +1919 -0
  23. package/dist/types/index.d.mts +2 -2
  24. package/package.json +22 -17
  25. package/dist/config-aNigy4ec.d.mts +0 -90
  26. package/dist/domain/index.d.mts +0 -90
  27. package/dist/domain/index.mjs +0 -4
  28. package/dist/domain-errors-D7q-2LYD.mjs +0 -120
  29. package/dist/event-emitter.port-CLYPoMni.d.mts +0 -8
  30. package/dist/event-types-xQBtYBbX.mjs +0 -24
  31. package/dist/events-CprEWlN7.mjs +0 -25
  32. package/dist/index-B6ZstxSP.d.mts +0 -2357
  33. package/dist/index-BcoGeDco.d.mts +0 -127
  34. package/dist/index-DqzTjeEB.d.mts +0 -148
  35. package/dist/index-O7TL4R2P.d.mts +0 -86
  36. package/dist/index-_pg4J11t.d.mts +0 -191
  37. package/dist/repositories-qMX7Xh_y.mjs +0 -440
  38. package/dist/services/index.d.mts +0 -2
  39. package/dist/services/index.mjs +0 -2
  40. package/dist/services-CoTqC4E6.mjs +0 -1098
  41. package/dist/tier-definition-Djf72UPa.d.mts +0 -120
  42. package/dist/tier-definition.port-BzrHYBRo.d.mts +0 -143
  43. /package/dist/{notification.port-BzUsH0It.d.mts → notification.port-CJ9nXj0k.d.mts} +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
+ adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.2.0]
7
+
8
+ Structural rewrite on top of the 0.1.0 shape. The public API is now a
9
+ smaller, sharper surface organized around seven subpath exports
10
+ (`/domain/enums`, `/models`, `/repositories`, `/events`, `/types`,
11
+ `/notifications`, plus the root engine factory). Legacy barrel + service
12
+ layers were removed; hosts wire business flows directly against the
13
+ repositories and the engine factory.
14
+
15
+ ### Changed
16
+
17
+ - **Domain restructure.** Dropped the `src/domain/ports/` + `src/services/`
18
+ layer in favor of thin repositories that expose exactly the operations
19
+ hosts need — `earn`, `redeem`, `adjust`, `expire`, referral credit,
20
+ tier recomputation. No more `earning.service` / `redemption.service` /
21
+ `tier.service` / `referral.service` / `ledger.service` / `member.service`
22
+ / `member-guard` indirections; the equivalent logic lives on the
23
+ repositories where it's naturally atomic.
24
+ - **Events subsystem simplified.** Removed `events/event-bus.ts` +
25
+ `events/event-types.ts` (both are internal indirection that the engine
26
+ didn't need). Event payloads are now colocated with the repository
27
+ that emits them.
28
+ - **Repositories updated to mongokit 3.11 + repo-core 0.2 conventions.**
29
+ Every repo extends `Repository<T>` directly, uses the tenant plugin via
30
+ `injectTenant`, and atomic state changes go through CAS guards.
31
+ - **Peer deps** unchanged in spirit, tightened in range:
32
+ - `@classytic/mongokit >= 3.11`
33
+ - `@classytic/primitives >= 0.1`
34
+ - `@classytic/repo-core >= 0.2`
35
+ - `mongoose >= 9.4`, `zod >= 4`
36
+ - **DevDeps:** `@classytic/primitives` devDep moved from `file:` to `>=0.1.0`
37
+ (now that primitives ships on npm); all `@classytic/*` dev ranges
38
+ standardized on `>=`.
39
+
40
+ ### Removed
41
+
42
+ - `src/services/*` — logic folded into repositories.
43
+ - `src/domain/ports/*` barrels and per-port files — structural types
44
+ now live with the repositories they describe.
45
+ - Event-bus indirection — repositories emit via the engine's configured
46
+ transport directly.
47
+
48
+ ### Added
49
+
50
+ - Subpath export `/notifications` — notification port + built-in
51
+ lifecycle payloads (tier up/down, points expiring, referral credit).
52
+ - Test restructure — 257 tests across unit + integration projects; the
53
+ deleted legacy suites were collapsed into focused scenario files that
54
+ exercise the new public API.
55
+
56
+ ### Peer compatibility
57
+
58
+ Anyone on 0.1.0 wiring against the old `*.service.ts` modules will need
59
+ to migrate to the repository methods. Signatures are similar — earn /
60
+ redeem / adjust remain the nouns — but the call sites move from
61
+ `services.earning.earnPoints(...)` to `repositories.pointTransaction.earn(...)`
62
+ and similar. No behavior changes in the ledger semantics.
63
+
64
+ ## [0.1.0]
65
+
66
+ Initial release. Loyalty points, tiers, earning rules, referrals, and
67
+ redemption engine for MongoDB. Tenant-scoped, event-emitting, atomic
68
+ point-ledger with CAS guards.
@@ -0,0 +1,136 @@
1
+ import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
2
+ import { EventTransport } from "@classytic/primitives/events";
3
+ import { Connection } from "mongoose";
4
+ import { OutboxStore } from "@classytic/primitives/outbox";
5
+ import { TenantConfig, TenantFieldType } from "@classytic/primitives/tenant";
6
+
7
+ //#region src/types/config.d.ts
8
+ type LoyaltyModelName = 'Member' | 'PointTransaction' | 'EarningRule' | 'TierDefinition' | 'Redemption' | 'Referral';
9
+ type TenantConfig$1 = false | TenantConfig;
10
+ interface IndexDefinition {
11
+ fields: Record<string, 1 | -1>;
12
+ options?: Record<string, unknown> | undefined;
13
+ }
14
+ type ModelName = 'member' | 'pointTransaction' | 'earningRule' | 'tierDefinition' | 'redemption' | 'referral';
15
+ type PluginType = unknown;
16
+ interface RepositoryPlugins {
17
+ member?: PluginType[] | undefined;
18
+ pointTransaction?: PluginType[] | undefined;
19
+ earningRule?: PluginType[] | undefined;
20
+ tierDefinition?: PluginType[] | undefined;
21
+ redemption?: PluginType[] | undefined;
22
+ referral?: PluginType[] | undefined;
23
+ }
24
+ interface LoyaltyConfig {
25
+ mongoose: Connection;
26
+ tenant?: TenantConfig$1 | undefined;
27
+ program?: {
28
+ id?: string | undefined;
29
+ conversionRate?: number | undefined;
30
+ } | undefined;
31
+ redemption?: {
32
+ minPoints?: number | undefined;
33
+ minOrderAmount?: number | undefined;
34
+ maxRedeemPercent?: number | undefined;
35
+ reservationTtlMinutes?: number | undefined;
36
+ } | undefined;
37
+ referral?: {
38
+ referrerRewardPoints?: number | undefined;
39
+ refereeRewardPoints?: number | undefined;
40
+ maxPerPeriod?: number | undefined;
41
+ periodDays?: number | undefined;
42
+ requireApproval?: boolean | undefined;
43
+ codeLength?: number | undefined;
44
+ /**
45
+ * Maximum ancestor depth for multi-tier referral rewards. When >= 2,
46
+ * the direct referrer gets `tierRates[0]` percent of
47
+ * `referrerRewardPoints`, the 2nd-tier ancestor gets `tierRates[1]`,
48
+ * etc. Depth 1 = direct referrer (matches single-hop legacy path).
49
+ * Default: 1 (single-hop, preserves prior behaviour).
50
+ */
51
+ maxDepth?: number | undefined;
52
+ /**
53
+ * Percent rates (0–100) per depth. `tierRates[0]` is the direct
54
+ * referrer's share. Omitted depths fall back to 0. Example:
55
+ * `{ maxDepth: 3, tierRates: [100, 50, 25] }` — direct gets 100%,
56
+ * grand-referrer 50%, great-grand 25%.
57
+ */
58
+ tierRates?: number[] | undefined;
59
+ } | undefined;
60
+ expiry?: {
61
+ enabled?: boolean | undefined;
62
+ defaultDays?: number | undefined;
63
+ } | undefined;
64
+ indexes?: Partial<Record<ModelName, IndexDefinition[]>> | undefined;
65
+ autoIndex?: boolean | Partial<Record<LoyaltyModelName, boolean>> | undefined;
66
+ /**
67
+ * Optional prefix prepended to every physical collection this package
68
+ * creates (see PACKAGE_RULES.md §20.1). Unset → default names
69
+ * (`loyalty_members`, `loyalty_point_transactions`, `loyalty_earning_rules`,
70
+ * `loyalty_tier_definitions`, `loyalty_redemptions`, `loyalty_referrals`).
71
+ * Model names and `ref:` populate are unaffected.
72
+ */
73
+ collectionPrefix?: string | undefined;
74
+ /**
75
+ * When true, existing Mongoose models with loyalty's names are deleted
76
+ * from the connection before re-registering. Hot-reload / test fixtures
77
+ * only. Default `false` — collision throws `LoyaltyModelCollisionError`.
78
+ * Hosts that need two loyalty engines should use two Mongoose connections
79
+ * (`mongoose.createConnection(...)`). See PACKAGE_RULES.md §21.
80
+ */
81
+ forceRecreate?: boolean | undefined;
82
+ /**
83
+ * Arc-compatible event transport. When omitted, the engine uses
84
+ * `InProcessLoyaltyBus` — a ~60-line structural match of arc's
85
+ * `MemoryEventTransport`. See PACKAGE_RULES §13.
86
+ */
87
+ eventTransport?: EventTransport | undefined;
88
+ /**
89
+ * Host-owned outbox store (PACKAGE_RULES §5.5 + P8). When present, every
90
+ * domain event is persisted via `outbox.save(event, { session })` **before**
91
+ * `eventTransport.publish(event)`. `session` is threaded from `ctx.session`
92
+ * so the outbox row commits atomically with the business write.
93
+ *
94
+ * Loyalty does NOT ship a store. Hosts wire arc's `repositoryAsOutboxStore(mongokitRepo)`
95
+ * over their own `event_outbox` collection, arc's `MemoryOutboxStore`, or any
96
+ * custom Postgres / Redis / Kafka implementation of the contract. When
97
+ * omitted, events flow through `eventTransport` only (no durable log).
98
+ */
99
+ outbox?: OutboxStore | undefined;
100
+ notifications?: NotificationAdapter | undefined;
101
+ plugins?: RepositoryPlugins | undefined;
102
+ }
103
+ interface ResolvedTenant {
104
+ enabled: boolean;
105
+ tenantField: string;
106
+ ref?: string;
107
+ fieldType: TenantFieldType;
108
+ contextKey: string;
109
+ }
110
+ interface ResolvedConfig {
111
+ programId: string;
112
+ conversionRate: number;
113
+ redemption: {
114
+ minPoints: number;
115
+ minOrderAmount: number;
116
+ maxRedeemPercent: number;
117
+ reservationTtlMinutes: number;
118
+ };
119
+ referral: {
120
+ referrerRewardPoints: number;
121
+ refereeRewardPoints: number;
122
+ maxPerPeriod: number;
123
+ periodDays: number;
124
+ requireApproval: boolean;
125
+ codeLength: number;
126
+ maxDepth: number;
127
+ tierRates: number[];
128
+ };
129
+ expiry: {
130
+ enabled: boolean;
131
+ defaultDays?: number;
132
+ };
133
+ tenant: ResolvedTenant;
134
+ }
135
+ //#endregion
136
+ export { PluginType as a, ResolvedTenant as c, ModelName as i, TenantConfig$1 as l, LoyaltyConfig as n, RepositoryPlugins as o, LoyaltyModelName as r, ResolvedConfig as s, IndexDefinition as t };
@@ -1,6 +1,6 @@
1
1
  //#region src/constants.ts
2
2
  const DEFAULT_TENANT_FIELD = "organizationId";
3
- const DEFAULT_TENANT_TYPE = "ObjectId";
3
+ const DEFAULT_TENANT_TYPE = "objectId";
4
4
  const DEFAULT_PROGRAM_ID = "default";
5
5
  const TRANSACTION_TYPES = Object.freeze([
6
6
  "earn",
@@ -36,7 +36,8 @@ const REDEMPTION_STATUSES = Object.freeze([
36
36
  "reserved",
37
37
  "confirmed",
38
38
  "released",
39
- "expired"
39
+ "expired",
40
+ "reversed"
40
41
  ]);
41
42
  const EARNING_ACTIONS = Object.freeze([
42
43
  "signup",
@@ -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-CaH4GhSF.mjs";
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-DMK8a98J.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 MEMBER_STATUSES, c as RULE_STATUSES, i as EARNING_ACTIONS, l as RULE_TYPES, o as REDEMPTION_STATUSES, s as REFERRAL_STATUSES, u as TRANSACTION_TYPES } from "../../constants-C75GvizO.mjs";
1
+ import { a as MEMBER_STATUSES, c as RULE_STATUSES, i as EARNING_ACTIONS, l as RULE_TYPES, o as REDEMPTION_STATUSES, s as REFERRAL_STATUSES, u as TRANSACTION_TYPES } from "../../constants-DkQYkOK1.mjs";
2
2
  export { EARNING_ACTIONS, MEMBER_STATUSES, REDEMPTION_STATUSES, REFERRAL_STATUSES, RULE_STATUSES, RULE_TYPES, TRANSACTION_TYPES };
@@ -1,2 +1,2 @@
1
- import { a as PointsEarnedPayload, c as TierChangedPayload, i as PointsAdjustedPayload, l as InProcessEventBus, n as LoyaltyEvents, o as RedemptionPayload, r as MemberLifecyclePayload, s as ReferralPayload, t as LoyaltyEvent } from "../index-O7TL4R2P.mjs";
2
- export { InProcessEventBus, LoyaltyEvent, LoyaltyEvents, MemberLifecyclePayload, PointsAdjustedPayload, PointsEarnedPayload, RedemptionPayload, ReferralPayload, TierChangedPayload };
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-C4jFHS7m.mjs";
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,3 +1,3 @@
1
- import { t as InProcessEventBus } from "../events-CprEWlN7.mjs";
2
- import { t as LoyaltyEvents } from "../event-types-xQBtYBbX.mjs";
3
- export { InProcessEventBus, LoyaltyEvents };
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-BtZOdiPw.mjs";
2
+ import { n as createEvent, t as LoyaltyEvents } from "../loyalty-events-Cyt9TwX6.mjs";
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 };
@@ -0,0 +1,311 @@
1
+ import { t as LoyaltyEvents } from "./loyalty-events-Cyt9TwX6.mjs";
2
+ import { createEvent, matchEventPattern } from "@classytic/primitives/events";
3
+ import { z } from "zod";
4
+ //#region src/events/in-process-bus.ts
5
+ var InProcessLoyaltyBus = class {
6
+ name = "in-process-loyalty";
7
+ handlers = /* @__PURE__ */ new Map();
8
+ logger;
9
+ constructor(options) {
10
+ this.logger = options?.logger ?? console;
11
+ }
12
+ async publish(event) {
13
+ const matched = /* @__PURE__ */ new Set();
14
+ for (const [pattern, set] of this.handlers.entries()) if (matchEventPattern(pattern, event.type)) for (const h of set) matched.add(h);
15
+ for (const handler of matched) try {
16
+ await handler(event);
17
+ } catch (err) {
18
+ this.logger.error(`[InProcessLoyaltyBus] Handler error for ${event.type}:`, err);
19
+ }
20
+ }
21
+ async subscribe(pattern, handler) {
22
+ if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
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();
33
+ }
34
+ };
35
+ //#endregion
36
+ //#region src/events/loyalty-event-catalog.ts
37
+ function defineLoyaltyEvent(input) {
38
+ const { name, version = 1, description, zodSchema } = input;
39
+ const def = {
40
+ name,
41
+ version,
42
+ schema: z.toJSONSchema(zodSchema),
43
+ zodSchema,
44
+ create(payload, meta) {
45
+ return createEvent(name, payload, {
46
+ source: "loyalty",
47
+ ...meta
48
+ });
49
+ }
50
+ };
51
+ if (description !== void 0) def.description = description;
52
+ return def;
53
+ }
54
+ /** Mirrors `MemberLifecyclePayload`. */
55
+ const memberLifecycleSchema = z.object({
56
+ memberId: z.string(),
57
+ externalId: z.string(),
58
+ externalType: z.string(),
59
+ status: z.string()
60
+ });
61
+ /** Mirrors `PointsEarnedPayload`. */
62
+ const pointsEarnedSchema = z.object({
63
+ memberId: z.string(),
64
+ externalId: z.string(),
65
+ externalType: z.string(),
66
+ points: z.number(),
67
+ balanceAfter: z.number(),
68
+ transactionId: z.string(),
69
+ earningRuleName: z.string().optional(),
70
+ referenceType: z.string().optional(),
71
+ referenceId: z.string().optional()
72
+ });
73
+ /** Mirrors `PointsAdjustedPayload` — reused by REDEEMED + ADJUSTED. */
74
+ const pointsAdjustedSchema = z.object({
75
+ memberId: z.string(),
76
+ externalId: z.string(),
77
+ points: z.number(),
78
+ balanceAfter: z.number(),
79
+ transactionId: z.string(),
80
+ description: z.string()
81
+ });
82
+ /** Mirrors `PointsExpiredPayload` — reused by EXPIRED + EXPIRING_SOON. */
83
+ const pointsExpiredSchema = z.object({
84
+ membersAffected: z.number().int().nonnegative(),
85
+ pointsExpired: z.number().int().nonnegative(),
86
+ transactionCount: z.number().int().nonnegative()
87
+ });
88
+ /** Mirrors `TierChangedPayload` — reused by UPGRADED + DOWNGRADED + GRACE_STARTED. */
89
+ const tierChangedSchema = z.object({
90
+ memberId: z.string(),
91
+ externalId: z.string(),
92
+ previousTier: z.string().nullable(),
93
+ newTier: z.string().nullable(),
94
+ lifetimePoints: z.number()
95
+ });
96
+ /** Mirrors `RedemptionPayload` — reused by RESERVED + CONFIRMED + RELEASED. */
97
+ const redemptionSchema = z.object({
98
+ memberId: z.string(),
99
+ externalId: z.string(),
100
+ redemptionId: z.string(),
101
+ points: z.number(),
102
+ discountAmount: z.number(),
103
+ ownerType: z.string(),
104
+ ownerId: z.string()
105
+ });
106
+ /** Mirrors `ReferralPayload` — reused across the referral lifecycle. */
107
+ const referralSchema = z.object({
108
+ referralId: z.string(),
109
+ referrerId: z.string(),
110
+ refereeId: z.string(),
111
+ referralCode: z.string(),
112
+ status: z.string(),
113
+ referrerRewardPoints: z.number().optional(),
114
+ refereeRewardPoints: z.number().optional()
115
+ });
116
+ /**
117
+ * Mirrors `TierOverrideLifecyclePayload`. `expiredAt` is serialised as ISO
118
+ * string at transport time (Dates aren't JSON-Schema representable —
119
+ * see zod/v4's `Date cannot be represented in JSON Schema` rule).
120
+ */
121
+ const tierOverrideLifecycleSchema = z.object({
122
+ memberId: z.string(),
123
+ externalId: z.string(),
124
+ tier: z.string(),
125
+ reason: z.string().optional(),
126
+ expiredAt: z.string().optional()
127
+ });
128
+ /** Mirrors `RedemptionReversedPayload`. */
129
+ const redemptionReversedSchema = z.object({
130
+ memberId: z.string(),
131
+ externalId: z.string(),
132
+ redemptionId: z.string(),
133
+ pointsCredited: z.number(),
134
+ originalPoints: z.number(),
135
+ proportional: z.boolean(),
136
+ reason: z.string().optional(),
137
+ transactionId: z.string()
138
+ });
139
+ /** Mirrors `CampaignAwardedPayload`. */
140
+ const campaignAwardedSchema = z.object({
141
+ campaignId: z.string(),
142
+ memberCount: z.number().int().nonnegative(),
143
+ awarded: z.number().int().nonnegative(),
144
+ skipped: z.number().int().nonnegative(),
145
+ totalPoints: z.number().int().nonnegative()
146
+ });
147
+ /** Mirrors `MultiTierReferralAwardedPayload`. */
148
+ const multiTierReferralAwardedSchema = z.object({
149
+ referralId: z.string(),
150
+ refereeId: z.string(),
151
+ awards: z.array(z.object({
152
+ memberId: z.string(),
153
+ depth: z.number().int().positive(),
154
+ points: z.number().int().nonnegative(),
155
+ transactionId: z.string()
156
+ }))
157
+ });
158
+ const MemberEnrolled = defineLoyaltyEvent({
159
+ name: LoyaltyEvents.MEMBER_ENROLLED,
160
+ description: "A new loyalty member was enrolled in a program.",
161
+ zodSchema: memberLifecycleSchema
162
+ });
163
+ const MemberDeactivated = defineLoyaltyEvent({
164
+ name: LoyaltyEvents.MEMBER_DEACTIVATED,
165
+ description: "A loyalty member was deactivated.",
166
+ zodSchema: memberLifecycleSchema
167
+ });
168
+ const MemberReactivated = defineLoyaltyEvent({
169
+ name: LoyaltyEvents.MEMBER_REACTIVATED,
170
+ description: "A deactivated loyalty member was reactivated.",
171
+ zodSchema: memberLifecycleSchema
172
+ });
173
+ const MemberSuspended = defineLoyaltyEvent({
174
+ name: LoyaltyEvents.MEMBER_SUSPENDED,
175
+ description: "A loyalty member was suspended.",
176
+ zodSchema: memberLifecycleSchema
177
+ });
178
+ const PointsEarned = defineLoyaltyEvent({
179
+ name: LoyaltyEvents.POINTS_EARNED,
180
+ description: "Points were earned by a member through an earning rule.",
181
+ zodSchema: pointsEarnedSchema
182
+ });
183
+ const PointsRedeemed = defineLoyaltyEvent({
184
+ name: LoyaltyEvents.POINTS_REDEEMED,
185
+ description: "Points were redeemed against a discount / reward.",
186
+ zodSchema: pointsAdjustedSchema
187
+ });
188
+ const PointsAdjusted = defineLoyaltyEvent({
189
+ name: LoyaltyEvents.POINTS_ADJUSTED,
190
+ description: "Points were manually adjusted (admin correction / override).",
191
+ zodSchema: pointsAdjustedSchema
192
+ });
193
+ const PointsExpired = defineLoyaltyEvent({
194
+ name: LoyaltyEvents.POINTS_EXPIRED,
195
+ description: "A batch of points expired across one or more members.",
196
+ zodSchema: pointsExpiredSchema
197
+ });
198
+ const PointsExpiringSoon = defineLoyaltyEvent({
199
+ name: LoyaltyEvents.POINTS_EXPIRING_SOON,
200
+ description: "Points are approaching their expiry window — host can notify members.",
201
+ zodSchema: pointsExpiredSchema
202
+ });
203
+ const TierUpgraded = defineLoyaltyEvent({
204
+ name: LoyaltyEvents.TIER_UPGRADED,
205
+ description: "A member qualified for a higher tier.",
206
+ zodSchema: tierChangedSchema
207
+ });
208
+ const TierDowngraded = defineLoyaltyEvent({
209
+ name: LoyaltyEvents.TIER_DOWNGRADED,
210
+ description: "A member dropped to a lower tier after grace expiry.",
211
+ zodSchema: tierChangedSchema
212
+ });
213
+ const TierGraceStarted = defineLoyaltyEvent({
214
+ name: LoyaltyEvents.TIER_GRACE_STARTED,
215
+ description: "A tier grace period was started on a member before potential downgrade.",
216
+ zodSchema: tierChangedSchema
217
+ });
218
+ const TierOverrideCleared = defineLoyaltyEvent({
219
+ name: LoyaltyEvents.TIER_OVERRIDE_CLEARED,
220
+ description: "A manual tier override was explicitly cleared (admin action).",
221
+ zodSchema: tierOverrideLifecycleSchema
222
+ });
223
+ const TierOverrideExpired = defineLoyaltyEvent({
224
+ name: LoyaltyEvents.TIER_OVERRIDE_EXPIRED,
225
+ description: "A tier override with a TTL expired and was swept by cron.",
226
+ zodSchema: tierOverrideLifecycleSchema
227
+ });
228
+ const RedemptionReserved = defineLoyaltyEvent({
229
+ name: LoyaltyEvents.REDEMPTION_RESERVED,
230
+ description: "Points were reserved against a pending redemption.",
231
+ zodSchema: redemptionSchema
232
+ });
233
+ const RedemptionConfirmed = defineLoyaltyEvent({
234
+ name: LoyaltyEvents.REDEMPTION_CONFIRMED,
235
+ description: "A reserved redemption was confirmed — points debited from balance.",
236
+ zodSchema: redemptionSchema
237
+ });
238
+ const RedemptionReleased = defineLoyaltyEvent({
239
+ name: LoyaltyEvents.REDEMPTION_RELEASED,
240
+ description: "A reserved redemption was released — points returned to balance.",
241
+ zodSchema: redemptionSchema
242
+ });
243
+ const RedemptionReversed = defineLoyaltyEvent({
244
+ name: LoyaltyEvents.REDEMPTION_REVERSED,
245
+ description: "A confirmed redemption was reversed — points (fully or partially) credited back.",
246
+ zodSchema: redemptionReversedSchema
247
+ });
248
+ const CampaignAwarded = defineLoyaltyEvent({
249
+ name: LoyaltyEvents.CAMPAIGN_AWARDED,
250
+ description: "A bulk-earn campaign awarded points to a batch of members.",
251
+ zodSchema: campaignAwardedSchema
252
+ });
253
+ const ReferralRecorded = defineLoyaltyEvent({
254
+ name: LoyaltyEvents.REFERRAL_RECORDED,
255
+ description: "A referral was recorded (pending approval when required).",
256
+ zodSchema: referralSchema
257
+ });
258
+ const ReferralApproved = defineLoyaltyEvent({
259
+ name: LoyaltyEvents.REFERRAL_APPROVED,
260
+ description: "A pending referral was approved.",
261
+ zodSchema: referralSchema
262
+ });
263
+ const ReferralRejected = defineLoyaltyEvent({
264
+ name: LoyaltyEvents.REFERRAL_REJECTED,
265
+ description: "A pending referral was rejected.",
266
+ zodSchema: referralSchema
267
+ });
268
+ const ReferralRewarded = defineLoyaltyEvent({
269
+ name: LoyaltyEvents.REFERRAL_REWARDED,
270
+ description: "Referrer and / or referee rewards were awarded for an approved referral.",
271
+ zodSchema: referralSchema
272
+ });
273
+ const MultiTierReferralAwarded = defineLoyaltyEvent({
274
+ name: LoyaltyEvents.MULTI_TIER_REFERRAL_AWARDED,
275
+ description: "Ancestor referrers were awarded points via a multi-tier referral chain.",
276
+ zodSchema: multiTierReferralAwardedSchema
277
+ });
278
+ /**
279
+ * Every loyalty event defined in the package — pass to Arc's
280
+ * `EventRegistry`. Hosts wire ONE array; the whole `loyalty.*` namespace
281
+ * becomes introspectable via OpenAPI and auto-validated at publish time
282
+ * when `eventPlugin({ validateMode: 'reject' })` is set.
283
+ */
284
+ const loyaltyEventDefinitions = [
285
+ MemberEnrolled,
286
+ MemberDeactivated,
287
+ MemberReactivated,
288
+ MemberSuspended,
289
+ PointsEarned,
290
+ PointsRedeemed,
291
+ PointsAdjusted,
292
+ PointsExpired,
293
+ PointsExpiringSoon,
294
+ TierUpgraded,
295
+ TierDowngraded,
296
+ TierGraceStarted,
297
+ TierOverrideCleared,
298
+ TierOverrideExpired,
299
+ RedemptionReserved,
300
+ RedemptionConfirmed,
301
+ RedemptionReleased,
302
+ RedemptionReversed,
303
+ CampaignAwarded,
304
+ ReferralRecorded,
305
+ ReferralApproved,
306
+ ReferralRejected,
307
+ ReferralRewarded,
308
+ MultiTierReferralAwarded
309
+ ];
310
+ //#endregion
311
+ export { TierOverrideExpired as C, InProcessLoyaltyBus as E, TierOverrideCleared as S, loyaltyEventDefinitions as T, ReferralRecorded as _, MemberSuspended as a, TierDowngraded as b, PointsEarned as c, PointsRedeemed as d, RedemptionConfirmed as f, ReferralApproved as g, RedemptionReversed as h, MemberReactivated as i, PointsExpired as l, RedemptionReserved as m, MemberDeactivated as n, MultiTierReferralAwarded as o, RedemptionReleased as p, MemberEnrolled as r, PointsAdjusted as s, CampaignAwarded as t, PointsExpiringSoon as u, ReferralRejected as v, TierUpgraded as w, TierGraceStarted as x, ReferralRewarded as y };