@classytic/promo 0.2.5 → 0.4.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/dist/index.d.mts CHANGED
@@ -1,163 +1,191 @@
1
- import { c as ProgramType, f as StackingMode, g as VoucherStatus, i as DiscountScope, m as TriggerMode, r as DiscountMode, s as ProgramStatus, u as RewardType } from "./constants-CrbSSQG5.mjs";
1
+ import { EvaluationCommitted, EvaluationCommittedPayloadSchema, EvaluationCompleted, EvaluationCompletedPayloadSchema, EvaluationRolledBack, EvaluationRolledBackPayloadSchema, GiftCardExhausted, GiftCardSpent, GiftCardSpentPayloadSchema, GiftCardToppedUp, GiftCardToppedUpPayloadSchema, ProgramActivated, ProgramArchived, ProgramCreated, ProgramLifecyclePayloadSchema, ProgramPaused, PromoEventDefinition, PromoEventPayloadOf, PromoEventSchema, RewardAdded, RewardPayloadSchema, RewardRemoved, RewardUpdated, RuleAdded, RulePayloadSchema, RuleRemoved, RuleUpdated, VoucherCancelled, VoucherExpired, VoucherGenerated, VoucherGeneratedPayloadSchema, VoucherLifecyclePayloadSchema, VoucherRedeemed, VoucherRedeemedPayloadSchema, promoEventDefinitions } from "./events/promo-event-catalog.mjs";
2
+ import { c as ProgramType, f as StackingMode, g as VoucherStatus, i as DiscountScope, m as TriggerMode, r as DiscountMode, s as ProgramStatus, u as RewardType } from "./constants-hcMTDJml.mjs";
2
3
  import { PluginType as PluginType$1, Repository } from "@classytic/mongokit";
3
4
  import { ResolvedTenantConfig, TenantConfig, TenantFieldType } from "@classytic/repo-core/tenant";
4
5
  import { DomainEvent, DomainEvent as DomainEvent$1, EventHandler, EventTransport, EventTransport as EventTransport$1 } from "@classytic/primitives/events";
5
6
  import { ClientSession, Connection, Model } from "mongoose";
6
- import { z } from "zod";
7
+ import { RetryPolicy } from "@classytic/repo-core/repository";
7
8
  import { OutboxStore } from "@classytic/primitives/outbox";
8
9
  import { OperationContext } from "@classytic/primitives/context";
10
+ import { HttpError } from "@classytic/repo-core/errors";
9
11
 
10
12
  //#region src/types/inputs.d.ts
11
13
  /**
12
14
  * Extends `@classytic/primitives`' {@link OperationContext}. The
13
15
  * `[key: string]: unknown` fall-through lets hosts pass dynamic tenant keys
14
16
  * when they've configured a custom `contextKey` on `TenantConfig`.
17
+ *
18
+ * Primitives' `actorId` is replaced by `actorRef` — the commerce-wide
19
+ * convention (cart/order/flow) for "stable user id OR guest session id".
20
+ *
21
+ * **Option threading.** Promo deliberately spreads the WHOLE context into
22
+ * mongokit option bags (`{ lean: true, ...ctx }`) instead of using a
23
+ * fixed-key options extractor — the index-signature fall-through means a
24
+ * host's custom `contextKey` value reaches `multiTenantPlugin` without
25
+ * promo knowing its name. The typed fields below (`session`, `signal`,
26
+ * `retryPolicy`, `organizationId`) ride the same spread: mongokit 3.16
27
+ * reads them straight off the options bag (cancellation at every op
28
+ * boundary, `withRetry` around driver round-trips).
15
29
  */
