@classytic/promo 0.2.3 → 0.2.5
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 +53 -0
- package/dist/index.mjs +18 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,59 @@
|
|
|
3
3
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
4
|
adhering to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.2.5] — 2026-05-08
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **§P8 dispatch — `outbox.save` failures now propagate.** [src/events/dispatch.ts](src/events/dispatch.ts)
|
|
11
|
+
previously caught `outbox.save` errors and only logged them, swallowing
|
|
12
|
+
the failure. Per PACKAGE_RULES §P8 the outbox row is the durability
|
|
13
|
+
anchor for the transactional-outbox pattern: if the save fails, the
|
|
14
|
+
caller's transaction MUST roll back so the business doc and the event
|
|
15
|
+
row land atomically. Swallowing meant a parent doc could commit while
|
|
16
|
+
the event vanished, with no relay row to recover from. Fix re-throws
|
|
17
|
+
after logging, matching the textbook §P8 shape used in
|
|
18
|
+
`@classytic/loyalty` 0.2.3 and `@classytic/order`.
|
|
19
|
+
- `events.publish` failures continue to be swallowed (correctly, per §P8 —
|
|
20
|
+
the host's outbox relay re-publishes from the durable row), with logging
|
|
21
|
+
preserved for observability.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Peer-dep ranges tightened** — `@classytic/mongokit` `>=3.13.3`
|
|
26
|
+
(was `>=3.13.0`) and `@classytic/repo-core` `>=0.4.2` (was `>=0.4.0`).
|
|
27
|
+
3.13.3 preserves `errorLabels` on `MongoServerError` so concurrent
|
|
28
|
+
transactional writes (the `claim()`-based program / voucher state
|
|
29
|
+
transitions) retry correctly under contention; 0.4.2 adds the typed
|
|
30
|
+
`FindAllOptions` shape with optional `limit?` for callers that need
|
|
31
|
+
bounded non-paginated reads.
|
|
32
|
+
|
|
33
|
+
### Audited (no change required)
|
|
34
|
+
|
|
35
|
+
- Two `Model.*` raw-driver sites in
|
|
36
|
+
[src/repositories/pending-evaluation.repository.ts](src/repositories/pending-evaluation.repository.ts)
|
|
37
|
+
(`findOneAndDelete`, `deleteOne`) confirmed as the documented atomic
|
|
38
|
+
take-and-return semantic that mongokit's `delete()` doesn't expose.
|
|
39
|
+
Tenant scoping is re-implemented at the repository layer (mirrors
|
|
40
|
+
`multiTenantPlugin`'s injection) so the bypass keeps cross-tenant
|
|
41
|
+
isolation intact.
|
|
42
|
+
- `customIdPlugin` not used — §P9 dual-id resolution N/A.
|
|
43
|
+
|
|
44
|
+
## [0.2.4]
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`evaluation.preview` / `evaluation.evaluate`**: submitted codes are now
|
|
49
|
+
trimmed (in addition to uppercased) before resolution. `' bigboss10 '`
|
|
50
|
+
resolves to `'BIGBOSS10'` and is reported under that canonical form in
|
|
51
|
+
`appliedCodes`. Duplicates after normalization are de-duped (a single code
|
|
52
|
+
submitted multiple times appears once).
|
|
53
|
+
- **Empty-cart preview**: when `items` is empty (or `subtotal <= 0`), the
|
|
54
|
+
engine now rejects all submitted codes with `reason: 'Cart is empty'`
|
|
55
|
+
instead of listing them under `appliedCodes` with `totalDiscount: 0`. Hosts
|
|
56
|
+
that surface `appliedCodes` directly to the UI no longer need to special-case
|
|
57
|
+
zero-item carts.
|
|
58
|
+
|
|
6
59
|
## [0.2.0]
|
|
7
60
|
|
|
8
61
|
Structural rewrite on top of the 0.1.0 shape. The public API is now a
|
package/dist/index.mjs
CHANGED
|
@@ -501,6 +501,7 @@ async function dispatchPromoEvent(deps, event, ctx) {
|
|
|
501
501
|
await deps.outbox.save(event, saveOptions);
|
|
502
502
|
} catch (err) {
|
|
503
503
|
logger.error(`[promo] outbox.save failed for ${event.type}:`, err);
|
|
504
|
+
throw err;
|
|
504
505
|
}
|
|
505
506
|
if (deps.events) try {
|
|
506
507
|
await deps.events.publish(event);
|
|
@@ -1125,7 +1126,23 @@ var EvaluationService = class {
|
|
|
1125
1126
|
await dispatchPromoEvent(this.dispatchDeps, createEvent$1(PromoEvents.EVALUATION_ROLLED_BACK, { evaluationId }, ctx), ctx);
|
|
1126
1127
|
}
|
|
1127
1128
|
async doEvaluate(input, ctx, isPreview) {
|
|
1128
|
-
const submittedCodes = (input.codes ?? []).map((c) => c.toUpperCase());
|
|
1129
|
+
const submittedCodes = Array.from(new Set((input.codes ?? []).map((c) => c.trim().toUpperCase()).filter((c) => c.length > 0)));
|
|
1130
|
+
if (input.items.length === 0 || input.subtotal <= 0) return {
|
|
1131
|
+
evaluationId: randomBytes(16).toString("hex"),
|
|
1132
|
+
cartHash: computeCartHash(input),
|
|
1133
|
+
appliedDiscounts: [],
|
|
1134
|
+
freeProducts: [],
|
|
1135
|
+
totalDiscount: 0,
|
|
1136
|
+
subtotalAfterDiscount: Math.max(0, input.subtotal),
|
|
1137
|
+
appliedCodes: [],
|
|
1138
|
+
rejectedCodes: submittedCodes.map((code) => ({
|
|
1139
|
+
code,
|
|
1140
|
+
reason: "Cart is empty"
|
|
1141
|
+
})),
|
|
1142
|
+
warnings: [],
|
|
1143
|
+
isPreview,
|
|
1144
|
+
programsApplied: []
|
|
1145
|
+
};
|
|
1129
1146
|
const programs = await this.programRepo.findActive(void 0, ctx);
|
|
1130
1147
|
const programIds = programs.map((p) => p._id);
|
|
1131
1148
|
const [allRules, allRewards] = await Promise.all([this.ruleRepo.findAll({ programId: { $in: programIds } }, ctx), this.rewardRepo.findAll({ programId: { $in: programIds } }, ctx)]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/promo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Production-grade promotion, coupon, and discount engine for MongoDB — programs, rules, rewards, vouchers, gift cards, buy-x-get-y",
|
|
5
5
|
"author": "Classytic",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@classytic/promo",
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
"release": "npm run push -- main && npm run release:tag && npm publish"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@classytic/mongokit": ">=3.13.
|
|
60
|
+
"@classytic/mongokit": ">=3.13.3",
|
|
61
61
|
"@classytic/primitives": ">=0.5.0",
|
|
62
|
-
"@classytic/repo-core": ">=0.4.
|
|
62
|
+
"@classytic/repo-core": ">=0.4.2",
|
|
63
63
|
"mongoose": ">=9.4.1",
|
|
64
64
|
"zod": ">=4.0.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@biomejs/biome": "^2.4.9",
|
|
68
68
|
"@classytic/dev-tools": "^0.2.0",
|
|
69
|
-
"@classytic/mongokit": "
|
|
69
|
+
"@classytic/mongokit": "^3.13.3",
|
|
70
70
|
"@classytic/primitives": ">=0.5.0",
|
|
71
|
-
"@classytic/repo-core": "
|
|
71
|
+
"@classytic/repo-core": "^0.4.2",
|
|
72
72
|
"@types/node": "^25.5.0",
|
|
73
73
|
"@vitest/coverage-v8": "^3.2.4",
|
|
74
74
|
"knip": "^6.3.0",
|