@classytic/loyalty 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 (43) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/config-DoZPGBfW.d.mts +136 -0
  3. package/dist/{constants-C75GvizO.mjs → constants-DkQYkOK1.mjs} +3 -2
  4. package/dist/domain/enums/index.d.mts +1 -1
  5. package/dist/domain/enums/index.mjs +1 -1
  6. package/dist/events/index.d.mts +2 -2
  7. package/dist/events/index.mjs +3 -3
  8. package/dist/events-BtZOdiPw.mjs +311 -0
  9. package/dist/index-BOWqBSWx.d.mts +281 -0
  10. package/dist/index-C4jFHS7m.d.mts +400 -0
  11. package/dist/index-ClPKmtxh.d.mts +361 -0
  12. package/dist/{index-CaH4GhSF.d.mts → index-DMK8a98J.d.mts} +1 -1
  13. package/dist/index.d.mts +28 -12
  14. package/dist/index.mjs +30 -26
  15. package/dist/loyalty-events-Cyt9TwX6.mjs +50 -0
  16. package/dist/models/index.d.mts +1225 -2
  17. package/dist/models/index.mjs +2 -2
  18. package/dist/{models-CrZPmVxt.mjs → models-uhh9c-7k.mjs} +104 -41
  19. package/dist/notifications/index.d.mts +1 -1
  20. package/dist/repositories/index.d.mts +2 -2
  21. package/dist/repositories/index.mjs +1 -1
  22. package/dist/repositories-DBSxe9st.mjs +1919 -0
  23. package/dist/types/index.d.mts +2 -2
  24. package/package.json +22 -17
  25. package/dist/config-aNigy4ec.d.mts +0 -90
  26. package/dist/domain/index.d.mts +0 -90
  27. package/dist/domain/index.mjs +0 -4
  28. package/dist/domain-errors-D7q-2LYD.mjs +0 -120
  29. package/dist/event-emitter.port-CLYPoMni.d.mts +0 -8
  30. package/dist/event-types-xQBtYBbX.mjs +0 -24
  31. package/dist/events-CprEWlN7.mjs +0 -25
  32. package/dist/index-B6ZstxSP.d.mts +0 -2357
  33. package/dist/index-BcoGeDco.d.mts +0 -127
  34. package/dist/index-DqzTjeEB.d.mts +0 -148
  35. package/dist/index-O7TL4R2P.d.mts +0 -86
  36. package/dist/index-_pg4J11t.d.mts +0 -191
  37. package/dist/repositories-qMX7Xh_y.mjs +0 -440
  38. package/dist/services/index.d.mts +0 -2
  39. package/dist/services/index.mjs +0 -2
  40. package/dist/services-CoTqC4E6.mjs +0 -1098
  41. package/dist/tier-definition-Djf72UPa.d.mts +0 -120
  42. package/dist/tier-definition.port-BzrHYBRo.d.mts +0 -143
  43. /package/dist/{notification.port-BzUsH0It.d.mts → notification.port-CJ9nXj0k.d.mts} +0 -0
@@ -1,2 +1,2 @@
1
- import { t as createModels } from "../models-CrZPmVxt.mjs";
2
- export { createModels };
1
+ import { n as createModels, t as LoyaltyModelCollisionError } from "../models-uhh9c-7k.mjs";
2
+ export { LoyaltyModelCollisionError, createModels };
@@ -1,21 +1,44 @@
1
- import { a as MEMBER_STATUSES, c as RULE_STATUSES, l as RULE_TYPES, o as REDEMPTION_STATUSES, s as REFERRAL_STATUSES, u as TRANSACTION_TYPES } from "./constants-C75GvizO.mjs";
1
+ import { a as MEMBER_STATUSES, c as RULE_STATUSES, l as RULE_TYPES, o as REDEMPTION_STATUSES, s as REFERRAL_STATUSES, u as TRANSACTION_TYPES } from "./constants-DkQYkOK1.mjs";
2
2
  import mongoose from "mongoose";
3
3
  //#region src/models/create-model.ts
