@classytic/promo 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +226 -22
  3. package/dist/{index-J5BC20DN.d.mts → constants-CrbSSQG5.d.mts} +1 -3
  4. package/dist/{constants-BVajdyL3.mjs → constants-D0Rntp2f.mjs} +1 -3
  5. package/dist/index.d.mts +1301 -10
  6. package/dist/index.mjs +2165 -46
  7. package/dist/schemas/index.d.mts +253 -0
  8. package/dist/schemas/index.mjs +134 -0
  9. package/package.json +23 -37
  10. package/dist/config-iZjn_8pp.d.mts +0 -71
  11. package/dist/domain/enums/index.d.mts +0 -2
  12. package/dist/domain/enums/index.mjs +0 -2
  13. package/dist/domain/index.d.mts +0 -61
  14. package/dist/domain/index.mjs +0 -4
  15. package/dist/domain-errors-BEkXvy5O.mjs +0 -80
  16. package/dist/event-emitter.port-DaodlJSG.d.mts +0 -8
  17. package/dist/event-types-CsTV1FKX.mjs +0 -25
  18. package/dist/events/index.d.mts +0 -2
  19. package/dist/events/index.mjs +0 -3
  20. package/dist/events-CprEWlN7.mjs +0 -25
  21. package/dist/index-B7lLH19a.d.mts +0 -13
  22. package/dist/index-C52zSBkI.d.mts +0 -96
  23. package/dist/index-Cu9iwy4v.d.mts +0 -99
  24. package/dist/index-l09KqnlE.d.mts +0 -81
  25. package/dist/models/index.d.mts +0 -2
  26. package/dist/models/index.mjs +0 -2
  27. package/dist/models-DdBNae7h.mjs +0 -277
  28. package/dist/repositories/index.d.mts +0 -2
  29. package/dist/repositories/index.mjs +0 -2
  30. package/dist/repositories-DgZIY9wD.mjs +0 -295
  31. package/dist/results-Ca5ZCNbN.d.mts +0 -218
  32. package/dist/services/index.d.mts +0 -2
  33. package/dist/services/index.mjs +0 -2
  34. package/dist/services-Cz0gHrmX.mjs +0 -815
  35. package/dist/types/index.d.mts +0 -3
  36. package/dist/types/index.mjs +0 -1
  37. package/dist/unit-of-work.port-DaMW8WZK.d.mts +0 -7
  38. package/dist/voucher.port-yxfb3MHJ.d.mts +0 -146