16
- interface PromoContext extends OperationContext {
17
- /** Narrowed from primitives' `IdLike` to string. */
18
- actorId?: string;
30
+ interface PromoContext extends Omit<OperationContext, 'actorId' | 'organizationId'> {
31
+ /**
32
+ * Actor identity — a stable user ID (authenticated) or a session ID
33
+ * (guest). Matches the `actorRef` convention used by `@classytic/cart`,
34
+ * `@classytic/order`, and `@classytic/flow`.
35
+ */
36
+ actorRef?: string | undefined;
19
37
  /** Narrowed from primitives' `IdLike` to string. */
20
- organizationId?: string;
38
+ organizationId?: string | undefined;
21
39
  /** Narrows primitives' `session: unknown` to the concrete Mongoose type so
22
40
  * this context flows directly into mongokit repository calls. */
23
- session?: ClientSession;
41
+ session?: ClientSession | undefined;
42
+ /**
43
+ * Cooperative cancellation. Mongokit checks the signal at every op
44
+ * boundary; promo additionally checks it between iterations of its own
45
+ * batch loops (`expireByDate`, the per-program evaluation loop, the
46
+ * commit usage loops). Aborting never rolls back a committed write.
47
+ */
48
+ signal?: AbortSignal | undefined;
49
+ /** Per-call retry policy — mongokit wraps driver round-trips in
50
+ * `withRetry` (never hooks; hooks run exactly once). */
51
+ retryPolicy?: RetryPolicy | undefined;
24
52
  [key: string]: unknown;
25
53
  }
26
54
  interface CreateProgramInput {
27
55
  name: string;
28
- description?: string;
56
+ description?: string | undefined;
29
57
  programType: ProgramType;
30
58
  triggerMode: TriggerMode;
31
- stackingMode?: StackingMode;
32
- priority?: number;
33
- startsAt?: Date;
34
- endsAt?: Date;
35
- maxUsageTotal?: number;
36
- maxUsagePerCustomer?: number;
37
- applicableCustomerIds?: string[];
38
- applicableCustomerTags?: string[];
39
- metadata?: Record<string, unknown>;
59
+ stackingMode?: StackingMode | undefined;
60
+ priority?: number | undefined;
61
+ startsAt?: Date | undefined;
62
+ endsAt?: Date | undefined;
63
+ maxUsageTotal?: number | undefined;
64
+ maxUsagePerCustomer?: number | undefined;
65
+ applicableCustomerIds?: string[] | undefined;
66
+ applicableCustomerTags?: string[] | undefined;
67
+ metadata?: Record<string, unknown> | undefined;
40
68
  }
41
69
  interface UpdateProgramInput {
42
- name?: string;
43
- description?: string;
44
- stackingMode?: StackingMode;
45
- priority?: number;
46
- startsAt?: Date;
47
- endsAt?: Date;
48
- maxUsageTotal?: number;
49
- maxUsagePerCustomer?: number;
50
- applicableCustomerIds?: string[];
51
- applicableCustomerTags?: string[];
52
- metadata?: Record<string, unknown>;
70
+ name?: string | undefined;
71
+ description?: string | undefined;
72
+ stackingMode?: StackingMode | undefined;
73
+ priority?: number | undefined;
74
+ startsAt?: Date | undefined;
75
+ endsAt?: Date | undefined;
76
+ maxUsageTotal?: number | undefined;
77
+ maxUsagePerCustomer?: number | undefined;
78
+ applicableCustomerIds?: string[] | undefined;
79
+ applicableCustomerTags?: string[] | undefined;
80
+ metadata?: Record<string, unknown> | undefined;
53
81
  }
54
82
  interface CreateRuleInput {
55
- name?: string;
56
- minimumAmount?: number;
57
- minimumQuantity?: number;
58
- applicableProductIds?: string[];
59
- applicableCategories?: string[];
60
- applicableSkus?: string[];
61
- buyQuantity?: number;
62
- code?: string;
63
- startsAt?: Date;
64
- endsAt?: Date;
65
- metadata?: Record<string, unknown>;
83
+ name?: string | undefined;
84
+ minimumAmount?: number | undefined;
85
+ minimumQuantity?: number | undefined;
86
+ applicableProductIds?: string[] | undefined;
87
+ applicableCategories?: string[] | undefined;
88
+ applicableSkus?: string[] | undefined;
89
+ buyQuantity?: number | undefined;
90
+ code?: string | undefined;
91
+ startsAt?: Date | undefined;
92
+ endsAt?: Date | undefined;
93
+ metadata?: Record<string, unknown> | undefined;
66
94
  }
67
95
  interface UpdateRuleInput {
68
- name?: string;
69
- minimumAmount?: number;
70
- minimumQuantity?: number;
71
- applicableProductIds?: string[];
72
- applicableCategories?: string[];
73
- applicableSkus?: string[];
74
- buyQuantity?: number;
75
- code?: string;
76
- startsAt?: Date;
77
- endsAt?: Date;
78
- metadata?: Record<string, unknown>;
96
+ name?: string | undefined;
97
+ minimumAmount?: number | undefined;
98
+ minimumQuantity?: number | undefined;
99
+ applicableProductIds?: string[] | undefined;
100
+ applicableCategories?: string[] | undefined;
101
+ applicableSkus?: string[] | undefined;
102
+ buyQuantity?: number | undefined;
103
+ code?: string | undefined;
104
+ startsAt?: Date | undefined;
105
+ endsAt?: Date | undefined;
106
+ metadata?: Record<string, unknown> | undefined;
79
107
  }
80
108
  interface CreateRewardInput {
81
- ruleId?: string;
109
+ ruleId?: string | undefined;
82
110
  rewardType: RewardType;
83
- discountMode?: DiscountMode;
84
- discountAmount?: number;
85
- maxDiscountAmount?: number;
86
- discountScope?: DiscountScope;
87
- applicableProductIds?: string[];
88
- freeProductId?: string;
89
- freeProductSku?: string;
90
- freeQuantity?: number;
91
- giftCardAmount?: number;
92
- metadata?: Record<string, unknown>;
111
+ discountMode?: DiscountMode | undefined;
112
+ discountAmount?: number | undefined;
113
+ maxDiscountAmount?: number | undefined;
114
+ discountScope?: DiscountScope | undefined;
115
+ applicableProductIds?: string[] | undefined;
116
+ freeProductId?: string | undefined;
117
+ freeProductSku?: string | undefined;
118
+ freeQuantity?: number | undefined;
119
+ giftCardAmount?: number | undefined;
120
+ metadata?: Record<string, unknown> | undefined;
93
121
  }
94
122
  interface UpdateRewardInput {
95
- discountMode?: DiscountMode;
96
- discountAmount?: number;
97
- maxDiscountAmount?: number;
98
- discountScope?: DiscountScope;
99
- applicableProductIds?: string[];
100
- freeProductId?: string;
101
- freeProductSku?: string;
102
- freeQuantity?: number;
103
- giftCardAmount?: number;
104
- metadata?: Record<string, unknown>;
123
+ discountMode?: DiscountMode | undefined;
124
+ discountAmount?: number | undefined;
125
+ maxDiscountAmount?: number | undefined;
126
+ discountScope?: DiscountScope | undefined;
127
+ applicableProductIds?: string[] | undefined;
128
+ freeProductId?: string | undefined;
129
+ freeProductSku?: string | undefined;
130
+ freeQuantity?: number | undefined;
131
+ giftCardAmount?: number | undefined;
132
+ metadata?: Record<string, unknown> | undefined;
105
133
  }
106
134
  interface GenerateCodesInput {
107
135
  programId: string;
108
136
  count: number;
109
- customerId?: string;
110
- expiresAt?: Date;
111
- metadata?: Record<string, unknown>;
137
+ customerId?: string | undefined;
138
+ expiresAt?: Date | undefined;
139
+ metadata?: Record<string, unknown> | undefined;
112
140
  }
113
141
  interface GenerateSingleCodeInput {
114
142
  programId: string;
115
- code?: string;
116
- customerId?: string;
117
- expiresAt?: Date;
118
- initialBalance?: number;
119
- metadata?: Record<string, unknown>;
143
+ code?: string | undefined;
144
+ customerId?: string | undefined;
145
+ expiresAt?: Date | undefined;
146
+ initialBalance?: number | undefined;
147
+ metadata?: Record<string, unknown> | undefined;
120
148
  }
121
149
  interface RedeemVoucherInput {
122
150
  code: string;
123
151
  orderId: string;
124
- customerId?: string;
152
+ customerId?: string | undefined;
125
153
  discountAmount: number;
126
- idempotencyKey?: string;
154
+ idempotencyKey?: string | undefined;
127
155
  }
128
156
  interface GiftCardSpendInput {
129
157
  code: string;
130
158
  amount: number;
131
159
  orderId: string;
132
- description?: string;
133
- idempotencyKey?: string;
160
+ description?: string | undefined;
161
+ idempotencyKey?: string | undefined;
134
162
  }
135
163
  interface GiftCardTopUpInput {
136
164
  code: string;
137
165
  amount: number;
138
- description?: string;
139
- idempotencyKey?: string;
166
+ description?: string | undefined;
167
+ idempotencyKey?: string | undefined;
140
168
  }
141
169
  interface CartItem {
142
170
  productId: string;
143
- sku?: string;
144
- categoryId?: string;
171
+ sku?: string | undefined;
172
+ categoryId?: string | undefined;
145
173
  quantity: number;
146
174
  unitPrice: number;
147
- lineTotal?: number;
175
+ lineTotal?: number | undefined;
148
176
  }
149
177
  interface EvaluateInput {
150
178
  items: CartItem[];
151
179
  subtotal: number;
152
- codes?: string[];
153
- customerId?: string;
154
- customerTags?: string[];
180
+ codes?: string[] | undefined;
181
+ customerId?: string | undefined;
182
+ customerTags?: string[] | undefined;
155
183
  }
156
184
  interface ListQuery {
157
- page?: number;
158
- limit?: number;
159
- sort?: string;
160
- filters?: Record<string, unknown>;
185
+ page?: number | undefined;
186
+ limit?: number | undefined;
187
+ sort?: string | undefined;
188
+ filters?: Record<string, unknown> | undefined;
161
189
  }
162
190
  //#endregion
163
191
  //#region src/types/results.d.ts
@@ -169,14 +197,14 @@ interface DiscountLine {
169
197
  scope: 'order' | 'cheapest' | 'specific_products';
170
198
  amount: number;
171
199
  description: string;
172
- voucherCode?: string;
200
+ voucherCode?: string | undefined;
173
201
  }
174
202
  interface FreeProductLine {
175
203
  programId: string;
176
204
  programName: string;
177
205
  rewardId: string;
178
- productId?: string;
179
- productSku?: string;
206
+ productId?: string | undefined;
207
+ productSku?: string | undefined;
180
208
  quantity: number;
181
209
  description: string;
182
210
  }
@@ -220,10 +248,10 @@ interface VoucherValidation {
220
248
  programType: string;
221
249
  status: string;
222
250
  remainingUses: number;
223
- currentBalance?: number;
224
- expiresAt?: Date;
251
+ currentBalance?: number | undefined;
252
+ expiresAt?: Date | undefined;
225
253
  };
226
- error?: string;
254
+ error?: string | undefined;
227
255
  }