4
4
  /**
5
- * Injects the tenant field into a schema and prepends it to ALL existing indexes.
5
+ * Injects the tenant field into a schema and (when tenant scoping is enabled
6
+ * at the plugin layer) prepends it to ALL existing indexes.
7
+ *
8
+ * The field itself is added UNCONDITIONALLY — this mirrors the
9
+ * `@classytic/order` convention ("Child repos set `organizationId`
10
+ * explicitly on the doc. So writes work whether the host opts into the
11
+ * auto-wired `multiTenantPlugin` or sets `multiTenant: false` and enforces
12
+ * scoping at its own framework layer"). Arc hosts that scope via preset /
13
+ * `BaseController` need the schema field present so writes persist and
14
+ * filters match; turning off the mongokit plugin wouldn't help them if
15
+ * `organizationId` weren't a known schema path (Mongoose strict mode would
16
+ * strip it).
17
+ *
18
+ * `required: true` is intentionally NOT set — mongokit's
19
+ * `buildCrudSchemasFromModel` would forward it into arc's auto-generated
20
+ * request body JSON Schema and Fastify would reject valid POSTs at
21
+ * preValidation BEFORE arc's `BaseController.create` can inject orgId from
22
+ * scope. Enforcement comes from (a) arc's BaseController writing the field
23
+ * on every create/update and (b) mongokit's `multiTenantPlugin` (when
24
+ * enabled) throwing on missing ctx.
25
+ *
26
+ * Compound-index prefixing IS gated on `enabled`, since that concerns
27
+ * query efficiency and is only relevant when the mongokit-layer plugin
28
+ * scopes queries.
6
29
  */
