@classytic/loyalty 0.5.0 → 0.7.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/CHANGELOG.md +367 -231
- package/LICENSE +75 -21
- package/README.md +2 -2
- package/dist/{config-Dtun1duw.d.mts → config-CtkGj3xZ.d.mts} +41 -22
- package/dist/{constants-DkQYkOK1.mjs → constants-DOPBh6kt.mjs} +1 -3
- package/dist/domain/enums/index.d.mts +1 -1
- package/dist/domain/enums/index.mjs +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +2 -2
- package/dist/{events-DN5_p6T0.mjs → events-BLjg3uLA.mjs} +22 -31
- package/dist/{index-Dngaj21_.d.mts → index-BIPHUsfR.d.mts} +92 -14
- package/dist/index-BIhVuwIZ.d.mts +692 -0
- package/dist/{index-YSsi4e_G.d.mts → index-Cp_sXNpG.d.mts} +35 -4
- package/dist/{index-D_5Ojc2c.d.mts → index-YUFamhDk.d.mts} +7 -17
- package/dist/index.d.mts +60 -29
- package/dist/index.mjs +103 -52
- package/dist/models/index.d.mts +3 -1273
- package/dist/models/index.mjs +2 -2
- package/dist/{models-Ddh62lgO.mjs → models-weWqS5Bc.mjs} +152 -70
- package/dist/notifications/index.d.mts +1 -2
- package/dist/repositories/index.d.mts +1 -1
- package/dist/repositories/index.mjs +1 -1
- package/dist/{repositories-CjWfUlrA.mjs → repositories-DErtHQou.mjs} +198 -69
- package/dist/types/index.d.mts +3 -3
- package/package.json +119 -116
- /package/dist/{index-DMK8a98J.d.mts → index-D_Oe3aDk.d.mts} +0 -0
- /package/dist/{loyalty-events-C38JA3cU.mjs → loyalty-events-DXirolM_.mjs} +0 -0
- /package/dist/{notification.port-CJ9nXj0k.d.mts → notification.port-wY4IKimz.d.mts} +0 -0
package/dist/models/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { t as defineLoyaltyModels } from "../models-weWqS5Bc.mjs";
|
|
2
|
+
export { defineLoyaltyModels };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as REFERRAL_STATUSES, c as TRANSACTION_TYPES, i as REDEMPTION_STATUSES, o as RULE_STATUSES, r as MEMBER_STATUSES, s as RULE_TYPES } from "./constants-DOPBh6kt.mjs";
|
|
2
|
+
import { defineModels } from "@classytic/mongokit";
|
|
2
3
|
import mongoose from "mongoose";
|
|
4
|
+
import { mongooseMinorUnits } from "@classytic/validation/money";
|
|
3
5
|
//#region src/models/create-model.ts
|
|
4
6
|
/**
|
|
5
7
|
* Injects the tenant field into a schema and (when tenant scoping is enabled
|
|
@@ -31,7 +33,7 @@ function injectTenantField(schema, tenant) {
|
|
|
31
33
|
schema.add({ [tenant.tenantField]: {
|
|
32
34
|
type: tenant.fieldType === "objectId" ? mongoose.Schema.Types.ObjectId : String,
|
|
33
35
|
index: true,
|
|
34
|
-
...tenant.enabled ? { required: true } : {},
|
|
36
|
+
...tenant.enabled && tenant.required ? { required: true } : {},
|
|
35
37
|
...tenant.ref ? { ref: tenant.ref } : {}
|
|
36
38
|
} });
|
|
37
39
|
if (!tenant.enabled) return;
|
|
@@ -83,7 +85,11 @@ function createEarningRuleSchema() {
|
|
|
83
85
|
default: 0
|
|
84
86
|
},
|
|
85
87
|
conditions: {
|
|
86
|
-
minOrderAmount: {
|
|
88
|
+
minOrderAmount: {
|
|
89
|
+
type: Number,
|
|
90
|
+
min: 0,
|
|
91
|
+
validate: mongooseMinorUnits("conditions.minOrderAmount", { nullable: true })
|
|
92
|
+
},
|
|
87
93
|
categories: [{ type: String }],
|
|
88
94
|
tiers: [{ type: String }],
|
|
89
95
|
actions: [{ type: String }],
|
|
@@ -94,8 +100,26 @@ function createEarningRuleSchema() {
|
|
|
94
100
|
}
|
|
95
101
|
},
|
|
96
102
|
reward: {
|
|
97
|
-
pointsPerAmount: {
|
|
98
|
-
|
|
103
|
+
pointsPerAmount: {
|
|
104
|
+
type: Number,
|
|
105
|
+
min: 0
|
|
106
|
+
},
|
|
107
|
+
rateBasis: {
|
|
108
|
+
type: Number,
|
|
109
|
+
min: 1,
|
|
110
|
+
validate: {
|
|
111
|
+
validator: (v) => v === null || v === void 0 || Number.isInteger(v),
|
|
112
|
+
message: "reward.rateBasis must be an integer >= 1 (minor units) — got {VALUE}"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
amountPerPoint: {
|
|
116
|
+
type: Number,
|
|
117
|
+
min: 1,
|
|
118
|
+
validate: {
|
|
119
|
+
validator: (v) => v === null || v === void 0 || Number.isInteger(v),
|
|
120
|
+
message: "reward.amountPerPoint must be an integer >= 1 (minor units per point) — got {VALUE}"
|
|
121
|
+
}
|
|
122
|
+
},
|
|
99
123
|
fixedPoints: { type: Number },
|
|
100
124
|
multiplier: { type: Number },
|
|
101
125
|
roundingMode: {
|
|
@@ -113,6 +137,11 @@ function createEarningRuleSchema() {
|
|
|
113
137
|
endsAt: { type: Date },
|
|
114
138
|
metadata: { type: mongoose.Schema.Types.Mixed }
|
|
115
139
|
}, { timestamps: true });
|
|
140
|
+
schema.pre("validate", function() {
|
|
141
|
+
const pointsPerAmount = this.get("reward.pointsPerAmount");
|
|
142
|
+
const rateBasis = this.get("reward.rateBasis");
|
|
143
|
+
if (pointsPerAmount !== null && pointsPerAmount !== void 0 && (rateBasis === null || rateBasis === void 0)) this.invalidate("reward.rateBasis", "reward.rateBasis (integer minor units) is required when reward.pointsPerAmount is set");
|
|
144
|
+
});
|
|
116
145
|
schema.index({
|
|
117
146
|
status: 1,
|
|
118
147
|
type: 1,
|
|
@@ -282,11 +311,18 @@ function createRedemptionSchema() {
|
|
|
282
311
|
},
|
|
283
312
|
discountAmount: {
|
|
284
313
|
type: Number,
|
|
285
|
-
required: true
|
|
314
|
+
required: true,
|
|
315
|
+
min: 0,
|
|
316
|
+
validate: mongooseMinorUnits("discountAmount")
|
|
286
317
|
},
|
|
287
|
-
|
|
318
|
+
pointValueMinor: {
|
|
288
319
|
type: Number,
|
|
289
|
-
required: true
|
|
320
|
+
required: true,
|
|
321
|
+
min: 1,
|
|
322
|
+
validate: {
|
|
323
|
+
validator: Number.isInteger,
|
|
324
|
+
message: "pointValueMinor must be an integer >= 1 (minor units per point) — got {VALUE}"
|
|
325
|
+
}
|
|
290
326
|
},
|
|
291
327
|
status: {
|
|
292
328
|
type: String,
|
|
@@ -308,8 +344,16 @@ function createRedemptionSchema() {
|
|
|
308
344
|
reversalReason: { type: String },
|
|
309
345
|
reversalTransactionId: { type: mongoose.Schema.Types.ObjectId },
|
|
310
346
|
transactionId: { type: mongoose.Schema.Types.ObjectId },
|
|
311
|
-
initialBalance: {
|
|
312
|
-
|
|
347
|
+
initialBalance: {
|
|
348
|
+
type: Number,
|
|
349
|
+
min: 0,
|
|
350
|
+
validate: mongooseMinorUnits("initialBalance", { nullable: true })
|
|
351
|
+
},
|
|
352
|
+
remainingBalance: {
|
|
353
|
+
type: Number,
|
|
354
|
+
min: 0,
|
|
355
|
+
validate: mongooseMinorUnits("remainingBalance", { nullable: true })
|
|
356
|
+
},
|
|
313
357
|
approvals: {
|
|
314
358
|
type: mongoose.Schema.Types.Mixed,
|
|
315
359
|
default: null
|
|
@@ -394,7 +438,11 @@ function createTierDefinitionSchema() {
|
|
|
394
438
|
},
|
|
395
439
|
qualificationCriteria: {
|
|
396
440
|
minLifetimePoints: { type: Number },
|
|
397
|
-
minLifetimeSpend: {
|
|
441
|
+
minLifetimeSpend: {
|
|
442
|
+
type: Number,
|
|
443
|
+
min: 0,
|
|
444
|
+
validate: mongooseMinorUnits("qualificationCriteria.minLifetimeSpend", { nullable: true })
|
|
445
|
+
},
|
|
398
446
|
evaluationPeriodDays: { type: Number }
|
|
399
447
|
},
|
|
400
448
|
benefits: {
|
|
@@ -428,13 +476,6 @@ function createTierDefinitionSchema() {
|
|
|
428
476
|
}
|
|
429
477
|
//#endregion
|
|
430
478
|
//#region src/models/index.ts
|
|
431
|
-
function applyAutoIndex(models, autoIndex) {
|
|
432
|
-
if (autoIndex === void 0) return;
|
|
433
|
-
for (const [name, model] of Object.entries(models)) {
|
|
434
|
-
const value = typeof autoIndex === "boolean" ? autoIndex : autoIndex[name] ?? void 0;
|
|
435
|
-
if (value !== void 0) model.schema.set("autoIndex", value);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
479
|
/**
|
|
439
480
|
* Default physical collection names (see PACKAGE_RULES.md §20.1). Prefixed
|
|
440
481
|
* when `collectionPrefix` is provided; used verbatim when unset.
|
|
@@ -447,61 +488,102 @@ const DEFAULT_COLLECTIONS = {
|
|
|
447
488
|
Redemption: "loyalty_redemptions",
|
|
448
489
|
Referral: "loyalty_referrals"
|
|
449
490
|
};
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"
|
|
453
|
-
"
|
|
454
|
-
"
|
|
455
|
-
"
|
|
456
|
-
"
|
|
457
|
-
|
|
458
|
-
var LoyaltyModelCollisionError = class extends Error {
|
|
459
|
-
constructor(name) {
|
|
460
|
-
super(`[loyalty] Mongoose model "${name}" already exists on this connection. For hot-reload / test fixtures, pass \`forceRecreate: true\` to clobber the existing model. For two loyalty engines, use two Mongoose connections (\`mongoose.createConnection(...)\`) — each has its own model registry.`);
|
|
461
|
-
this.name = "LoyaltyModelCollisionError";
|
|
462
|
-
}
|
|
491
|
+
/** The registered Mongoose model names — stable, independent of `collectionPrefix`. */
|
|
492
|
+
const MODEL_NAMES = {
|
|
493
|
+
Member: "LoyaltyMember",
|
|
494
|
+
PointTransaction: "LoyaltyPointTransaction",
|
|
495
|
+
EarningRule: "LoyaltyEarningRule",
|
|
496
|
+
TierDefinition: "LoyaltyTierDefinition",
|
|
497
|
+
Redemption: "LoyaltyRedemption",
|
|
498
|
+
Referral: "LoyaltyReferral"
|
|
463
499
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
500
|
+
const ENTITIES = [
|
|
501
|
+
{
|
|
502
|
+
key: "Member",
|
|
503
|
+
modelName: MODEL_NAMES.Member,
|
|
504
|
+
collection: "Member",
|
|
505
|
+
indexKey: "member",
|
|
506
|
+
factory: createMemberSchema
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
key: "PointTransaction",
|
|
510
|
+
modelName: MODEL_NAMES.PointTransaction,
|
|
511
|
+
collection: "PointTransaction",
|
|
512
|
+
indexKey: "pointTransaction",
|
|
513
|
+
factory: createPointTransactionSchema
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
key: "EarningRule",
|
|
517
|
+
modelName: MODEL_NAMES.EarningRule,
|
|
518
|
+
collection: "EarningRule",
|
|
519
|
+
indexKey: "earningRule",
|
|
520
|
+
factory: createEarningRuleSchema
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
key: "TierDefinition",
|
|
524
|
+
modelName: MODEL_NAMES.TierDefinition,
|
|
525
|
+
collection: "TierDefinition",
|
|
526
|
+
indexKey: "tierDefinition",
|
|
527
|
+
factory: createTierDefinitionSchema
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
key: "Redemption",
|
|
531
|
+
modelName: MODEL_NAMES.Redemption,
|
|
532
|
+
collection: "Redemption",
|
|
533
|
+
indexKey: "redemption",
|
|
534
|
+
factory: createRedemptionSchema
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
key: "Referral",
|
|
538
|
+
modelName: MODEL_NAMES.Referral,
|
|
539
|
+
collection: "Referral",
|
|
540
|
+
indexKey: "referral",
|
|
541
|
+
factory: createReferralSchema
|
|
490
542
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
];
|
|
544
|
+
/**
|
|
545
|
+
* Describe the loyalty model set as a connection-free mongokit `ModelBlueprint`.
|
|
546
|
+
*
|
|
547
|
+
* Pure — builds no schema and registers nothing until `.bind(connection)`. This is the
|
|
548
|
+
* SINGLE authoritative place the loyalty models are described: tenant-field injection,
|
|
549
|
+
* user indexes, auto-index and collision policy all live here once (PACKAGE_RULES §20/§21).
|
|
550
|
+
* A name collision surfaces as mongokit's typed `ModelCollisionError`.
|
|
551
|
+
*/
|
|
552
|
+
function defineLoyaltyModels(config) {
|
|
553
|
+
const { tenant, indexes, autoIndex, collectionPrefix, forceRecreate } = config;
|
|
494
554
|
const prefix = collectionPrefix ?? "";
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
EarningRule: connection.model("LoyaltyEarningRule", earningRuleSchema, prefix + DEFAULT_COLLECTIONS.EarningRule),
|
|
499
|
-
TierDefinition: connection.model("LoyaltyTierDefinition", tierDefinitionSchema, prefix + DEFAULT_COLLECTIONS.TierDefinition),
|
|
500
|
-
Redemption: connection.model("LoyaltyRedemption", redemptionSchema, prefix + DEFAULT_COLLECTIONS.Redemption),
|
|
501
|
-
Referral: connection.model("LoyaltyReferral", referralSchema, prefix + DEFAULT_COLLECTIONS.Referral)
|
|
555
|
+
const resolveAutoIndex = (key) => {
|
|
556
|
+
if (autoIndex === void 0) return void 0;
|
|
557
|
+
return typeof autoIndex === "boolean" ? autoIndex : autoIndex[key];
|
|
502
558
|
};
|
|
503
|
-
|
|
504
|
-
|
|
559
|
+
const existing = forceRecreate ? {
|
|
560
|
+
mode: "replace",
|
|
561
|
+
environment: "test"
|
|
562
|
+
} : { mode: "throw" };
|
|
563
|
+
return defineModels({
|
|
564
|
+
models: ENTITIES.map((entity) => ({
|
|
565
|
+
name: entity.modelName,
|
|
566
|
+
collection: prefix + DEFAULT_COLLECTIONS[entity.collection],
|
|
567
|
+
existing,
|
|
568
|
+
schema: () => {
|
|
569
|
+
const schema = entity.factory();
|
|
570
|
+
injectTenantField(schema, tenant);
|
|
571
|
+
const userIndexes = indexes?.[entity.indexKey];
|
|
572
|
+
if (userIndexes) applyUserIndexes(schema, userIndexes, tenant);
|
|
573
|
+
const ai = resolveAutoIndex(entity.key);
|
|
574
|
+
if (ai !== void 0) schema.set("autoIndex", ai);
|
|
575
|
+
return schema;
|
|
576
|
+
}
|
|
577
|
+
})),
|
|
578
|
+
assemble: (models) => Object.freeze({
|
|
579
|
+
Member: models.get(MODEL_NAMES.Member),
|
|
580
|
+
PointTransaction: models.get(MODEL_NAMES.PointTransaction),
|
|
581
|
+
EarningRule: models.get(MODEL_NAMES.EarningRule),
|
|
582
|
+
TierDefinition: models.get(MODEL_NAMES.TierDefinition),
|
|
583
|
+
Redemption: models.get(MODEL_NAMES.Redemption),
|
|
584
|
+
Referral: models.get(MODEL_NAMES.Referral)
|
|
585
|
+
})
|
|
586
|
+
});
|
|
505
587
|
}
|
|
506
588
|
//#endregion
|
|
507
|
-
export {
|
|
589
|
+
export { defineLoyaltyModels as t };
|
|
@@ -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-
|
|
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-BIPHUsfR.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, l as EarningRuleRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, t as createRepositories } from "../repositories-
|
|
1
|
+
import { a as PointTransactionRepository, i as RedemptionRepository, l as EarningRuleRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, t as createRepositories } from "../repositories-DErtHQou.mjs";
|
|
2
2
|
export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
|