@classytic/promo 0.1.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.
@@ -0,0 +1,81 @@
1
+ import { c as Rule, i as ProgramPort, l as Reward, n as RulePort, o as Voucher, r as RewardPort, t as VoucherPort, u as Program } from "./voucher.port-yxfb3MHJ.mjs";
2
+ import { t as PromoModels } from "./index-B7lLH19a.mjs";
3
+ import { Model } from "mongoose";
4
+ import { PluginType } from "@classytic/mongokit";
5
+
6
+ //#region src/repositories/program.repository.d.ts
7
+ declare class ProgramRepository implements ProgramPort {
8
+ private repo;
9
+ private tenantField;
10
+ constructor(model: Model<any>, plugins?: PluginType[], tenantField?: string);
11
+ create(data: Record<string, unknown>, session?: unknown): Promise<Program>;
12
+ getById(id: string, tenantId?: string): Promise<Program | null>;
13
+ update(id: string, data: Record<string, unknown>, tenantId?: string, session?: unknown): Promise<Program>;
14
+ findMany(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<Program[]>;
15
+ findActive(tenantId?: string, now?: Date): Promise<Program[]>;
16
+ incrementUsage(id: string, tenantId?: string, session?: unknown): Promise<Program>;
17
+ decrementUsage(id: string, tenantId?: string, session?: unknown): Promise<Program>;
18
+ getCustomerUsage(id: string, customerId: string, tenantId?: string): Promise<number>;
19
+ incrementCustomerUsage(id: string, customerId: string, tenantId?: string, session?: unknown): Promise<Program>;
20
+ }
21
+ //#endregion
22
+ //#region src/repositories/reward.repository.d.ts
23
+ declare class RewardRepository implements RewardPort {
24
+ private repo;
25
+ private tenantField;
26
+ constructor(model: Model<any>, plugins?: PluginType[], tenantField?: string);
27
+ create(data: Record<string, unknown>, session?: unknown): Promise<Reward>;
28
+ getById(id: string, tenantId?: string): Promise<Reward | null>;
29
+ update(id: string, data: Record<string, unknown>, tenantId?: string, session?: unknown): Promise<Reward>;
30
+ delete(id: string, tenantId?: string, session?: unknown): Promise<void>;
31
+ findByProgramId(programId: string, tenantId?: string): Promise<Reward[]>;
32
+ findByProgramIds(programIds: string[], tenantId?: string): Promise<Reward[]>;
33
+ }
34
+ //#endregion
35
+ //#region src/repositories/rule.repository.d.ts
36
+ declare class RuleRepository implements RulePort {
37
+ private repo;
38
+ private tenantField;
39
+ constructor(model: Model<any>, plugins?: PluginType[], tenantField?: string);
40
+ create(data: Record<string, unknown>, session?: unknown): Promise<Rule>;
41
+ getById(id: string, tenantId?: string): Promise<Rule | null>;
42
+ update(id: string, data: Record<string, unknown>, tenantId?: string, session?: unknown): Promise<Rule>;
43
+ delete(id: string, tenantId?: string, session?: unknown): Promise<void>;
44
+ findByProgramId(programId: string, tenantId?: string): Promise<Rule[]>;
45
+ findByCode(code: string, tenantId?: string): Promise<Rule | null>;
46
+ findByProgramIds(programIds: string[], tenantId?: string): Promise<Rule[]>;
47
+ }
48
+ //#endregion
49
+ //#region src/repositories/voucher.repository.d.ts
50
+ declare class VoucherRepository implements VoucherPort {
51
+ private repo;
52
+ private tenantField;
53
+ constructor(model: Model<any>, plugins?: PluginType[], tenantField?: string);
54
+ create(data: Record<string, unknown>, session?: unknown): Promise<Voucher>;
55
+ createMany(data: Record<string, unknown>[], session?: unknown): Promise<Voucher[]>;
56
+ getById(id: string, tenantId?: string): Promise<Voucher | null>;
57
+ getByCode(code: string, tenantId?: string): Promise<Voucher | null>;
58
+ update(id: string, data: Record<string, unknown>, tenantId?: string, session?: unknown): Promise<Voucher>;
59
+ findMany(query: Record<string, unknown>, options?: Record<string, unknown>): Promise<Voucher[]>;
60
+ incrementUsage(id: string, redemption: Record<string, unknown>, tenantId?: string, session?: unknown): Promise<Voucher>;
61
+ addLedgerEntry(id: string, entry: Record<string, unknown>, balanceDelta: number, tenantId?: string, session?: unknown): Promise<Voucher>;
62
+ expireByDate(before: Date, tenantId?: string): Promise<number>;
63
+ hasIdempotencyKey(id: string, key: string): Promise<boolean>;
64
+ }
65
+ //#endregion
66
+ //#region src/repositories/index.d.ts
67
+ interface PromoRepositories {
68
+ program: ProgramRepository;
69
+ rule: RuleRepository;
70
+ reward: RewardRepository;
71
+ voucher: VoucherRepository;
72
+ }
73
+ interface RepositoryPluginsMap {
74
+ program?: PluginType[];
75
+ rule?: PluginType[];
76
+ reward?: PluginType[];
77
+ voucher?: PluginType[];
78
+ }
79
+ declare function createRepositories(models: PromoModels, plugins?: RepositoryPluginsMap, tenantField?: string): PromoRepositories;
80
+ //#endregion
81
+ export { RuleRepository as a, VoucherRepository as i, RepositoryPluginsMap as n, RewardRepository as o, createRepositories as r, ProgramRepository as s, PromoRepositories as t };
@@ -0,0 +1,20 @@
1
+ import { a as BalanceLedgerEntry, c as Rule, l as Reward, o as Voucher, s as VoucherRedemption, u as Program } from "./voucher.port-yxfb3MHJ.mjs";
2
+ import { t as EventEmitterPort } from "./event-emitter.port-DaodlJSG.mjs";
3
+ import { o as PromoEvents } from "./index-C52zSBkI.mjs";
4
+ import { i as PromoConfig, o as ResolvedConfig, s as ResolvedTenant } from "./config-iZjn_8pp.mjs";
5
+ import { t as PromoModels } from "./index-B7lLH19a.mjs";
6
+ import { t as PromoRepositories } from "./index-l09KqnlE.mjs";
7
+ import { C as UpdateRuleInput, S as UpdateRewardInput, _ as GiftCardTopUpInput, a as GiftCardBalance, b as RedeemVoucherInput, c as VoucherValidation, d as CreateRewardInput, f as CreateRuleInput, g as GiftCardSpendInput, h as GenerateSingleCodeInput, i as FreeProductLine, l as CartItem, m as GenerateCodesInput, n as DiscountLine, o as PaginatedResult, p as EvaluateInput, r as EvaluationResult, s as RejectedCode, t as CommitResult, u as CreateProgramInput, v as ListQuery, x as UpdateProgramInput, y as PromoContext } from "./results-Ca5ZCNbN.mjs";
8
+ import { i as FullProgram, t as PromoServices } from "./index-Cu9iwy4v.mjs";
9
+
10
+ //#region src/index.d.ts
11
+ declare function resolveConfig(config: PromoConfig): ResolvedConfig;
12
+ interface PromoEngine {
13
+ models: PromoModels;
14
+ repositories: PromoRepositories;
15
+ services: PromoServices;
16
+ events: EventEmitterPort;
17
+ }
18
+ declare function createPromoEngine(config: PromoConfig): PromoEngine;
19
+ //#endregion
20
+ export { type BalanceLedgerEntry, type CartItem, type CommitResult, type CreateProgramInput, type CreateRewardInput, type CreateRuleInput, type DiscountLine, type EvaluateInput, type EvaluationResult, type EventEmitterPort, type FreeProductLine, type FullProgram, type GenerateCodesInput, type GenerateSingleCodeInput, type GiftCardBalance, type GiftCardSpendInput, type GiftCardTopUpInput, type ListQuery, type PaginatedResult, type Program, type PromoConfig, type PromoContext, PromoEngine, PromoEvents, type PromoModels, type PromoRepositories, type PromoServices, type RedeemVoucherInput, type RejectedCode, type ResolvedConfig, type ResolvedTenant, type Reward, type Rule, type UpdateProgramInput, type UpdateRewardInput, type UpdateRuleInput, type Voucher, type VoucherRedemption, type VoucherValidation, createPromoEngine, resolveConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,86 @@
1
+ import { n as DEFAULT_TENANT_TYPE, t as DEFAULT_TENANT_FIELD } from "./constants-BVajdyL3.mjs";
2
+ import { l as ValidationError } from "./domain-errors-BEkXvy5O.mjs";
3
+ import { t as InProcessEventBus } from "./events-CprEWlN7.mjs";
4
+ import { t as createModels } from "./models-DdBNae7h.mjs";
5
+ import { t as createRepositories } from "./repositories-DgZIY9wD.mjs";
6
+ import { t as PromoEvents } from "./event-types-CsTV1FKX.mjs";
7
+ import { t as createServices } from "./services-Cz0gHrmX.mjs";
8
+ //#region src/index.ts
9
+ function resolveConfig(config) {
10
+ let tenant;
11
+ if (config.tenant === false) tenant = {
12
+ enabled: false,
13
+ field: "",
14
+ type: "ObjectId",
15
+ contextKey: ""
16
+ };
17
+ else if (config.tenant === void 0) tenant = {
18
+ enabled: true,
19
+ field: DEFAULT_TENANT_FIELD,
20
+ type: DEFAULT_TENANT_TYPE,
21
+ contextKey: DEFAULT_TENANT_FIELD
22
+ };
23
+ else tenant = {
24
+ enabled: true,
25
+ field: config.tenant.field ?? "organizationId",
26
+ type: config.tenant.type ?? "ObjectId",
27
+ contextKey: config.tenant.contextKey ?? config.tenant.field ?? "organizationId",
28
+ ...config.tenant.ref ? { ref: config.tenant.ref } : {}
29
+ };
30
+ const resolved = {
31
+ evaluation: {
32
+ maxStackablePromotions: config.evaluation?.maxStackablePromotions ?? 5,
33
+ allowExclusiveAndStackable: config.evaluation?.allowExclusiveAndStackable ?? true
34
+ },
35
+ voucher: {
36
+ codeLength: config.voucher?.codeLength ?? 8,
37
+ codePrefix: config.voucher?.codePrefix ?? "",
38
+ defaultExpiryDays: config.voucher?.defaultExpiryDays ?? null
39
+ },
40
+ giftCard: {
41
+ allowNegativeBalance: config.giftCard?.allowNegativeBalance ?? false,
42
+ maxBalance: config.giftCard?.maxBalance ?? null
43
+ },
44
+ tenant
45
+ };
46
+ if (resolved.voucher.codeLength < 4) throw new ValidationError("voucher codeLength must be >= 4");
47
+ if (resolved.evaluation.maxStackablePromotions < 1) throw new ValidationError("maxStackablePromotions must be >= 1");
48
+ return resolved;
49
+ }
50
+ var MongoUnitOfWork = class {
51
+ constructor(connection) {
52
+ this.connection = connection;
53
+ }
54
+ async withTransaction(cb) {
55
+ const session = await this.connection.startSession();
56
+ try {
57
+ let result;
58
+ await session.withTransaction(async () => {
59
+ result = await cb(session);
60
+ });
61
+ return result;
62
+ } finally {
63
+ await session.endSession();
64
+ }
65
+ }
66
+ };
67
+ function createPromoEngine(config) {
68
+ const resolvedConfig = resolveConfig(config);
69
+ const models = createModels(config.mongoose, resolvedConfig.tenant, config.indexes);
70
+ const repositories = createRepositories(models, config.plugins, resolvedConfig.tenant.field);
71
+ const unitOfWork = new MongoUnitOfWork(config.mongoose);
72
+ const events = config.events?.adapter ?? new InProcessEventBus();
73
+ return {
74
+ models,
75
+ repositories,
76
+ services: createServices({
77
+ repositories,
78
+ unitOfWork,
79
+ events,
80
+ config: resolvedConfig
81
+ }),
82
+ events
83
+ };
84
+ }
85
+ //#endregion
86
+ export { PromoEvents, createPromoEngine, resolveConfig };
@@ -0,0 +1,2 @@
1
+ import { n as createModels, t as PromoModels } from "../index-B7lLH19a.mjs";
2
+ export { PromoModels, createModels };
@@ -0,0 +1,2 @@
1
+ import { t as createModels } from "../models-DdBNae7h.mjs";
2
+ export { createModels };
@@ -0,0 +1,277 @@
1
+ import { a as PROGRAM_STATUSES, c as REWARD_TYPES, d as VOUCHER_STATUSES, i as DISCOUNT_SCOPES, l as STACKING_MODES, r as DISCOUNT_MODES, s as PROGRAM_TYPES, u as TRIGGER_MODES } from "./constants-BVajdyL3.mjs";
2
+ import mongoose from "mongoose";
3
+ //#region src/models/create-model.ts
4
+ function injectTenantField(schema, tenant) {
5
+ if (!tenant.enabled) return;
6
+ schema.add({ [tenant.field]: {
7
+ type: tenant.type === "ObjectId" ? mongoose.Schema.Types.ObjectId : String,
8
+ required: true,
9
+ index: true,
10
+ ...tenant.ref ? { ref: tenant.ref } : {}
11
+ } });
12
+ const existingIndexes = schema._indexes;
13
+ if (existingIndexes && existingIndexes.length > 0) for (const indexEntry of existingIndexes) {
14
+ const fields = indexEntry[0];
15
+ const newFields = { [tenant.field]: 1 };
16
+ for (const [key, val] of Object.entries(fields)) newFields[key] = val;
17
+ indexEntry[0] = newFields;
18
+ }
19
+ }
20
+ function applyUserIndexes(schema, indexes, tenant) {
21
+ for (const def of indexes) {
22
+ const fields = tenant.enabled ? {
23
+ [tenant.field]: 1,
24
+ ...def.fields
25
+ } : { ...def.fields };
26
+ schema.index(fields, def.options ?? {});
27
+ }
28
+ }
29
+ //#endregion
30
+ //#region src/models/schemas/program.schema.ts
31
+ const { Schema: Schema$3 } = mongoose;
32
+ function createProgramSchema() {
33
+ const schema = new Schema$3({
34
+ name: {
35
+ type: String,
36
+ required: true
37
+ },
38
+ description: { type: String },
39
+ programType: {
40
+ type: String,
41
+ enum: PROGRAM_TYPES,
42
+ required: true
43
+ },
44
+ triggerMode: {
45
+ type: String,
46
+ enum: TRIGGER_MODES,
47
+ required: true
48
+ },
49
+ status: {
50
+ type: String,
51
+ enum: PROGRAM_STATUSES,
52
+ default: "draft"
53
+ },
54
+ stackingMode: {
55
+ type: String,
56
+ enum: STACKING_MODES,
57
+ default: "exclusive"
58
+ },
59
+ priority: {
60
+ type: Number,
61
+ default: 0
62
+ },
63
+ startsAt: { type: Date },
64
+ endsAt: { type: Date },
65
+ maxUsageTotal: { type: Number },
66
+ usedCount: {
67
+ type: Number,
68
+ default: 0
69
+ },
70
+ maxUsagePerCustomer: { type: Number },
71
+ applicableCustomerIds: [{ type: String }],
72
+ applicableCustomerTags: [{ type: String }],
73
+ customerUsageCounts: {
74
+ type: Map,
75
+ of: Number,
76
+ default: () => /* @__PURE__ */ new Map()
77
+ },
78
+ metadata: { type: Schema$3.Types.Mixed }
79
+ }, { timestamps: true });
80
+ schema.index({
81
+ status: 1,
82
+ programType: 1,
83
+ priority: -1
84
+ });
85
+ schema.index({
86
+ status: 1,
87
+ startsAt: 1,
88
+ endsAt: 1
89
+ });
90
+ schema.index({
91
+ status: 1,
92
+ priority: -1,
93
+ _id: -1
94
+ });
95
+ return schema;
96
+ }
97
+ //#endregion
98
+ //#region src/models/schemas/reward.schema.ts
99
+ const { Schema: Schema$2 } = mongoose;
100
+ function createRewardSchema() {
101
+ const schema = new Schema$2({
102
+ programId: {
103
+ type: Schema$2.Types.ObjectId,
104
+ required: true
105
+ },
106
+ ruleId: { type: Schema$2.Types.ObjectId },
107
+ rewardType: {
108
+ type: String,
109
+ enum: REWARD_TYPES,
110
+ required: true
111
+ },
112
+ discountMode: {
113
+ type: String,
114
+ enum: DISCOUNT_MODES
115
+ },
116
+ discountAmount: { type: Number },
117
+ maxDiscountAmount: { type: Number },
118
+ discountScope: {
119
+ type: String,
120
+ enum: DISCOUNT_SCOPES,
121
+ default: "order"
122
+ },
123
+ applicableProductIds: [{ type: String }],
124
+ freeProductId: { type: String },
125
+ freeProductSku: { type: String },
126
+ freeQuantity: {
127
+ type: Number,
128
+ default: 1
129
+ },
130
+ giftCardAmount: { type: Number },
131
+ metadata: { type: Schema$2.Types.Mixed }
132
+ }, { timestamps: true });
133
+ schema.index({ programId: 1 });
134
+ schema.index({ ruleId: 1 }, { sparse: true });
135
+ return schema;
136
+ }
137
+ //#endregion
138
+ //#region src/models/schemas/rule.schema.ts
139
+ const { Schema: Schema$1 } = mongoose;
140
+ function createRuleSchema() {
141
+ const schema = new Schema$1({
142
+ programId: {
143
+ type: Schema$1.Types.ObjectId,
144
+ required: true
145
+ },
146
+ name: { type: String },
147
+ minimumAmount: {
148
+ type: Number,
149
+ default: 0
150
+ },
151
+ minimumQuantity: {
152
+ type: Number,
153
+ default: 0
154
+ },
155
+ applicableProductIds: [{ type: String }],
156
+ applicableCategories: [{ type: String }],
157
+ applicableSkus: [{ type: String }],
158
+ buyQuantity: { type: Number },
159
+ code: {
160
+ type: String,
161
+ uppercase: true,
162
+ trim: true
163
+ },
164
+ startsAt: { type: Date },
165
+ endsAt: { type: Date },
166
+ metadata: { type: Schema$1.Types.Mixed }
167
+ }, { timestamps: true });
168
+ schema.index({ programId: 1 });
169
+ schema.index({ code: 1 }, { sparse: true });
170
+ return schema;
171
+ }
172
+ //#endregion
173
+ //#region src/models/schemas/voucher.schema.ts
174
+ const { Schema } = mongoose;
175
+ function createVoucherSchema() {
176
+ const schema = new Schema({
177
+ programId: {
178
+ type: Schema.Types.ObjectId,
179
+ required: true,
180
+ index: true
181
+ },
182
+ code: {
183
+ type: String,
184
+ required: true,
185
+ uppercase: true,
186
+ trim: true
187
+ },
188
+ status: {
189
+ type: String,
190
+ enum: VOUCHER_STATUSES,
191
+ default: "active"
192
+ },
193
+ customerId: { type: String },
194
+ usageLimit: {
195
+ type: Number,
196
+ default: 1
197
+ },
198
+ usedCount: {
199
+ type: Number,
200
+ default: 0
201
+ },
202
+ initialBalance: { type: Number },
203
+ currentBalance: { type: Number },
204
+ balanceLedger: [{
205
+ amount: {
206
+ type: Number,
207
+ required: true
208
+ },
209
+ orderId: { type: String },
210
+ description: { type: String },
211
+ createdAt: {
212
+ type: Date,
213
+ default: Date.now
214
+ },
215
+ idempotencyKey: { type: String }
216
+ }],
217
+ expiresAt: { type: Date },
218
+ redemptions: [{
219
+ orderId: {
220
+ type: String,
221
+ required: true
222
+ },
223
+ customerId: { type: String },
224
+ discountAmount: {
225
+ type: Number,
226
+ required: true
227
+ },
228
+ redeemedAt: {
229
+ type: Date,
230
+ default: Date.now
231
+ },
232
+ idempotencyKey: { type: String }
233
+ }],
234
+ metadata: { type: Schema.Types.Mixed }
235
+ }, { timestamps: true });
236
+ schema.index({ code: 1 }, { unique: true });
237
+ schema.index({
238
+ programId: 1,
239
+ status: 1
240
+ });
241
+ schema.index({
242
+ customerId: 1,
243
+ status: 1
244
+ }, { sparse: true });
245
+ schema.index({ expiresAt: 1 }, { sparse: true });
246
+ return schema;
247
+ }
248
+ //#endregion
249
+ //#region src/models/index.ts
250
+ function createModels(connection, tenant, indexes) {
251
+ if (connection.models.PromoProgram && connection.models.PromoRule && connection.models.PromoReward && connection.models.PromoVoucher) return {
252
+ Program: connection.models.PromoProgram,
253
+ Rule: connection.models.PromoRule,
254
+ Reward: connection.models.PromoReward,
255
+ Voucher: connection.models.PromoVoucher
256
+ };
257
+ const programSchema = createProgramSchema();
258
+ const ruleSchema = createRuleSchema();
259
+ const rewardSchema = createRewardSchema();
260
+ const voucherSchema = createVoucherSchema();
261
+ injectTenantField(programSchema, tenant);
262
+ injectTenantField(ruleSchema, tenant);
263
+ injectTenantField(rewardSchema, tenant);
264
+ injectTenantField(voucherSchema, tenant);
265
+ if (indexes?.program) applyUserIndexes(programSchema, indexes.program, tenant);
266
+ if (indexes?.rule) applyUserIndexes(ruleSchema, indexes.rule, tenant);
267
+ if (indexes?.reward) applyUserIndexes(rewardSchema, indexes.reward, tenant);
268
+ if (indexes?.voucher) applyUserIndexes(voucherSchema, indexes.voucher, tenant);
269
+ return {
270
+ Program: connection.models.PromoProgram ?? connection.model("PromoProgram", programSchema),
271
+ Rule: connection.models.PromoRule ?? connection.model("PromoRule", ruleSchema),
272
+ Reward: connection.models.PromoReward ?? connection.model("PromoReward", rewardSchema),
273
+ Voucher: connection.models.PromoVoucher ?? connection.model("PromoVoucher", voucherSchema)
274
+ };
275
+ }
276
+ //#endregion
277
+ export { createModels as t };
@@ -0,0 +1,2 @@
1
+ import { a as RuleRepository, i as VoucherRepository, n as RepositoryPluginsMap, o as RewardRepository, r as createRepositories, s as ProgramRepository, t as PromoRepositories } from "../index-l09KqnlE.mjs";
2
+ export { ProgramRepository, PromoRepositories, RepositoryPluginsMap, RewardRepository, RuleRepository, VoucherRepository, createRepositories };
@@ -0,0 +1,2 @@
1
+ import { a as ProgramRepository, i as RewardRepository, n as VoucherRepository, r as RuleRepository, t as createRepositories } from "../repositories-DgZIY9wD.mjs";
2
+ export { ProgramRepository, RewardRepository, RuleRepository, VoucherRepository, createRepositories };