7
30
  function injectTenantField(schema, tenant) {
8
- if (!tenant.enabled) return;
9
- schema.add({ [tenant.field]: {
10
- type: tenant.type === "ObjectId" ? mongoose.Schema.Types.ObjectId : String,
11
- required: true,
31
+ schema.add({ [tenant.tenantField]: {
32
+ type: tenant.fieldType === "objectId" ? mongoose.Schema.Types.ObjectId : String,
12
33
  index: true,
34
+ ...tenant.enabled ? { required: true } : {},
13
35
  ...tenant.ref ? { ref: tenant.ref } : {}
14
36
  } });
37
+ if (!tenant.enabled) return;
15
38
  const existingIndexes = schema._indexes;
16
39
  if (existingIndexes && existingIndexes.length > 0) for (const indexEntry of existingIndexes) {
17
40
  const fields = indexEntry[0];
18
- const newFields = { [tenant.field]: 1 };
41
+ const newFields = { [tenant.tenantField]: 1 };
19
42
  for (const [key, val] of Object.entries(fields)) newFields[key] = val;
20
43
  indexEntry[0] = newFields;
21
44
  }
@@ -26,7 +49,7 @@ function injectTenantField(schema, tenant) {
26
49
  function applyUserIndexes(schema, indexes, tenant) {
27
50
  for (const def of indexes) {
28
51
  const fields = tenant.enabled ? {
29
- [tenant.field]: 1,
52
+ [tenant.tenantField]: 1,
30
53
  ...def.fields
31
54
  } : { ...def.fields };
32
55
  schema.index(fields, def.options ?? {});
@@ -118,22 +141,16 @@ function createMemberSchema() {
118
141
  enum: MEMBER_STATUSES,
119
142
  default: "active"
120
143
  },
121
- cardId: {
122
- type: String,
123
- unique: true,
124
- sparse: true
125
- },
126
- referralCode: {
127
- type: String,
128
- unique: true,
129
- sparse: true
130
- },
144
+ cardId: { type: String },
145
+ referralCode: { type: String },
131
146
  tier: {
132
147
  type: String,
133
148
  default: null
134
149
  },
135
150
  tierOverride: { type: String },
136
151
  tierOverrideReason: { type: String },
152
+ tierOverrideExpiresAt: { type: Date },
153
+ referredBy: { type: String },
137
154
  tierQualifiedAt: { type: Date },
138
155
  tierExpiresAt: { type: Date },
139
156
  balance: {
@@ -165,6 +182,14 @@ function createMemberSchema() {
165
182
  externalId: 1,
166
183
  externalType: 1
167
184
  }, { unique: true });
185
+ schema.index({ cardId: 1 }, {
186
+ unique: true,
187
+ partialFilterExpression: { cardId: { $type: "string" } }
188
+ });
189
+ schema.index({ referralCode: 1 }, {
190
+ unique: true,
191
+ partialFilterExpression: { referralCode: { $type: "string" } }
192
+ });
168
193
  return schema;
169
194
  }
170
195
  //#endregion
@@ -218,10 +243,7 @@ function createPointTransactionSchema() {
218
243
  });
219
244
  schema.index({ idempotencyKey: 1 }, {
220
245
  unique: true,
221
- partialFilterExpression: { idempotencyKey: {
222
- $exists: true,
223
- $ne: null
224
- } }
246
+ partialFilterExpression: { idempotencyKey: { $type: "string" } }
225
247
  });
226
248
  return schema;
227
249
  }
@@ -270,7 +292,12 @@ function createRedemptionSchema() {
270
292
  expiresAt: { type: Date },
271
293
  confirmedAt: { type: Date },
272
294
  releasedAt: { type: Date },
273
- transactionId: { type: mongoose.Schema.Types.ObjectId }
295
+ reversedAt: { type: Date },
296
+ reversalReason: { type: String },
297
+ reversalTransactionId: { type: mongoose.Schema.Types.ObjectId },
298
+ transactionId: { type: mongoose.Schema.Types.ObjectId },
299
+ initialBalance: { type: Number },
300
+ remainingBalance: { type: Number }
274
301
  }, { timestamps: true });
275
302
  schema.index({
276
303
  ownerType: 1,
@@ -374,21 +401,52 @@ function createTierDefinitionSchema() {
374
401
  }
375
402
  //#endregion
376
403
  //#region src/models/index.ts
377
- function createModels(connection, tenant, indexes) {
404
+ function applyAutoIndex(models, autoIndex) {
405
+ if (autoIndex === void 0) return;
406
+ for (const [name, model] of Object.entries(models)) {
407
+ const value = typeof autoIndex === "boolean" ? autoIndex : autoIndex[name] ?? void 0;
408
+ if (value !== void 0) model.schema.set("autoIndex", value);
409
+ }
410
+ }
411
+ /**
412
+ * Default physical collection names (see PACKAGE_RULES.md §20.1). Prefixed
413
+ * when `collectionPrefix` is provided; used verbatim when unset.
414
+ */
415
+ const DEFAULT_COLLECTIONS = {
416
+ Member: "loyalty_members",
417
+ PointTransaction: "loyalty_point_transactions",
418
+ EarningRule: "loyalty_earning_rules",
419
+ TierDefinition: "loyalty_tier_definitions",
420
+ Redemption: "loyalty_redemptions",
421
+ Referral: "loyalty_referrals"
422
+ };
423
+ const LOYALTY_MODEL_NAMES = [
424
+ "LoyaltyMember",
425
+ "LoyaltyPointTransaction",
426
+ "LoyaltyEarningRule",
427
+ "LoyaltyTierDefinition",
428
+ "LoyaltyRedemption",
429
+ "LoyaltyReferral"
430
+ ];
431
+ var LoyaltyModelCollisionError = class extends Error {
432
+ constructor(name) {
433
+ 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.`);
434
+ this.name = "LoyaltyModelCollisionError";
435
+ }
436
+ };
437
+ function createModels(connection, tenant, indexes, autoIndex, collectionPrefix, forceRecreate) {
378
438
  const memberSchema = createMemberSchema();
379
439
  const pointTransactionSchema = createPointTransactionSchema();
380
440
  const earningRuleSchema = createEarningRuleSchema();
381
441
  const tierDefinitionSchema = createTierDefinitionSchema();
382
442
  const redemptionSchema = createRedemptionSchema();
383
443
  const referralSchema = createReferralSchema();
384
- if (tenant.enabled) {
385
- injectTenantField(memberSchema, tenant);
386
- injectTenantField(pointTransactionSchema, tenant);
387
- injectTenantField(earningRuleSchema, tenant);
388
- injectTenantField(tierDefinitionSchema, tenant);
389
- injectTenantField(redemptionSchema, tenant);
390
- injectTenantField(referralSchema, tenant);
391
- }
444
+ injectTenantField(memberSchema, tenant);
445
+ injectTenantField(pointTransactionSchema, tenant);
446
+ injectTenantField(earningRuleSchema, tenant);
447
+ injectTenantField(tierDefinitionSchema, tenant);
448
+ injectTenantField(redemptionSchema, tenant);
449
+ injectTenantField(referralSchema, tenant);
392
450
  if (indexes) {
393
451
  const schemaMap = {
394
452
  member: memberSchema,
@@ -403,15 +461,20 @@ function createModels(connection, tenant, indexes) {
403
461
  if (schema && defs) applyUserIndexes(schema, defs, tenant);
404
462
  }
405
463
  }
406
- const model = (name, schema) => connection.models[name] || connection.model(name, schema);
407
- return {
408
- Member: model("LoyaltyMember", memberSchema),
409
- PointTransaction: model("LoyaltyPointTransaction", pointTransactionSchema),
410
- EarningRule: model("LoyaltyEarningRule", earningRuleSchema),
411
- TierDefinition: model("LoyaltyTierDefinition", tierDefinitionSchema),
412
- Redemption: model("LoyaltyRedemption", redemptionSchema),
413
- Referral: model("LoyaltyReferral", referralSchema)
464
+ if (forceRecreate) {
465
+ for (const name of LOYALTY_MODEL_NAMES) if (connection.models[name]) connection.deleteModel(name);
466
+ } else for (const name of LOYALTY_MODEL_NAMES) if (connection.models[name]) throw new LoyaltyModelCollisionError(name);
467
+ const prefix = collectionPrefix ?? "";
468
+ const models = {
469
+ Member: connection.model("LoyaltyMember", memberSchema, prefix + DEFAULT_COLLECTIONS.Member),
470
+ PointTransaction: connection.model("LoyaltyPointTransaction", pointTransactionSchema, prefix + DEFAULT_COLLECTIONS.PointTransaction),
471
+ EarningRule: connection.model("LoyaltyEarningRule", earningRuleSchema, prefix + DEFAULT_COLLECTIONS.EarningRule),
472
+ TierDefinition: connection.model("LoyaltyTierDefinition", tierDefinitionSchema, prefix + DEFAULT_COLLECTIONS.TierDefinition),
473
+ Redemption: connection.model("LoyaltyRedemption", redemptionSchema, prefix + DEFAULT_COLLECTIONS.Redemption),
474
+ Referral: connection.model("LoyaltyReferral", referralSchema, prefix + DEFAULT_COLLECTIONS.Referral)
414
475
  };
476
+ applyAutoIndex(models, autoIndex);
477
+ return models;
415
478
  }
416
479
  //#endregion
417
- export { createModels as t };
480
+ export { createModels as n, LoyaltyModelCollisionError as t };
@@ -1,4 +1,4 @@
1
- import { t as NotificationAdapter } from "../notification.port-BzUsH0It.mjs";
1
+ import { t as NotificationAdapter } from "../notification.port-CJ9nXj0k.mjs";
2
2
 
3
3
  //#region src/notifications/index.d.ts
4
4
  interface NotificationsAdapterConfig {
@@ -1,2 +1,2 @@
1
- import { a as RedemptionRepository, c as EarningRuleRepository, i as ReferralRepository, n as createRepositories, o as PointTransactionRepository, r as TierDefinitionRepository, s as MemberRepository, t as LoyaltyRepositories } from "../index-BcoGeDco.mjs";
2
- export { EarningRuleRepository, LoyaltyRepositories, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
1
+ import { a as ReferralRepository, d as MemberRepository, i as TierDefinitionRepository, l as EarningRuleRepository, n as LoyaltyRepositories, r as createRepositories, s as RedemptionRepository, t as CreateRepositoriesInput, u as PointTransactionRepository } from "../index-ClPKmtxh.mjs";
2
+ export { CreateRepositoriesInput, EarningRuleRepository, LoyaltyRepositories, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };
@@ -1,2 +1,2 @@
1
- import { a as PointTransactionRepository, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "../repositories-qMX7Xh_y.mjs";
1
+ import { a as PointTransactionRepository, i as RedemptionRepository, n as TierDefinitionRepository, o as MemberRepository, r as ReferralRepository, s as EarningRuleRepository, t as createRepositories } from "../repositories-DBSxe9st.mjs";
2
2
  export { EarningRuleRepository, MemberRepository, PointTransactionRepository, RedemptionRepository, ReferralRepository, TierDefinitionRepository, createRepositories };