@classytic/loyalty 0.2.1 → 0.2.3

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 CHANGED
@@ -3,6 +3,39 @@
3
3
  Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
4
  adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## [0.2.3] — 2026-05-08
7
+
8
+ ### Fixed
9
+
10
+ - **§P8 dispatch — `outbox.save` failures now propagate.** [src/events/dispatch.ts](src/events/dispatch.ts)
11
+ previously caught `outbox.save` errors and swallowed them silently. Per
12
+ PACKAGE_RULES §P8 the outbox row is the durability anchor for the
13
+ transactional-outbox pattern: if the save fails, the caller's transaction
14
+ MUST roll back so the business doc and the event row land atomically.
15
+ Swallowing the failure meant a parent doc could commit while the event
16
+ vanished, with no relay row to recover from. Fix re-throws after logging.
17
+ `events.publish` failures continue to be swallowed (correctly, per §P8 —
18
+ the host's outbox relay re-publishes from the durable row), but now log
19
+ before swallowing so failures are observable.
20
+ - **`DispatchDeps.logger` field added.** Optional. Both arms of the
21
+ dispatch helper log through it when set, surfacing failures in the host's
22
+ observability stack without coupling the package to a specific logger.
23
+
24
+ ### Changed
25
+
26
+ - **Peer-dep ranges tightened** — `@classytic/mongokit` `>=3.13.3`
27
+ (was `>=3.13.0`) and `@classytic/repo-core` `>=0.4.2` (was `>=0.4.0`).
28
+ 3.13.3 preserves `errorLabels` on `MongoServerError` so concurrent
29
+ transactional writes (the `claim()`-based redemption + tier-eval paths)
30
+ retry correctly under contention; 0.4.2 adds the typed `FindAllOptions`
31
+ shape with `limit?` for callers that need a bounded non-paginated read.
32
+
33
+ ### Internal
34
+
35
+ - Replaced 4 `arr.find()!` non-null assertions in [src/index.ts](src/index.ts)
36
+ + scenario tests with explicit narrowing — TS now proves what the runtime
37
+ shape already guaranteed.
38
+
6
39
  ## [0.2.0]
7
40
 
8
41
  Structural rewrite on top of the 0.1.0 shape. The public API is now a
package/README.md CHANGED
@@ -139,6 +139,13 @@ import type {
139
139
  } from '@classytic/loyalty';
140
140
  ```
141
141
 
142
+ ## Migration Notes
143
+
144
+ Earning rates now live in earning rules, not host membership config fields such
145
+ as `PlatformConfig.membership.pointsPerAmount` or `amountPerPoint`. See
146
+ [docs/config-engine-migration.md](docs/config-engine-migration.md) for the
147
+ `LoyaltyConfig` to engine boundary and migration checklist.
148
+
142
149
  ## Events
143
150
 
144
151
  ```typescript
@@ -2,7 +2,7 @@ import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
2
2
  import { EventTransport } from "@classytic/primitives/events";
3
3
  import { Connection } from "mongoose";
4
4
  import { OutboxStore } from "@classytic/primitives/outbox";
5
- import { TenantConfig, TenantFieldType } from "@classytic/primitives/tenant";
5
+ import { TenantConfig, TenantFieldType } from "@classytic/repo-core/tenant";
6
6
 
7
7
  //#region src/types/config.d.ts
8
8
  type LoyaltyModelName = 'Member' | 'PointTransaction' | 'EarningRule' | 'TierDefinition' | 'Redemption' | 'Referral';
@@ -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-C4jFHS7m.mjs";
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-DArT4zkB.mjs";
2
2
  export { CampaignAwarded, CampaignAwardedPayload, DomainEvent, EventHandler, EventTransport, InProcessLoyaltyBus, InProcessLoyaltyBusOptions, LoyaltyEventDefinition, LoyaltyEventMap, LoyaltyEventName, LoyaltyEventPayload, LoyaltyEventPayloadOf, LoyaltyEventSchema, LoyaltyEvents, MemberDeactivated, MemberEnrolled, MemberLifecyclePayload, MemberReactivated, MemberSuspended, MultiTierReferralAwarded, MultiTierReferralAwardedPayload, PointsAdjusted, PointsAdjustedPayload, PointsEarned, PointsEarnedPayload, PointsExpired, PointsExpiredPayload, PointsExpiringSoon, PointsRedeemed, RedemptionConfirmed, RedemptionPayload, RedemptionReleased, RedemptionReserved, RedemptionReversed, RedemptionReversedPayload, ReferralApproved, ReferralPayload, ReferralRecorded, ReferralRejected, ReferralRewarded, TierChangedPayload, TierDowngraded, TierGraceStarted, TierOverrideCleared, TierOverrideExpired, TierOverrideLifecyclePayload, TierUpgraded, createEvent, loyaltyEventDefinitions };
@@ -1,12 +1,13 @@
1
1
  import { i as ReferralStatus, r as RedemptionStatus } from "./index-DMK8a98J.mjs";
2
- import { S as EarningRule, _ as TierEvaluationBatch, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, o as LoyaltyContext, v as PointTransaction, x as TierDefinition, y as Member } from "./index-BOWqBSWx.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-DWarXsmw.mjs";
3
3
  import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
4
- import { c as ResolvedTenant, o as RepositoryPlugins, s as ResolvedConfig } from "./config-DoZPGBfW.mjs";
4
+ import { c as ResolvedTenant, o as RepositoryPlugins, s as ResolvedConfig } from "./config-96Sl6oNJ.mjs";
5
5
  import { LoyaltyModels } from "./models/index.mjs";
6
6
  import { EventTransport } from "@classytic/primitives/events";
7
7
  import { ClientSession, Model } from "mongoose";
8
8
  import { PluginType, Repository } from "@classytic/mongokit";
9
9
  import { OutboxStore } from "@classytic/primitives/outbox";
10
+ import { ApprovalChain } from "@classytic/primitives/approval";
10
11
 
11
12
  //#region src/domain/ports/unit-of-work.port.d.ts
12
13
  interface UnitOfWork {
@@ -134,7 +135,12 @@ declare class EarningRuleRepository extends Repository<EarningRule> {
134
135
  txRepo: PointTransactionRepository;
135
136
  config: ResolvedConfig;
136
137
  constructor(model: Model<EarningRule>, plugins?: PluginType[]);
137
- /** Auto-inject tenant id from options into doc payload. See MemberRepository. */
138
+ private applyCreateDefaults;
139
+ /**
140
+ * Auto-injects `programId` from `LoyaltyConfig.program.id` and the tenant id
141
+ * from repo options when omitted. This keeps custom-program engines aligned
142
+ * with `MemberRepository.enroll(...)`, which uses the same config default.
143
+ */
138
144
  create(data: Parameters<Repository<EarningRule>['create']>[0], options?: Parameters<Repository<EarningRule>['create']>[1]): Promise<EarningRule>;
139
145
  createMany(docs: Parameters<Repository<EarningRule>['createMany']>[0], options?: Parameters<Repository<EarningRule>['createMany']>[1]): Promise<EarningRule[]>;
140
146
  evaluateOrder(input: {
@@ -187,6 +193,18 @@ interface Redemption {
187
193
  */
188
194
  initialBalance?: number;
189
195
  remainingBalance?: number;
196
+ /**
197
+ * Optional multi-step approval chain (P7 canonical — same shape as
198
+ * `@classytic/purchase` / `@classytic/order`). High-value redemptions
199
+ * (gift cards above threshold, large discount vouchers) gate on
200
+ * `isApproved(approvals)` before `confirm()` flips the FSM. Single-tier
201
+ * flows skip the chain entirely.
202
+ */
203
+ approvals?: ApprovalChain | null;
204
+ /** Snapshot of the matrix policy id that produced this chain (host-managed). */
205
+ approvalPolicyId?: string | null;
206
+ /** Version of the matrix policy at submit time — frozen for audit. */
207
+ approvalPolicyVersion?: number | null;
190
208
  createdAt: Date;
191
209
  updatedAt: Date;
192
210
  }
@@ -1,4 +1,4 @@
1
- import { o as LoyaltyContext } from "./index-BOWqBSWx.mjs";
1
+ import { o as LoyaltyContext } from "./index-DWarXsmw.mjs";
2
2
  import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventHandler as EventHandler$1, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
3
3
  import { z } from "zod";
4
4
 
@@ -1,5 +1,6 @@
1
1
  import { a as RuleStatus, n as MemberStatus, o as RuleType, s as TransactionType } from "./index-DMK8a98J.mjs";
2
2
  import { OperationContext } from "@classytic/primitives/context";
3
+ import { ApprovalChain } from "@classytic/primitives/approval";
3
4
 
4
5
  //#region src/domain/entities/earning-rule.d.ts
5
6
  interface EarningConditions {
@@ -124,6 +125,19 @@ interface PointTransaction {
124
125
  expiredAt?: Date;
125
126
  metadata?: Record<string, unknown>;
126
127
  actorId: string;
128
+ /**
129
+ * Optional multi-step approval chain (P7 canonical — same shape as
130
+ * `@classytic/purchase` / `@classytic/order`). Manual `adjustment` /
131
+ * `bonus` transactions above a host-defined threshold gate on
132
+ * `isApproved(approvals)` before the journal entry is committed.
133
+ * Automatic transactions (earn from purchase, expiry sweeps) skip the
134
+ * chain entirely — single-tier flows leave this null.
135
+ */
136
+ approvals?: ApprovalChain | null;
137
+ /** Snapshot of the matrix policy id that produced this chain (host-managed). */
138
+ approvalPolicyId?: string | null;
139
+ /** Version of the matrix policy at submit time — frozen for audit. */
140
+ approvalPolicyVersion?: number | null;
127
141
  createdAt: Date;
128
142
  }
129
143
  //#endregion
@@ -245,6 +259,14 @@ interface CreateEarningRuleInput {
245
259
  reward: EarningReward;
246
260
  startsAt?: Date | string;
247
261
  endsAt?: Date | string;
262
+ /**
263
+ * Loyalty program this rule belongs to.
264
+ *
265
+ * Optional for repository-created rules: `EarningRuleRepository.create(...)`
266
+ * and `createMany(...)` fill this from `LoyaltyConfig.program.id`, matching
267
+ * the default used by `MemberRepository.enroll(...)`. Pass explicitly only
268
+ * when creating a rule for a different program.
269
+ */
248
270
  programId?: string;
249
271
  metadata?: Record<string, unknown>;
250
272
  }
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { S as EarningRule, a as EnrollInput, b as MemberBalance, c as UpdateTierInput, d as EarningResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, n as CreateEarningRuleInput, o as LoyaltyContext, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, v as PointTransaction, x as TierDefinition, y as Member } from "./index-BOWqBSWx.mjs";
1
+ import { S as EarningRule, a as EnrollInput, b as MemberBalance, c as UpdateTierInput, d as EarningResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, n as CreateEarningRuleInput, o as LoyaltyContext, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, v as PointTransaction, x as TierDefinition, y as Member } from "./index-DWarXsmw.mjs";
2
2
  import { t as NotificationAdapter } from "./notification.port-CJ9nXj0k.mjs";
3
- import { c as ResolvedTenant, n as LoyaltyConfig, r as LoyaltyModelName, s as ResolvedConfig } from "./config-DoZPGBfW.mjs";
4
- import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, V as TierDowngraded, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, s as LoyaltyEventPayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, z as ReferralRejected } from "./index-C4jFHS7m.mjs";
3
+ import { c as ResolvedTenant, n as LoyaltyConfig, r as LoyaltyModelName, s as ResolvedConfig } from "./config-96Sl6oNJ.mjs";
4
+ import { A as PointsExpired, B as ReferralRewarded, C as MemberDeactivated, E as MemberSuspended, F as RedemptionReserved, G as TierUpgraded, H as TierGraceStarted, J as InProcessLoyaltyBusOptions, K as loyaltyEventDefinitions, L as ReferralApproved, M as PointsRedeemed, N as RedemptionConfirmed, O as PointsAdjusted, P as RedemptionReleased, R as ReferralRecorded, S as LoyaltyEventSchema, T as MemberReactivated, V as TierDowngraded, Y as createEvent, _ as TierChangedPayload, a as LoyaltyEventMap, b as LoyaltyEventDefinition, c as LoyaltyEvents, d as PointsAdjustedPayload, f as PointsEarnedPayload, g as ReferralPayload, j as PointsExpiringSoon, k as PointsEarned, l as MemberLifecyclePayload, m as RedemptionPayload, o as LoyaltyEventName, p as PointsExpiredPayload, q as InProcessLoyaltyBus, s as LoyaltyEventPayload, w as MemberEnrolled, x as LoyaltyEventPayloadOf, z as ReferralRejected } from "./index-DArT4zkB.mjs";
5
5
  import { LoyaltyModels } from "./models/index.mjs";
6
- import { a as ReferralRepository, c as Redemption, d as MemberRepository, f as UnitOfWork, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, o as Referral, s as RedemptionRepository, u as PointTransactionRepository } from "./index-ClPKmtxh.mjs";
6
+ import { a as ReferralRepository, c as Redemption, d as MemberRepository, f as UnitOfWork, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, o as Referral, s as RedemptionRepository, u as PointTransactionRepository } from "./index-Cl-WGBFJ.mjs";
7
7
  import { DomainEvent, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
8
8
 
9
9
  //#region src/index.d.ts
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as DEFAULT_TENANT_FIELD, r as DEFAULT_TENANT_TYPE } from "./constants-DkQYkOK1.mjs";
2
- import { a as PointTransactionRepository, c as ValidationError, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "./repositories-DBSxe9st.mjs";
2
+ import { a as PointTransactionRepository, c as ValidationError, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "./repositories-BMwidCp-.mjs";
3
3
  import { E as InProcessLoyaltyBus, T as loyaltyEventDefinitions, _ as ReferralRecorded, a as MemberSuspended, b as TierDowngraded, c as PointsEarned, d as PointsRedeemed, f as RedemptionConfirmed, g as ReferralApproved, i as MemberReactivated, l as PointsExpired, m as RedemptionReserved, n as MemberDeactivated, p as RedemptionReleased, r as MemberEnrolled, s as PointsAdjusted, u as PointsExpiringSoon, v as ReferralRejected, w as TierUpgraded, x as TierGraceStarted, y as ReferralRewarded } from "./events-BtZOdiPw.mjs";
4
- import { n as createModels } from "./models-uhh9c-7k.mjs";
4
+ import { n as createModels } from "./models-0_OQIqBy.mjs";
5
5
  import { n as createEvent, t as LoyaltyEvents } from "./loyalty-events-Cyt9TwX6.mjs";
6
6
  //#region src/index.ts
7
7
  function resolveConfig(config) {
@@ -62,11 +62,13 @@ var MongoUnitOfWork = class {
62
62
  async withTransaction(cb) {
63
63
  const session = await this.connection.startSession();
64
64
  try {
65
- let result;
65
+ const slot = { assigned: false };
66
66
  await session.withTransaction(async () => {
67
- result = await cb(session);
67
+ slot.value = await cb(session);
68
+ slot.assigned = true;
68
69
  });
69
- return result;
70
+ if (!slot.assigned) throw new Error("withTransaction: callback did not assign a result");
71
+ return slot.value;
70
72
  } finally {
71
73
  await session.endSession();
72
74
  }
@@ -1,4 +1,4 @@
1
- import { c as ResolvedTenant, i as ModelName, r as LoyaltyModelName, t as IndexDefinition } from "../config-DoZPGBfW.mjs";
1
+ import { c as ResolvedTenant, i as ModelName, r as LoyaltyModelName, t as IndexDefinition } from "../config-96Sl6oNJ.mjs";
2
2
  import * as _$mongoose from "mongoose";
3
3
  import { Connection, Model } from "mongoose";
4
4
 
@@ -243,6 +243,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
243
243
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
244
244
  expiresAt?: NativeDate | null | undefined;
245
245
  expiredAt?: NativeDate | null | undefined;
246
+ approvals?: any;
247
+ approvalPolicyId?: string | null | undefined;
248
+ approvalPolicyVersion?: number | null | undefined;
246
249
  createdAt: NativeDate;
247
250
  }, {}, {}, {
248
251
  id: string;
@@ -261,6 +264,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
261
264
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
262
265
  expiresAt?: NativeDate | null | undefined;
263
266
  expiredAt?: NativeDate | null | undefined;
267
+ approvals?: any;
268
+ approvalPolicyId?: string | null | undefined;
269
+ approvalPolicyVersion?: number | null | undefined;
264
270
  createdAt: NativeDate;
265
271
  }, {
266
272
  id: string;
@@ -284,6 +290,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
284
290
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
285
291
  expiresAt?: NativeDate | null | undefined;
286
292
  expiredAt?: NativeDate | null | undefined;
293
+ approvals?: any;
294
+ approvalPolicyId?: string | null | undefined;
295
+ approvalPolicyVersion?: number | null | undefined;
287
296
  createdAt: NativeDate;
288
297
  } & {
289
298
  _id: _$mongoose.Types.ObjectId;
@@ -311,6 +320,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
311
320
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
312
321
  expiresAt?: NativeDate | null | undefined;
313
322
  expiredAt?: NativeDate | null | undefined;
323
+ approvals?: any;
324
+ approvalPolicyId?: string | null | undefined;
325
+ approvalPolicyVersion?: number | null | undefined;
314
326
  createdAt: NativeDate;
315
327
  }, _$mongoose.Document<unknown, {}, {
316
328
  programId: string;
@@ -327,6 +339,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
327
339
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
328
340
  expiresAt?: NativeDate | null | undefined;
329
341
  expiredAt?: NativeDate | null | undefined;
342
+ approvals?: any;
343
+ approvalPolicyId?: string | null | undefined;
344
+ approvalPolicyVersion?: number | null | undefined;
330
345
  createdAt: NativeDate;
331
346
  }, {
332
347
  id: string;
@@ -350,6 +365,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
350
365
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
351
366
  expiresAt?: NativeDate | null | undefined;
352
367
  expiredAt?: NativeDate | null | undefined;
368
+ approvals?: any;
369
+ approvalPolicyId?: string | null | undefined;
370
+ approvalPolicyVersion?: number | null | undefined;
353
371
  createdAt: NativeDate;
354
372
  } & {
355
373
  _id: _$mongoose.Types.ObjectId;
@@ -372,6 +390,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
372
390
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
373
391
  expiresAt?: NativeDate | null | undefined;
374
392
  expiredAt?: NativeDate | null | undefined;
393
+ approvals?: any;
394
+ approvalPolicyId?: string | null | undefined;
395
+ approvalPolicyVersion?: number | null | undefined;
375
396
  createdAt: NativeDate;
376
397
  } & {
377
398
  _id: _$mongoose.Types.ObjectId;
@@ -392,6 +413,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
392
413
  earningRuleId?: _$mongoose.Types.ObjectId | null | undefined;
393
414
  expiresAt?: NativeDate | null | undefined;
394
415
  expiredAt?: NativeDate | null | undefined;
416
+ approvals?: any;
417
+ approvalPolicyId?: string | null | undefined;
418
+ approvalPolicyVersion?: number | null | undefined;
395
419
  createdAt: NativeDate;
396
420
  } & {
397
421
  _id: _$mongoose.Types.ObjectId;
@@ -883,6 +907,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
883
907
  ownerType: string;
884
908
  ownerId: string;
885
909
  expiresAt?: NativeDate | null | undefined;
910
+ approvals?: any;
911
+ approvalPolicyId?: string | null | undefined;
912
+ approvalPolicyVersion?: number | null | undefined;
886
913
  confirmedAt?: NativeDate | null | undefined;
887
914
  releasedAt?: NativeDate | null | undefined;
888
915
  reversedAt?: NativeDate | null | undefined;
@@ -904,6 +931,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
904
931
  ownerType: string;
905
932
  ownerId: string;
906
933
  expiresAt?: NativeDate | null | undefined;
934
+ approvals?: any;
935
+ approvalPolicyId?: string | null | undefined;
936
+ approvalPolicyVersion?: number | null | undefined;
907
937
  confirmedAt?: NativeDate | null | undefined;
908
938
  releasedAt?: NativeDate | null | undefined;
909
939
  reversedAt?: NativeDate | null | undefined;
@@ -927,6 +957,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
927
957
  ownerType: string;
928
958
  ownerId: string;
929
959
  expiresAt?: NativeDate | null | undefined;
960
+ approvals?: any;
961
+ approvalPolicyId?: string | null | undefined;
962
+ approvalPolicyVersion?: number | null | undefined;
930
963
  confirmedAt?: NativeDate | null | undefined;
931
964
  releasedAt?: NativeDate | null | undefined;
932
965
  reversedAt?: NativeDate | null | undefined;
@@ -954,6 +987,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
954
987
  ownerType: string;
955
988
  ownerId: string;
956
989
  expiresAt?: NativeDate | null | undefined;
990
+ approvals?: any;
991
+ approvalPolicyId?: string | null | undefined;
992
+ approvalPolicyVersion?: number | null | undefined;
957
993
  confirmedAt?: NativeDate | null | undefined;
958
994
  releasedAt?: NativeDate | null | undefined;
959
995
  reversedAt?: NativeDate | null | undefined;
@@ -973,6 +1009,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
973
1009
  ownerType: string;
974
1010
  ownerId: string;
975
1011
  expiresAt?: NativeDate | null | undefined;
1012
+ approvals?: any;
1013
+ approvalPolicyId?: string | null | undefined;
1014
+ approvalPolicyVersion?: number | null | undefined;
976
1015
  confirmedAt?: NativeDate | null | undefined;
977
1016
  releasedAt?: NativeDate | null | undefined;
978
1017
  reversedAt?: NativeDate | null | undefined;
@@ -996,6 +1035,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
996
1035
  ownerType: string;
997
1036
  ownerId: string;
998
1037
  expiresAt?: NativeDate | null | undefined;
1038
+ approvals?: any;
1039
+ approvalPolicyId?: string | null | undefined;
1040
+ approvalPolicyVersion?: number | null | undefined;
999
1041
  confirmedAt?: NativeDate | null | undefined;
1000
1042
  releasedAt?: NativeDate | null | undefined;
1001
1043
  reversedAt?: NativeDate | null | undefined;
@@ -1021,6 +1063,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
1021
1063
  ownerType: string;
1022
1064
  ownerId: string;
1023
1065
  expiresAt?: NativeDate | null | undefined;
1066
+ approvals?: any;
1067
+ approvalPolicyId?: string | null | undefined;
1068
+ approvalPolicyVersion?: number | null | undefined;
1024
1069
  confirmedAt?: NativeDate | null | undefined;
1025
1070
  releasedAt?: NativeDate | null | undefined;
1026
1071
  reversedAt?: NativeDate | null | undefined;
@@ -1046,6 +1091,9 @@ declare function createModels(connection: Connection, tenant: ResolvedTenant, in
1046
1091
  ownerType: string;
1047
1092
  ownerId: string;
1048
1093
  expiresAt?: NativeDate | null | undefined;
1094
+ approvals?: any;
1095
+ approvalPolicyId?: string | null | undefined;
1096
+ approvalPolicyVersion?: number | null | undefined;
1049
1097
  confirmedAt?: NativeDate | null | undefined;
1050
1098
  releasedAt?: NativeDate | null | undefined;
1051
1099
  reversedAt?: NativeDate | null | undefined;
@@ -1,2 +1,2 @@
1
- import { n as createModels, t as LoyaltyModelCollisionError } from "../models-uhh9c-7k.mjs";
1
+ import { n as createModels, t as LoyaltyModelCollisionError } from "../models-0_OQIqBy.mjs";
2
2
  export { LoyaltyModelCollisionError, createModels };
@@ -232,6 +232,18 @@ function createPointTransactionSchema() {
232
232
  actorId: {
233
233
  type: String,
234
234
  required: true
235
+ },
236
+ approvals: {
237
+ type: mongoose.Schema.Types.Mixed,
238
+ default: null
239
+ },
240
+ approvalPolicyId: {
241
+ type: String,
242
+ default: null
243
+ },
244
+ approvalPolicyVersion: {
245
+ type: Number,
246
+ default: null
235
247
  }
236
248
  }, { timestamps: {
237
249
  createdAt: true,
@@ -297,7 +309,19 @@ function createRedemptionSchema() {
297
309
  reversalTransactionId: { type: mongoose.Schema.Types.ObjectId },
298
310
  transactionId: { type: mongoose.Schema.Types.ObjectId },
299
311
  initialBalance: { type: Number },
300
- remainingBalance: { type: Number }
312
+ remainingBalance: { type: Number },
313
+ approvals: {
314
+ type: mongoose.Schema.Types.Mixed,
315
+ default: null
316
+ },
317
+ approvalPolicyId: {
318
+ type: String,
319
+ default: null
320
+ },
321
+ approvalPolicyVersion: {
322
+ type: Number,
323
+ default: null
324
+ }
301
325
  }, { timestamps: true });
302
326
  schema.index({
303
327
  ownerType: 1,
@@ -1,2 +1,2 @@
1
- import { a as ReferralRepository, d as MemberRepository, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, r as createRepositories, s as RedemptionRepository, t as CreateRepositoriesInput, u as PointTransactionRepository } from "../index-ClPKmtxh.mjs";
1
+ import { a as ReferralRepository, d as MemberRepository, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, r as createRepositories, s as RedemptionRepository, t as CreateRepositoriesInput, u as PointTransactionRepository } from "../index-Cl-WGBFJ.mjs";
2
2
  export { CreateRepositoriesInput, EarningRuleRepository, LoyaltyRepositories, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
@@ -1,2 +1,2 @@
1
- import { a as PointTransactionRepository, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "../repositories-DBSxe9st.mjs";
1
+ import { a as PointTransactionRepository, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "../repositories-BMwidCp-.mjs";
2
2
  export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
@@ -193,14 +193,22 @@ var EarningRuleRepository = class extends Repository {
193
193
  constructor(model, plugins = []) {
194
194
  super(model, plugins);
195
195
  }
196
- /** Auto-inject tenant id from options into doc payload. See MemberRepository. */
197
- async create(data, options) {
196
+ applyCreateDefaults(data, options) {
197
+ data.programId ??= this.config.programId;
198
198
  injectTenantIntoData(data, options, this.config.tenant);
199
+ }
200
+ /**
201
+ * Auto-injects `programId` from `LoyaltyConfig.program.id` and the tenant id
202
+ * from repo options when omitted. This keeps custom-program engines aligned
203
+ * with `MemberRepository.enroll(...)`, which uses the same config default.
204
+ */
205
+ async create(data, options) {
206
+ this.applyCreateDefaults(data, options);
199
207
  return super.create(data, options);
200
208
  }
201
209
  async createMany(docs, options) {
202
210
  const opts = options;
203
- for (const doc of docs) injectTenantIntoData(doc, opts, this.config.tenant);
211
+ for (const doc of docs) this.applyCreateDefaults(doc, opts);
204
212
  return super.createMany(docs, options);
205
213
  }
206
214
  async evaluateOrder(input, ctx) {
@@ -346,10 +354,15 @@ async function dispatchLoyaltyEvent(deps, event, ctx) {
346
354
  const saveOptions = {};
347
355
  if (ctx.session !== void 0) saveOptions.session = ctx.session;
348
356
  await deps.outbox.save(event, saveOptions);
349
- } catch (_err) {}
357
+ } catch (err) {
358
+ deps.logger?.error(`[loyalty] outbox.save failed for ${event.type}:`, err);
359
+ throw err;
360
+ }
350
361
  if (deps.events) try {
351
362
  await deps.events.publish(event);
352
- } catch (_err) {}
363
+ } catch (err) {
364
+ deps.logger?.error(`[loyalty] events.publish failed for ${event.type}:`, err);
365
+ }
353
366
  }
354
367
  //#endregion
355
368
  //#region src/utils/code-generator.ts
@@ -1,3 +1,3 @@
1
- import { _ as TierEvaluationBatch, a as EnrollInput, c as UpdateTierInput, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, m as PaginatedResult, n as CreateEarningRuleInput, o as LoyaltyContext, p as ListQuery, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, u as EarningBreakdown } from "../index-BOWqBSWx.mjs";
2
- import { a as PluginType, c as ResolvedTenant, i as ModelName, l as TenantConfig, n as LoyaltyConfig, o as RepositoryPlugins, s as ResolvedConfig, t as IndexDefinition } from "../config-DoZPGBfW.mjs";
1
+ import { _ as TierEvaluationBatch, a as EnrollInput, c as UpdateTierInput, d as EarningResult, f as ExpirationResult, g as TierEvaluation, h as RedemptionValidation, i as EarnPointsInput, l as BalanceSummary, m as PaginatedResult, n as CreateEarningRuleInput, o as LoyaltyContext, p as ListQuery, r as CreateTierInput, s as UpdateEarningRuleInput, t as AdjustPointsInput, u as EarningBreakdown } from "../index-DWarXsmw.mjs";
2
+ import { a as PluginType, c as ResolvedTenant, i as ModelName, l as TenantConfig, n as LoyaltyConfig, o as RepositoryPlugins, s as ResolvedConfig, t as IndexDefinition } from "../config-96Sl6oNJ.mjs";
3
3
  export { AdjustPointsInput, BalanceSummary, CreateEarningRuleInput, CreateTierInput, EarnPointsInput, EarningBreakdown, EarningResult, EnrollInput, ExpirationResult, IndexDefinition, ListQuery, LoyaltyConfig, LoyaltyContext, ModelName, PaginatedResult, PluginType, RedemptionValidation, RepositoryPlugins, ResolvedConfig, ResolvedTenant, TenantConfig, TierEvaluation, TierEvaluationBatch, UpdateEarningRuleInput, UpdateTierInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/loyalty",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Loyalty points, tiers, earning rules, referrals, and redemption engine for MongoDB",
5
5
  "author": "Classytic",
6
6
  "homepage": "https://www.npmjs.com/package/@classytic/loyalty",
@@ -78,18 +78,18 @@
78
78
  "release": "npm run push -- main && npm run release:tag && npm publish"
79
79
  },
80
80
  "peerDependencies": {
81
- "@classytic/mongokit": ">=3.11.0",
82
- "@classytic/primitives": ">=0.1.0",
83
- "@classytic/repo-core": ">=0.2.0",
81
+ "@classytic/mongokit": ">=3.13.3",
82
+ "@classytic/primitives": ">=0.5.0",
83
+ "@classytic/repo-core": ">=0.4.2",
84
84
  "mongoose": ">=9.4.1",
85
85
  "zod": ">=4.0.0"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@biomejs/biome": "^2.4.9",
89
89
  "@classytic/dev-tools": "^0.2.0",
90
- "@classytic/mongokit": ">=3.11.0",
91
- "@classytic/primitives": ">=0.1.0",
92
- "@classytic/repo-core": ">=0.2.0",
90
+ "@classytic/mongokit": "^3.13.3",
91
+ "@classytic/primitives": ">=0.5.0",
92
+ "@classytic/repo-core": "^0.4.2",
93
93
  "@types/node": "^25.5.0",
94
94
  "@vitest/coverage-v8": "^3.2.4",
95
95
  "knip": "^6.3.0",