228
256
  interface GiftCardBalance {
229
257
  code: string;
@@ -255,7 +283,7 @@ interface PaginatedResult<T> {
255
283
  interface StoredEvaluationSnapshot {
256
284
  readonly result: EvaluationResult;
257
285
  readonly ctx: PromoContext;
258
- readonly customerId?: string;
286
+ readonly customerId?: string | undefined;
259
287
  readonly programUsages: ReadonlyArray<{
260
288
  programId: string;
261
289
  }>;
@@ -284,7 +312,7 @@ interface StoredEvaluationSnapshot {
284
312
  * have to translate between context shapes at the call site.
285
313
  */
286
314
  interface EvaluationStoreContext {
287
- readonly tenantValue?: string;
315
+ readonly tenantValue?: string | undefined;
288
316
  readonly session?: unknown;
289
317
  }
290
318
  /**
@@ -387,6 +415,34 @@ interface PromoConfig {
387
415
  } | undefined;
388
416
  indexes?: Partial<Record<ModelName, IndexDefinition[]>> | undefined;
389
417
  autoIndex?: boolean | Partial<Record<ModelName, boolean>> | undefined;
418
+ /**
419
+ * Optional namespace prepended to every physical collection promo
420
+ * creates (PACKAGE_RULES §20.1) — e.g. `'commerce_'` →
421
+ * `commerce_promoprograms`. Model names are never prefixed, so `ref:`
422
+ * populate keeps working. Unset → the historical default names
423
+ * (`promoprograms`, `promorules`, `promorewards`, `promovouchers`,
424
+ * `promopendingevaluations`).
425
+ */
426
+ collectionPrefix?: string | undefined;
427
+ /**
428
+ * When true, existing Mongoose models with promo's names on the
429
+ * connection are deleted and re-registered — hot-reload / test fixtures
430
+ * only. Default `false`: a name collision throws
431
+ * `PromoModelCollisionError` (rule 21). Two promo engines in one
432
+ * process need two Mongoose connections.
433
+ */
434
+ forceRecreate?: boolean | undefined;
435
+ /**
436
+ * Escape hatch for the boot-time capability gate. Promo's commit /
437
+ * redeem / spend / topUp paths are transactional; by default
438
+ * `createPromoEngine` asserts the backend's `capabilities.transactions`
439
+ * flag at boot (fail loud at boot, not with a cryptic error 263 on the
440
+ * first checkout). `allowNonTransactional: true` skips the gate AND lets
441
+ * the internal unit-of-work fall back to non-transactional execution on
442
+ * standalone MongoDB (dev / CI without a replica set) — atomicity across
443
+ * documents is then best-effort.
444
+ */
445
+ allowNonTransactional?: boolean | undefined;
390
446
  events?: {
391
447
  transport?: EventTransport$1 | undefined;
392
448
  } | undefined;
@@ -448,6 +504,21 @@ interface PromoModels {
448
504
  Voucher: Model<unknown>;
449
505
  PendingEvaluation: Model<unknown>;
450
506
  }
507
+ /**
508
+ * Explicit physical collection names (PACKAGE_RULES §20.1) — the values
509
+ * Mongoose's pluralizer produced before they were pinned, so existing
510
+ * deployments keep their data with zero migration. Passed as the third
511
+ * arg of `connection.model()` so a host that disables/changes the
512
+ * pluralizer can't silently rename collections. Changing any entry is a
513
+ * BREAKING change (renames the physical collection — ship a migration).
514
+ */
515
+ declare const DEFAULT_COLLECTIONS: {
516
+ readonly PromoProgram: "promoprograms";
517
+ readonly PromoRule: "promorules";
518
+ readonly PromoReward: "promorewards";
519
+ readonly PromoVoucher: "promovouchers";
520
+ readonly PromoPendingEvaluation: "promopendingevaluations";
521
+ };
451
522
  //#endregion
452
523
  //#region src/events/dispatch.d.ts
453
524
  interface DispatchLogger {
@@ -458,6 +529,23 @@ interface DispatchDeps {
458
529
  outbox?: OutboxStore | undefined;
459
530
  logger?: DispatchLogger | undefined;
460
531
  }
532
+ /**
533
+ * Context key for the post-commit publish queue. Promo's transactional
534
+ * verbs attach it automatically when they own the transaction; hosts that
535
+ * own the outer transaction (`ctx.session`) MAY attach their own array and
536
+ * drain it with {@link flushPendingPromoEvents} after commit for strict
537
+ * no-ghost-event semantics. Symbol-keyed so it survives `{ ...ctx }`
538
+ * spreads but never collides with host context fields.
539
+ */
540
+ declare const PENDING_PROMO_EVENTS: unique symbol;
541
+ /**
542
+ * Drain a pending-events queue through the transport, swallowing per-event
543
+ * publish errors (durable delivery is the outbox's job). Promo's
544
+ * transactional verbs call this after commit; hosts that attached their own
545
+ * `[PENDING_PROMO_EVENTS]` queue call it after their outer transaction
546
+ * commits. The queue is emptied in place.
547
+ */
548
+ declare function flushPendingPromoEvents(deps: DispatchDeps, events: DomainEvent$1[]): Promise<void>;
461
549
  //#endregion
462
550
  //#region src/repositories/pending-evaluation.repository.d.ts
463
551
  /**
@@ -525,16 +613,16 @@ declare class PendingEvaluationRepository extends Repository<PendingEvaluationDo
525
613
  * keep cross-tenant isolation on the cache layer too.
526
614
  */
527
615
  takeByEvaluationId(evaluationId: string, ctx?: {
528
- tenantValue?: string;
529
- session?: ClientSession;
616
+ tenantValue?: string | undefined;
617
+ session?: ClientSession | undefined;
530
618
  }): Promise<PendingEvaluationDocument | null>;
531
619
  /**
532
620
  * Idempotent delete. Returns whether anything was actually removed,
533
621
  * so callers can distinguish "we cleaned it" from "already gone".
534
622
  */
535
623
  deleteByEvaluationId(evaluationId: string, ctx?: {
536
- tenantValue?: string;
537
- session?: ClientSession;
624
+ tenantValue?: string | undefined;
625
+ session?: ClientSession | undefined;
538
626
  }): Promise<boolean>;
539
627
  /**
540
628
  * Build a query filter with the configured tenant scope appended,
@@ -766,11 +854,11 @@ interface PromoRepositories {
766
854
  pendingEvaluation: PendingEvaluationRepository;
767
855
  }
768
856
  interface RepositoryPluginsMap {
769
- program?: PluginType$1[];
770
- rule?: PluginType$1[];
771
- reward?: PluginType$1[];
772
- voucher?: PluginType$1[];
773
- pendingEvaluation?: PluginType$1[];
857
+ program?: PluginType$1[] | undefined;
858
+ rule?: PluginType$1[] | undefined;
859
+ reward?: PluginType$1[] | undefined;
860
+ voucher?: PluginType$1[] | undefined;
861
+ pendingEvaluation?: PluginType$1[] | undefined;
774
862
  }
775
863
  interface RepositoryDispatchDeps {
776
864
  events?: EventTransport$1 | undefined;
@@ -840,6 +928,28 @@ declare class VoucherService {
840
928
  private dispatchDeps;
841
929
  private config;
842
930
  constructor(voucherRepo: VoucherRepository, programRepo: ProgramRepository, unitOfWork: UnitOfWork, dispatchDeps: DispatchDeps, config: ResolvedConfig);
931
+ /**
932
+ * Session threading — checkout-chain participation. When the host
933
+ * already owns a transaction (e.g. the cart → order → flow → promo →
934
+ * invoice chain runs as ONE atomic unit), it passes `ctx.session` and
935
+ * the transactional body JOINS that session: every write commits or
936
+ * aborts with the host's order. MongoDB has no nested transactions, so
937
+ * we must NOT open our own in that case — the host owns commit/abort/
938
+ * retry. Without a host session we own the transaction via the
939
+ * UnitOfWork (mongokit retry semantics included).
940
+ *
941
+ * **Post-commit publish (§P8 phased variant).** When promo owns the
942
+ * transaction, a pending queue is attached to the tx context so every
943
+ * `dispatchPromoEvent` inside the body defers its transport publish; the
944
+ * queue flushes only after the transaction commits — a rollback (or a
945
+ * transient retry of the body) can't leak ghost events to in-process
946
+ * subscribers. The queue resets at the start of each retry attempt.
947
+ * When the HOST owns the session, promo can't know when the host
948
+ * commits: with a host-attached `[PENDING_PROMO_EVENTS]` queue the
949
+ * deferral still applies (host flushes); without one, publish happens
950
+ * in-scope as the documented best effort (see events/dispatch.ts).
951
+ */
952
+ private runTransactional;
843
953
  generateCodes(input: GenerateCodesInput, ctx: PromoContext): Promise<Voucher[]>;
844
954
  generateSingleCode(input: GenerateSingleCodeInput, ctx: PromoContext): Promise<Voucher>;
845
955
  validateCode(code: string, ctx: PromoContext): Promise<VoucherValidation>;
@@ -897,17 +1007,17 @@ declare class InMemoryEvaluationStore implements EvaluationStore {
897
1007
  }
898
1008
  //#endregion
899
1009
  //#region src/domain/errors/base.d.ts
900
- declare abstract class PromoError extends Error {
901
- abstract readonly code: string;
902
- constructor(message: string);
1010
+ declare class PromoError extends Error implements HttpError {
1011
+ readonly code: string;
1012
+ readonly status: number;
1013
+ constructor(message: string, code: string, status: number);
903
1014
  }
904
1015
  //#endregion
905
1016
  //#region src/domain/errors/domain-errors.d.ts
906
1017
  declare class ValidationError extends PromoError {
907
- readonly code = "VALIDATION_ERROR";
1018
+ constructor(message: string);
908
1019
  }
909
1020
  declare class ProgramNotFoundError extends PromoError {
910
- readonly code = "PROGRAM_NOT_FOUND";
911
1021
  constructor(id?: string);
912
1022
  }
913
1023
  /**
@@ -920,31 +1030,24 @@ declare class ProgramNotFoundError extends PromoError {
920
1030
  declare class ProgramUsageCapExceededError extends PromoError {
921
1031
  readonly programId: string;
922
1032
  readonly maxUsageTotal: number;
923
- readonly code = "PROGRAM_USAGE_CAP_EXCEEDED";
924
1033
  constructor(programId: string, maxUsageTotal: number);
925
1034
  }
926
1035
  declare class RuleNotFoundError extends PromoError {
927
- readonly code = "RULE_NOT_FOUND";
928
1036
  constructor(id?: string);
929
1037
  }
930
1038
  declare class RewardNotFoundError extends PromoError {
931
- readonly code = "REWARD_NOT_FOUND";
932
1039
  constructor(id?: string);
933
1040
  }
934
1041
  declare class VoucherNotFoundError extends PromoError {
935
- readonly code = "VOUCHER_NOT_FOUND";
936
1042
  constructor(codeOrId?: string);
937
1043
  }
938
1044
  declare class InvalidTransitionError extends PromoError {
939
- readonly code = "INVALID_TRANSITION";
940
1045
  constructor(from: string, to: string);
941
1046
  }
942
1047
  declare class VoucherExpiredError extends PromoError {
943
- readonly code = "VOUCHER_EXPIRED";
944
1048
  constructor(code: string);
945
1049
  }
946
1050
  declare class VoucherExhaustedError extends PromoError {
947
- readonly code = "VOUCHER_EXHAUSTED";
948
1051
  constructor(code: string);
949
1052
  }
950
1053
  /**
@@ -954,7 +1057,6 @@ declare class VoucherExhaustedError extends PromoError {
954
1057
  * later" messaging.
955
1058
  */
956
1059
  declare class GiftCardExhaustedError extends PromoError {
957
- readonly code = "GIFT_CARD_EXHAUSTED";
958
1060
  constructor(code: string);
959
1061
  }
960
1062
  /**
@@ -967,24 +1069,28 @@ declare class ConcurrencyConflictError extends PromoError {
967
1069
  readonly resource: 'voucher' | 'program' | 'rule' | 'reward';
968
1070
  readonly resourceId: string;
969
1071
  readonly cause?: unknown | undefined;
970
- readonly code = "CONCURRENCY_CONFLICT";
971
- readonly status = 409;
972
1072
  constructor(resource: 'voucher' | 'program' | 'rule' | 'reward', resourceId: string, cause?: unknown | undefined);
973
1073
  }
974
1074
  declare class InsufficientBalanceError extends PromoError {
975
- readonly code = "INSUFFICIENT_BALANCE";
976
1075
  constructor(code: string, available: number, requested: number);
977
1076
  }
978
1077
  declare class TenantIsolationError extends PromoError {
979
- readonly code = "TENANT_ISOLATION";
980
1078
  constructor();
981
1079
  }
982
1080
  declare class DuplicateRedemptionError extends PromoError {
983
- readonly code = "DUPLICATE_REDEMPTION";
984
1081
  constructor(key: string);
985
1082
  }
1083
+ /**
1084
+ * Residual E11000 on the voucher `code` unique index, classified via
1085
+ * mongokit's `isDuplicateKeyError` and re-thrown as this typed shape
1086
+ * (Concurrency House Rules: "typed collision errors are the contract;
1087
+ * the unique index is the authoritative guard"). Realistic path:
1088
+ * `generateSingleCode` with a caller-supplied code that already exists.
1089
+ */
1090
+ declare class DuplicateVoucherCodeError extends PromoError {
1091
+ constructor(code?: string);
1092
+ }
986
1093
  declare class EvaluationNotFoundError extends PromoError {
987
- readonly code = "EVALUATION_NOT_FOUND";
988
1094
  constructor(id: string);
989
1095
  }
990
1096
  /**
@@ -995,9 +1101,18 @@ declare class EvaluationNotFoundError extends PromoError {
995
1101
  * tries to apply the stale discount to the altered order.
996
1102
  */
997
1103
  declare class CartHashMismatchError extends PromoError {
998
- readonly code = "CART_HASH_MISMATCH";
999
1104
  constructor(evaluationId: string);
1000
1105
  }
1106
+ /**
1107
+ * Thrown by `createModels` when a Mongoose model with one of promo's names
1108
+ * is already registered on the connection (rule 21). Silent `deleteModel`
1109
+ * would clobber another engine's models; the throw makes the collision
1110
+ * loud. Hot-reload / test fixtures opt in via `forceRecreate: true`; two
1111
+ * promo engines in one process need two Mongoose connections.
1112
+ */
1113
+ declare class PromoModelCollisionError extends PromoError {
1114
+ constructor(modelName: string);
1115
+ }
1001
1116
  //#endregion
1002
1117
  //#region src/events/event-constants.d.ts
1003
1118
  declare const PromoEvents: {
@@ -1031,237 +1146,20 @@ interface InProcessPromoBusOptions {
1031
1146
  };
1032
1147
  }
1033
1148
  //#endregion
1034
- //#region src/events/promo-event-catalog.d.ts
1035
- interface PromoEventSchema {
1036
- type: 'object';
1037
- properties?: Record<string, {
1038
- type?: string;
1039
- format?: string;
1040
- [key: string]: unknown;
1041
- }>;
1042
- required?: string[];
1043
- [key: string]: unknown;
1044
- }
1045
- interface PromoEventDefinition<TSchema extends z.ZodType = z.ZodType> {
1046
- readonly name: string;
1047
- readonly version: number;
1048
- readonly description?: string;
1049
- readonly schema: PromoEventSchema;
1050
- readonly zodSchema: TSchema;
1051
- create(payload: z.infer<TSchema>, meta?: Partial<DomainEvent$1['meta']>): DomainEvent$1<z.infer<TSchema>>;
1052
- readonly __payload?: z.infer<TSchema>;
1053
- }
1054
- type PromoEventPayloadOf<D> = D extends PromoEventDefinition<infer S> ? z.infer<S> : never;
1055
- /** Mirrors `ProgramLifecyclePayload`. */
1056
- declare const programLifecycleSchema: z.ZodObject<{
1057
- programId: z.ZodString;
1058
- programType: z.ZodString;
1059
- status: z.ZodString;
1060
- actorId: z.ZodOptional<z.ZodString>;
1061
- }, z.core.$strip>;
1062
- /** Mirrors `RulePayload`. */
1063
- declare const ruleSchema: z.ZodObject<{
1064
- programId: z.ZodString;
1065
- ruleId: z.ZodString;
1066
- actorId: z.ZodOptional<z.ZodString>;
1067
- }, z.core.$strip>;
1068
- /** Mirrors `RewardPayload`. */
1069
- declare const rewardSchema: z.ZodObject<{
1070
- programId: z.ZodString;
1071
- rewardId: z.ZodString;
1072
- actorId: z.ZodOptional<z.ZodString>;
1073
- }, z.core.$strip>;
1074
- /** Mirrors `VoucherGeneratedPayload`. */
1075
- declare const voucherGeneratedSchema: z.ZodObject<{
1076
- programId: z.ZodString;
1077
- voucherIds: z.ZodArray<z.ZodString>;
1078
- codes: z.ZodArray<z.ZodString>;
1079
- count: z.ZodNumber;
1080
- actorId: z.ZodOptional<z.ZodString>;
1081
- }, z.core.$strip>;
1082
- /** Mirrors `VoucherRedeemedPayload`. */
1083
- declare const voucherRedeemedSchema: z.ZodObject<{
1084
- voucherId: z.ZodString;
1085
- code: z.ZodString;
1086
- orderId: z.ZodString;
1087
- discountAmount: z.ZodNumber;
1088
- customerId: z.ZodOptional<z.ZodString>;
1089
- }, z.core.$strip>;
1090
- /**
1091
- * Mirrors `VoucherLifecyclePayload` — shared by VOUCHER_CANCELLED,
1092
- * VOUCHER_EXPIRED, and GIFT_CARD_EXHAUSTED (repo emits `status: 'cancelled'`
1093
- * / `'used'` / host-supplied terminal value).
1094
- */
1095
- declare const voucherLifecycleSchema: z.ZodObject<{
1096
- voucherId: z.ZodString;
1097
- code: z.ZodString;
1098
- status: z.ZodString;
1099
- }, z.core.$strip>;
1100
- /** Mirrors `GiftCardSpentPayload`. */
1101
- declare const giftCardSpentSchema: z.ZodObject<{
1102
- voucherId: z.ZodString;
1103
- code: z.ZodString;
1104
- amount: z.ZodNumber;
1105
- remainingBalance: z.ZodNumber;
1106
- orderId: z.ZodString;
1107
- }, z.core.$strip>;
1108
- /** Mirrors `GiftCardToppedUpPayload`. */
1109
- declare const giftCardToppedUpSchema: z.ZodObject<{
1110
- voucherId: z.ZodString;
1111
- code: z.ZodString;
1112
- amount: z.ZodNumber;
1113
- newBalance: z.ZodNumber;
1114
- }, z.core.$strip>;
1115
- /** Mirrors `EvaluationCompletedPayload`. */
1116
- declare const evaluationCompletedSchema: z.ZodObject<{
1117
- evaluationId: z.ZodString;
1118
- totalDiscount: z.ZodNumber;
1119
- programsApplied: z.ZodNumber;
1120
- codesUsed: z.ZodArray<z.ZodString>;
1121
- isPreview: z.ZodBoolean;
1122
- }, z.core.$strip>;
1123
- /** Mirrors `EvaluationCommittedPayload`. */
1124
- declare const evaluationCommittedSchema: z.ZodObject<{
1125
- evaluationId: z.ZodString;
1126
- orderId: z.ZodString;
1127
- totalDiscount: z.ZodNumber;
1128
- }, z.core.$strip>;
1129
- /** Single-field rollback payload — emitted by `evaluation.service.ts`. */
1130
- declare const evaluationRolledBackSchema: z.ZodObject<{
1131
- evaluationId: z.ZodString;
1132
- }, z.core.$strip>;
1133
- type ProgramLifecyclePayloadSchema = z.infer<typeof programLifecycleSchema>;
1134
- type RulePayloadSchema = z.infer<typeof ruleSchema>;
1135
- type RewardPayloadSchema = z.infer<typeof rewardSchema>;
1136
- type VoucherGeneratedPayloadSchema = z.infer<typeof voucherGeneratedSchema>;
1137
- type VoucherRedeemedPayloadSchema = z.infer<typeof voucherRedeemedSchema>;
1138
- type VoucherLifecyclePayloadSchema = z.infer<typeof voucherLifecycleSchema>;
1139
- type GiftCardSpentPayloadSchema = z.infer<typeof giftCardSpentSchema>;
1140
- type GiftCardToppedUpPayloadSchema = z.infer<typeof giftCardToppedUpSchema>;
1141
- type EvaluationCompletedPayloadSchema = z.infer<typeof evaluationCompletedSchema>;
1142
- type EvaluationCommittedPayloadSchema = z.infer<typeof evaluationCommittedSchema>;
1143
- type EvaluationRolledBackPayloadSchema = z.infer<typeof evaluationRolledBackSchema>;
1144
- declare const ProgramCreated: PromoEventDefinition<z.ZodObject<{
1145
- programId: z.ZodString;
1146
- programType: z.ZodString;
1147
- status: z.ZodString;
1148
- actorId: z.ZodOptional<z.ZodString>;
1149
- }, z.core.$strip>>;
1150
- declare const ProgramActivated: PromoEventDefinition<z.ZodObject<{
1151
- programId: z.ZodString;
1152
- programType: z.ZodString;
1153
- status: z.ZodString;
1154
- actorId: z.ZodOptional<z.ZodString>;
1155
- }, z.core.$strip>>;
1156
- declare const ProgramPaused: PromoEventDefinition<z.ZodObject<{
1157
- programId: z.ZodString;
1158
- programType: z.ZodString;
1159
- status: z.ZodString;
1160
- actorId: z.ZodOptional<z.ZodString>;
1161
- }, z.core.$strip>>;
1162
- declare const ProgramArchived: PromoEventDefinition<z.ZodObject<{
1163
- programId: z.ZodString;
1164
- programType: z.ZodString;
1165
- status: z.ZodString;
1166
- actorId: z.ZodOptional<z.ZodString>;
1167
- }, z.core.$strip>>;
1168
- declare const RuleAdded: PromoEventDefinition<z.ZodObject<{
1169
- programId: z.ZodString;
1170
- ruleId: z.ZodString;
1171
- actorId: z.ZodOptional<z.ZodString>;
1172
- }, z.core.$strip>>;
1173
- declare const RuleUpdated: PromoEventDefinition<z.ZodObject<{
1174
- programId: z.ZodString;
1175
- ruleId: z.ZodString;
1176
- actorId: z.ZodOptional<z.ZodString>;
1177
- }, z.core.$strip>>;
1178
- declare const RuleRemoved: PromoEventDefinition<z.ZodObject<{
1179
- programId: z.ZodString;
1180
- ruleId: z.ZodString;
1181
- actorId: z.ZodOptional<z.ZodString>;
1182
- }, z.core.$strip>>;
1183
- declare const RewardAdded: PromoEventDefinition<z.ZodObject<{
1184
- programId: z.ZodString;
1185
- rewardId: z.ZodString;
1186
- actorId: z.ZodOptional<z.ZodString>;
1187
- }, z.core.$strip>>;
1188
- declare const RewardUpdated: PromoEventDefinition<z.ZodObject<{
1189
- programId: z.ZodString;
1190
- rewardId: z.ZodString;
1191
- actorId: z.ZodOptional<z.ZodString>;
1192
- }, z.core.$strip>>;
1193
- declare const RewardRemoved: PromoEventDefinition<z.ZodObject<{
1194
- programId: z.ZodString;
1195
- rewardId: z.ZodString;
1196
- actorId: z.ZodOptional<z.ZodString>;
1197
- }, z.core.$strip>>;
1198
- declare const VoucherGenerated: PromoEventDefinition<z.ZodObject<{
1199
- programId: z.ZodString;
1200
- voucherIds: z.ZodArray<z.ZodString>;
1201
- codes: z.ZodArray<z.ZodString>;
1202
- count: z.ZodNumber;
1203
- actorId: z.ZodOptional<z.ZodString>;
1204
- }, z.core.$strip>>;
1205
- declare const VoucherRedeemed: PromoEventDefinition<z.ZodObject<{
1206
- voucherId: z.ZodString;
1207
- code: z.ZodString;
1208
- orderId: z.ZodString;
1209
- discountAmount: z.ZodNumber;
1210
- customerId: z.ZodOptional<z.ZodString>;
1211
- }, z.core.$strip>>;
1212
- declare const VoucherCancelled: PromoEventDefinition<z.ZodObject<{
1213
- voucherId: z.ZodString;
1214
- code: z.ZodString;
1215
- status: z.ZodString;
1216
- }, z.core.$strip>>;
1217
- declare const VoucherExpired: PromoEventDefinition<z.ZodObject<{
1218
- voucherId: z.ZodString;
1219
- code: z.ZodString;
1220
- status: z.ZodString;
1221
- }, z.core.$strip>>;
1222
- declare const GiftCardSpent: PromoEventDefinition<z.ZodObject<{
1223
- voucherId: z.ZodString;
1224
- code: z.ZodString;
1225
- amount: z.ZodNumber;
1226
- remainingBalance: z.ZodNumber;
1227
- orderId: z.ZodString;
1228
- }, z.core.$strip>>;
1229
- declare const GiftCardToppedUp: PromoEventDefinition<z.ZodObject<{
1230
- voucherId: z.ZodString;
1231
- code: z.ZodString;
1232
- amount: z.ZodNumber;
1233
- newBalance: z.ZodNumber;
1234
- }, z.core.$strip>>;
1235
- declare const GiftCardExhausted: PromoEventDefinition<z.ZodObject<{
1236
- voucherId: z.ZodString;
1237
- code: z.ZodString;
1238
- status: z.ZodString;
1239
- }, z.core.$strip>>;
1240
- declare const EvaluationCompleted: PromoEventDefinition<z.ZodObject<{
1241
- evaluationId: z.ZodString;
1242
- totalDiscount: z.ZodNumber;
1243
- programsApplied: z.ZodNumber;
1244
- codesUsed: z.ZodArray<z.ZodString>;
1245
- isPreview: z.ZodBoolean;
1246
- }, z.core.$strip>>;
1247
- declare const EvaluationCommitted: PromoEventDefinition<z.ZodObject<{
1248
- evaluationId: z.ZodString;
1249
- orderId: z.ZodString;
1250
- totalDiscount: z.ZodNumber;
1251
- }, z.core.$strip>>;
1252
- declare const EvaluationRolledBack: PromoEventDefinition<z.ZodObject<{
1253
- evaluationId: z.ZodString;
1254
- }, z.core.$strip>>;
1255
- /**
1256
- * Every promo event defined in the package — pass to Arc's
1257
- * `EventRegistry`. Hosts wire ONE array; the whole `promo.*` namespace
1258
- * becomes introspectable via OpenAPI and auto-validated at publish time
1259
- * when `eventPlugin({ validateMode: 'reject' })` is set.
1260
- */
1261
- declare const promoEventDefinitions: ReadonlyArray<PromoEventDefinition>;
1262
- //#endregion
1263
1149
  //#region src/index.d.ts
1264
1150
  declare function resolveConfig(config: PromoConfig): ResolvedConfig;
1151
+ /**
1152
+ * Boot-time capability gate (PACKAGE_RULES "Runtime capabilities").
1153
+ * commit / redeem / spend / topUp are transactional — assert the backend
1154
+ * declares `transactions` at engine creation so misconfigured deployments
1155
+ * fail loud at boot instead of with a cryptic error on the first checkout.
1156
+ * `allowNonTransactional: true` is the explicit opt-out for standalone
1157
+ * MongoDB (dev / CI) — the unit-of-work then falls back to
1158
+ * non-transactional execution.
1159
+ *
1160
+ * Exported for unit testing; hosts never call it directly.
1161
+ */
1162
+ declare function assertPromoCapabilities(capabilities: Record<string, unknown> | undefined): void;
1265
1163
  interface PromoEngine {
1266
1164
  models: PromoModels;
1267
1165
  repositories: PromoRepositories;
@@ -1308,4 +1206,4 @@ interface PromoEngine {
1308
1206
  */
1309
1207
  declare function createPromoEngine(config: PromoConfig): PromoEngine;
1310
1208
  //#endregion
1311
- export { type BalanceLedgerEntry, CartHashMismatchError, type CartItem, type CommitOptions, type CommitResult, ConcurrencyConflictError, type CreateProgramInput, type CreateRewardInput, type CreateRuleInput, type DiscountLine, type DomainEvent, DuplicateRedemptionError, type EvaluateInput, EvaluationCommitted, type EvaluationCommittedPayloadSchema, EvaluationCompleted, type EvaluationCompletedPayloadSchema, EvaluationNotFoundError, type EvaluationResult, EvaluationRolledBack, type EvaluationRolledBackPayloadSchema, type EvaluationStore, type EvaluationStoreContext, type EventHandler, type EventTransport, type FreeProductLine, type GenerateCodesInput, type GenerateSingleCodeInput, type GiftCardBalance, GiftCardExhausted, GiftCardExhaustedError, type GiftCardSpendInput, GiftCardSpent, type GiftCardSpentPayloadSchema, type GiftCardTopUpInput, GiftCardToppedUp, type GiftCardToppedUpPayloadSchema, InMemoryEvaluationStore, type InProcessPromoBusOptions, InsufficientBalanceError, InvalidTransitionError, type ListQuery, MongoEvaluationStore, type PaginatedResult, type PendingEvaluationDocument, PendingEvaluationRepository, type PluginType, type Program, ProgramActivated, ProgramArchived, ProgramCreated, type ProgramLifecyclePayloadSchema, ProgramNotFoundError, ProgramPaused, ProgramRepository, ProgramUsageCapExceededError, type PromoConfig, type PromoContext, PromoEngine, PromoError, type PromoEventDefinition, type PromoEventName, type PromoEventPayloadOf, type PromoEventSchema, PromoEvents, type PromoModels, type PromoRepositories, type PromoServices, type RedeemVoucherInput, type RejectedCode, type RepositoryDispatchDeps, type RepositoryPluginsMap, type ResolvedConfig, type ResolvedTenant, type Reward, RewardAdded, RewardNotFoundError, type RewardPayloadSchema, RewardRemoved, RewardRepository, RewardUpdated, type Rule, RuleAdded, RuleNotFoundError, type RulePayloadSchema, RuleRemoved, RuleRepository, RuleUpdated, type StoredEvaluationSnapshot, type TenantFieldType, TenantIsolationError, type UpdateProgramInput, type UpdateRewardInput, type UpdateRuleInput, ValidationError, type Voucher, VoucherCancelled, VoucherExhaustedError, VoucherExpired, VoucherExpiredError, VoucherGenerated, type VoucherGeneratedPayloadSchema, type VoucherLifecyclePayloadSchema, VoucherNotFoundError, VoucherRedeemed, type VoucherRedeemedPayloadSchema, type VoucherRedemption, VoucherRepository, type VoucherValidation, createPromoEngine, promoEventDefinitions, resolveConfig };
1209
+ export { type BalanceLedgerEntry, CartHashMismatchError, type CartItem, type CommitOptions, type CommitResult, ConcurrencyConflictError, type CreateProgramInput, type CreateRewardInput, type CreateRuleInput, DEFAULT_COLLECTIONS, type DiscountLine, type DispatchDeps, type DispatchLogger, type DomainEvent, DuplicateRedemptionError, DuplicateVoucherCodeError, type EvaluateInput, EvaluationCommitted, type EvaluationCommittedPayloadSchema, EvaluationCompleted, type EvaluationCompletedPayloadSchema, EvaluationNotFoundError, type EvaluationResult, EvaluationRolledBack, type EvaluationRolledBackPayloadSchema, type EvaluationStore, type EvaluationStoreContext, type EventHandler, type EventTransport, type FreeProductLine, type GenerateCodesInput, type GenerateSingleCodeInput, type GiftCardBalance, GiftCardExhausted, GiftCardExhaustedError, type GiftCardSpendInput, GiftCardSpent, type GiftCardSpentPayloadSchema, type GiftCardTopUpInput, GiftCardToppedUp, type GiftCardToppedUpPayloadSchema, InMemoryEvaluationStore, type InProcessPromoBusOptions, InsufficientBalanceError, InvalidTransitionError, type ListQuery, MongoEvaluationStore, PENDING_PROMO_EVENTS, type PaginatedResult, type PendingEvaluationDocument, PendingEvaluationRepository, type PluginType, type Program, ProgramActivated, ProgramArchived, ProgramCreated, type ProgramLifecyclePayloadSchema, ProgramNotFoundError, ProgramPaused, ProgramRepository, ProgramUsageCapExceededError, type PromoConfig, type PromoContext, PromoEngine, PromoError, type PromoEventDefinition, type PromoEventName, type PromoEventPayloadOf, type PromoEventSchema, PromoEvents, PromoModelCollisionError, type PromoModels, type PromoRepositories, type PromoServices, type RedeemVoucherInput, type RejectedCode, type RepositoryDispatchDeps, type RepositoryPluginsMap, type ResolvedConfig, type ResolvedTenant, type Reward, RewardAdded, RewardNotFoundError, type RewardPayloadSchema, RewardRemoved, RewardRepository, RewardUpdated, type Rule, RuleAdded, RuleNotFoundError, type RulePayloadSchema, RuleRemoved, RuleRepository, RuleUpdated, type StoredEvaluationSnapshot, type TenantFieldType, TenantIsolationError, type UpdateProgramInput, type UpdateRewardInput, type UpdateRuleInput, ValidationError, type Voucher, VoucherCancelled, VoucherExhaustedError, VoucherExpired, VoucherExpiredError, VoucherGenerated, type VoucherGeneratedPayloadSchema, type VoucherLifecyclePayloadSchema, VoucherNotFoundError, VoucherRedeemed, type VoucherRedeemedPayloadSchema, type VoucherRedemption, VoucherRepository, type VoucherValidation, assertPromoCapabilities, createPromoEngine, flushPendingPromoEvents, promoEventDefinitions, resolveConfig };