@@ -1,277 +0,0 @@
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 };
@@ -1,2 +0,0 @@
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 };
@@ -1,2 +0,0 @@
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 };
@@ -1,295 +0,0 @@
1
- import { createRepository } from "@classytic/mongokit";
2
- //#region src/utils/mongoose.ts
3
- function toDoc(doc) {
4
- if (doc == null) return doc;
5
- const d = doc;
6
- return typeof d.toObject === "function" ? d.toObject({ virtuals: true }) : doc;
7
- }
8
- function toDocs(docs) {
9
- return docs.map((d) => toDoc(d));
10
- }
11
- //#endregion
12
- //#region src/repositories/program.repository.ts
13
- var ProgramRepository = class {
14
- repo;
15
- tenantField;
16
- constructor(model, plugins = [], tenantField = "organizationId") {
17
- this.repo = createRepository(model, plugins);
18
- this.tenantField = tenantField;
19
- }
20
- async create(data, session) {
21
- return toDoc(await this.repo.create(data, { session }));
22
- }
23
- async getById(id, tenantId) {
24
- if (tenantId) return toDoc(await this.repo.getByQuery({
25
- _id: id,
26
- [this.tenantField]: tenantId
27
- }, { throwOnNotFound: false }));
28
- return toDoc(await this.repo.getById(id, { throwOnNotFound: false }));
29
- }
30
- async update(id, data, tenantId, session) {
31
- const opts = {
32
- session,
33
- lean: true
34
- };
35
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
36
- return toDoc(await this.repo.update(id, data, opts));
37
- }
38
- async findMany(query, options) {
39
- return toDocs((await this.repo.getAll({
40
- filters: query,
41
- limit: options?.limit ?? 100,
42
- page: options?.page ?? 1,
43
- sort: options?.sort ?? "-priority"
44
- })).docs);
45
- }
46
- async findActive(tenantId, now) {
47
- const currentDate = now ?? /* @__PURE__ */ new Date();
48
- const query = {
49
- status: "active",
50
- $or: [
51
- { startsAt: { $lte: currentDate } },
52
- { startsAt: { $exists: false } },
53
- { startsAt: null }
54
- ]
55
- };
56
- if (tenantId) query[this.tenantField] = tenantId;
57
- return toDocs(await this.repo.Model.find(query).sort({
58
- priority: -1,
59
- _id: -1
60
- }).limit(200).lean()).filter((p) => !p.endsAt || p.endsAt >= currentDate);
61
- }
62
- async incrementUsage(id, tenantId, session) {
63
- const opts = {
64
- session,
65
- lean: true
66
- };
67
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
68
- return toDoc(await this.repo.update(id, { $inc: { usedCount: 1 } }, opts));
69
- }
70
- async decrementUsage(id, tenantId, session) {
71
- const opts = {
72
- session,
73
- lean: true
74
- };
75
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
76
- return toDoc(await this.repo.update(id, { $inc: { usedCount: -1 } }, opts));
77
- }
78
- async getCustomerUsage(id, customerId, tenantId) {
79
- const query = { _id: id };
80
- if (tenantId) query[this.tenantField] = tenantId;
81
- const doc = await this.repo.getByQuery(query, {
82
- throwOnNotFound: false,
83
- lean: true
84
- });
85
- if (!doc) return 0;
86
- const counts = doc.customerUsageCounts;
87
- if (!counts) return 0;
88
- if (counts instanceof Map) return counts.get(customerId) ?? 0;
89
- return counts[customerId] ?? 0;
90
- }
91
- async incrementCustomerUsage(id, customerId, tenantId, session) {
92
- const opts = {
93
- session,
94
- lean: true
95
- };
96
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
97
- return toDoc(await this.repo.update(id, { $inc: { [`customerUsageCounts.${customerId}`]: 1 } }, opts));
98
- }
99
- };
100
- //#endregion
101
- //#region src/repositories/reward.repository.ts
102
- var RewardRepository = class {
103
- repo;
104
- tenantField;
105
- constructor(model, plugins = [], tenantField = "organizationId") {
106
- this.repo = createRepository(model, plugins);
107
- this.tenantField = tenantField;
108
- }
109
- async create(data, session) {
110
- return toDoc(await this.repo.create(data, { session }));
111
- }
112
- async getById(id, tenantId) {
113
- if (tenantId) return toDoc(await this.repo.getByQuery({
114
- _id: id,
115
- [this.tenantField]: tenantId
116
- }, { throwOnNotFound: false }));
117
- return toDoc(await this.repo.getById(id, { throwOnNotFound: false }));
118
- }
119
- async update(id, data, tenantId, session) {
120
- if (tenantId) return toDoc(await this.repo.Model.findOneAndUpdate({
121
- _id: id,
122
- [this.tenantField]: tenantId
123
- }, { $set: data }, {
124
- returnDocument: "after",
125
- ...session ? { session } : {}
126
- }));
127
- return toDoc(await this.repo.update(id, data, { session }));
128
- }
129
- async delete(id, tenantId, session) {
130
- if (tenantId) {
131
- await this.repo.Model.deleteOne({
132
- _id: id,
133
- [this.tenantField]: tenantId
134
- }, { ...session ? { session } : {} });
135
- return;
136
- }
137
- await this.repo.delete(id, { session });
138
- }
139
- async findByProgramId(programId, tenantId) {
140
- const query = { programId };
141
- if (tenantId) query[this.tenantField] = tenantId;
142
- return toDocs(await this.repo.findAll(query));
143
- }
144
- async findByProgramIds(programIds, tenantId) {
145
- const query = { programId: { $in: programIds } };
146
- if (tenantId) query[this.tenantField] = tenantId;
147
- return toDocs(await this.repo.findAll(query));
148
- }
149
- };
150
- //#endregion
151
- //#region src/repositories/rule.repository.ts
152
- var RuleRepository = class {
153
- repo;
154
- tenantField;
155
- constructor(model, plugins = [], tenantField = "organizationId") {
156
- this.repo = createRepository(model, plugins);
157
- this.tenantField = tenantField;
158
- }
159
- async create(data, session) {
160
- return toDoc(await this.repo.create(data, { session }));
161
- }
162
- async getById(id, tenantId) {
163
- if (tenantId) return toDoc(await this.repo.getByQuery({
164
- _id: id,
165
- [this.tenantField]: tenantId
166
- }, { throwOnNotFound: false }));
167
- return toDoc(await this.repo.getById(id, { throwOnNotFound: false }));
168
- }
169
- async update(id, data, tenantId, session) {
170
- if (tenantId) return toDoc(await this.repo.Model.findOneAndUpdate({
171
- _id: id,
172
- [this.tenantField]: tenantId
173
- }, { $set: data }, {
174
- returnDocument: "after",
175
- ...session ? { session } : {}
176
- }));
177
- return toDoc(await this.repo.update(id, data, { session }));
178
- }
179
- async delete(id, tenantId, session) {
180
- if (tenantId) {
181
- await this.repo.Model.deleteOne({
182
- _id: id,
183
- [this.tenantField]: tenantId
184
- }, { ...session ? { session } : {} });
185
- return;
186
- }
187
- await this.repo.delete(id, { session });
188
- }
189
- async findByProgramId(programId, tenantId) {
190
- const query = { programId };
191
- if (tenantId) query[this.tenantField] = tenantId;
192
- return toDocs(await this.repo.findAll(query));
193
- }
194
- async findByCode(code, tenantId) {
195
- const query = { code: code.toUpperCase() };
196
- if (tenantId) query[this.tenantField] = tenantId;
197
- return toDoc(await this.repo.getByQuery(query, { throwOnNotFound: false }));
198
- }
199
- async findByProgramIds(programIds, tenantId) {
200
- const query = { programId: { $in: programIds } };
201
- if (tenantId) query[this.tenantField] = tenantId;
202
- return toDocs(await this.repo.findAll(query));
203
- }
204
- };
205
- //#endregion
206
- //#region src/repositories/voucher.repository.ts
207
- var VoucherRepository = class {
208
- repo;
209
- tenantField;
210
- constructor(model, plugins = [], tenantField = "organizationId") {
211
- this.repo = createRepository(model, plugins);
212
- this.tenantField = tenantField;
213
- }
214
- async create(data, session) {
215
- return toDoc(await this.repo.create(data, { session }));
216
- }
217
- async createMany(data, session) {
218
- return toDocs(await this.repo.createMany(data, { session }));
219
- }
220
- async getById(id, tenantId) {
221
- if (tenantId) return toDoc(await this.repo.getByQuery({
222
- _id: id,
223
- [this.tenantField]: tenantId
224
- }, { throwOnNotFound: false }));
225
- return toDoc(await this.repo.getById(id, { throwOnNotFound: false }));
226
- }
227
- async getByCode(code, tenantId) {
228
- const query = { code: code.toUpperCase() };
229
- if (tenantId) query[this.tenantField] = tenantId;
230
- return toDoc(await this.repo.getByQuery(query, { throwOnNotFound: false }));
231
- }
232
- async update(id, data, tenantId, session) {
233
- const opts = {
234
- session,
235
- lean: true
236
- };
237
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
238
- return toDoc(await this.repo.update(id, data, opts));
239
- }
240
- async findMany(query, options) {
241
- return toDocs((await this.repo.getAll({
242
- filters: query,
243
- limit: options?.limit ?? 100,
244
- page: options?.page ?? 1
245
- })).docs);
246
- }
247
- async incrementUsage(id, redemption, tenantId, session) {
248
- const opts = {
249
- session,
250
- lean: true
251
- };
252
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
253
- return toDoc(await this.repo.update(id, {
254
- $inc: { usedCount: 1 },
255
- $push: { redemptions: redemption }
256
- }, opts));
257
- }
258
- async addLedgerEntry(id, entry, balanceDelta, tenantId, session) {
259
- const opts = {
260
- session,
261
- lean: true
262
- };
263
- if (tenantId) opts.query = { [this.tenantField]: tenantId };
264
- return toDoc(await this.repo.update(id, {
265
- $inc: { currentBalance: balanceDelta },
266
- $push: { balanceLedger: entry }
267
- }, opts));
268
- }
269
- async expireByDate(before, tenantId) {
270
- const filter = {
271
- status: "active",
272
- expiresAt: { $lte: before }
273
- };
274
- if (tenantId) filter[this.tenantField] = tenantId;
275
- return (await this.repo.Model.updateMany(filter, { $set: { status: "expired" } })).modifiedCount;
276
- }
277
- async hasIdempotencyKey(id, key) {
278
- return await this.repo.Model.countDocuments({
279
- _id: id,
280
- $or: [{ "redemptions.idempotencyKey": key }, { "balanceLedger.idempotencyKey": key }]
281
- }) > 0;
282
- }
283
- };
284
- //#endregion
285
- //#region src/repositories/index.ts
286
- function createRepositories(models, plugins, tenantField = "organizationId") {
287
- return {
288
- program: new ProgramRepository(models.Program, plugins?.program, tenantField),
289
- rule: new RuleRepository(models.Rule, plugins?.rule, tenantField),
290
- reward: new RewardRepository(models.Reward, plugins?.reward, tenantField),
291
- voucher: new VoucherRepository(models.Voucher, plugins?.voucher, tenantField)
292
- };
293
- }
294
- //#endregion
295
- export { ProgramRepository as a, RewardRepository as i, VoucherRepository as n, RuleRepository as r, createRepositories as t };