@voyant-travel/commerce 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.
- package/LICENSE +201 -0
- package/README.md +145 -0
- package/dist/accepted-quote-version-reservation-golden-flow.test.d.ts +2 -0
- package/dist/accepted-quote-version-reservation-golden-flow.test.d.ts.map +1 -0
- package/dist/accepted-quote-version-reservation-golden-flow.test.js +398 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/interface.d.ts +18 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +246 -0
- package/dist/interface.test.d.ts +2 -0
- package/dist/interface.test.d.ts.map +1 -0
- package/dist/interface.test.js +357 -0
- package/dist/markets/index.d.ts +11 -0
- package/dist/markets/index.d.ts.map +1 -0
- package/dist/markets/index.js +12 -0
- package/dist/markets/routes.d.ts +1182 -0
- package/dist/markets/routes.d.ts.map +1 -0
- package/dist/markets/routes.js +209 -0
- package/dist/markets/schema.d.ts +1527 -0
- package/dist/markets/schema.d.ts.map +1 -0
- package/dist/markets/schema.js +240 -0
- package/dist/markets/service-core.d.ts +253 -0
- package/dist/markets/service-core.d.ts.map +1 -0
- package/dist/markets/service-core.js +242 -0
- package/dist/markets/service-rules.d.ts +191 -0
- package/dist/markets/service-rules.d.ts.map +1 -0
- package/dist/markets/service-rules.js +155 -0
- package/dist/markets/service-shared.d.ts +36 -0
- package/dist/markets/service-shared.d.ts.map +1 -0
- package/dist/markets/service-shared.js +7 -0
- package/dist/markets/service.d.ts +43 -0
- package/dist/markets/service.d.ts.map +1 -0
- package/dist/markets/service.js +42 -0
- package/dist/markets/validation.d.ts +451 -0
- package/dist/markets/validation.d.ts.map +1 -0
- package/dist/markets/validation.js +160 -0
- package/dist/pricing/events.d.ts +53 -0
- package/dist/pricing/events.d.ts.map +1 -0
- package/dist/pricing/events.js +28 -0
- package/dist/pricing/index.d.ts +15 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +18 -0
- package/dist/pricing/routes-core.d.ts +981 -0
- package/dist/pricing/routes-core.d.ts.map +1 -0
- package/dist/pricing/routes-core.js +102 -0
- package/dist/pricing/routes-public.d.ts +136 -0
- package/dist/pricing/routes-public.d.ts.map +1 -0
- package/dist/pricing/routes-public.js +14 -0
- package/dist/pricing/routes-rules.d.ts +1339 -0
- package/dist/pricing/routes-rules.d.ts.map +1 -0
- package/dist/pricing/routes-rules.js +138 -0
- package/dist/pricing/routes-shared.d.ts +14 -0
- package/dist/pricing/routes-shared.d.ts.map +1 -0
- package/dist/pricing/routes-shared.js +3 -0
- package/dist/pricing/routes.d.ts +7 -0
- package/dist/pricing/routes.d.ts.map +1 -0
- package/dist/pricing/routes.js +6 -0
- package/dist/pricing/schema-catalogs.d.ts +467 -0
- package/dist/pricing/schema-catalogs.d.ts.map +1 -0
- package/dist/pricing/schema-catalogs.js +47 -0
- package/dist/pricing/schema-categories.d.ts +497 -0
- package/dist/pricing/schema-categories.d.ts.map +1 -0
- package/dist/pricing/schema-categories.js +54 -0
- package/dist/pricing/schema-departure-overrides.d.ts +228 -0
- package/dist/pricing/schema-departure-overrides.d.ts.map +1 -0
- package/dist/pricing/schema-departure-overrides.js +36 -0
- package/dist/pricing/schema-option-rules.d.ts +1770 -0
- package/dist/pricing/schema-option-rules.d.ts.map +1 -0
- package/dist/pricing/schema-option-rules.js +181 -0
- package/dist/pricing/schema-policies.d.ts +395 -0
- package/dist/pricing/schema-policies.d.ts.map +1 -0
- package/dist/pricing/schema-policies.js +41 -0
- package/dist/pricing/schema-relations.d.ts +59 -0
- package/dist/pricing/schema-relations.d.ts.map +1 -0
- package/dist/pricing/schema-relations.js +111 -0
- package/dist/pricing/schema-shared.d.ts +11 -0
- package/dist/pricing/schema-shared.d.ts.map +1 -0
- package/dist/pricing/schema-shared.js +67 -0
- package/dist/pricing/schema.d.ts +8 -0
- package/dist/pricing/schema.d.ts.map +1 -0
- package/dist/pricing/schema.js +7 -0
- package/dist/pricing/service-catalog-plane-pricing.d.ts +95 -0
- package/dist/pricing/service-catalog-plane-pricing.d.ts.map +1 -0
- package/dist/pricing/service-catalog-plane-pricing.js +382 -0
- package/dist/pricing/service-catalogs.d.ts +139 -0
- package/dist/pricing/service-catalogs.d.ts.map +1 -0
- package/dist/pricing/service-catalogs.js +89 -0
- package/dist/pricing/service-categories.d.ts +147 -0
- package/dist/pricing/service-categories.d.ts.map +1 -0
- package/dist/pricing/service-categories.js +105 -0
- package/dist/pricing/service-departure-overrides.d.ts +67 -0
- package/dist/pricing/service-departure-overrides.d.ts.map +1 -0
- package/dist/pricing/service-departure-overrides.js +54 -0
- package/dist/pricing/service-option-rules.d.ts +321 -0
- package/dist/pricing/service-option-rules.d.ts.map +1 -0
- package/dist/pricing/service-option-rules.js +340 -0
- package/dist/pricing/service-policies.d.ts +123 -0
- package/dist/pricing/service-policies.d.ts.map +1 -0
- package/dist/pricing/service-policies.js +95 -0
- package/dist/pricing/service-public.d.ts +89 -0
- package/dist/pricing/service-public.d.ts.map +1 -0
- package/dist/pricing/service-public.js +473 -0
- package/dist/pricing/service-rule-resolver.d.ts +67 -0
- package/dist/pricing/service-rule-resolver.d.ts.map +1 -0
- package/dist/pricing/service-rule-resolver.js +204 -0
- package/dist/pricing/service-shared.d.ts +53 -0
- package/dist/pricing/service-shared.d.ts.map +1 -0
- package/dist/pricing/service-shared.js +4 -0
- package/dist/pricing/service-transfer-rules.d.ts +211 -0
- package/dist/pricing/service-transfer-rules.d.ts.map +1 -0
- package/dist/pricing/service-transfer-rules.js +139 -0
- package/dist/pricing/service.d.ts +79 -0
- package/dist/pricing/service.d.ts.map +1 -0
- package/dist/pricing/service.js +78 -0
- package/dist/pricing/validation-public.d.ts +412 -0
- package/dist/pricing/validation-public.d.ts.map +1 -0
- package/dist/pricing/validation-public.js +111 -0
- package/dist/pricing/validation-shared.d.ts +71 -0
- package/dist/pricing/validation-shared.d.ts.map +1 -0
- package/dist/pricing/validation-shared.js +63 -0
- package/dist/pricing/validation.d.ts +987 -0
- package/dist/pricing/validation.d.ts.map +1 -0
- package/dist/pricing/validation.js +307 -0
- package/dist/promotions/events.d.ts +38 -0
- package/dist/promotions/events.d.ts.map +1 -0
- package/dist/promotions/events.js +25 -0
- package/dist/promotions/index.d.ts +12 -0
- package/dist/promotions/index.d.ts.map +1 -0
- package/dist/promotions/index.js +17 -0
- package/dist/promotions/routes-shared.d.ts +14 -0
- package/dist/promotions/routes-shared.d.ts.map +1 -0
- package/dist/promotions/routes-shared.js +3 -0
- package/dist/promotions/routes.d.ts +395 -0
- package/dist/promotions/routes.d.ts.map +1 -0
- package/dist/promotions/routes.js +55 -0
- package/dist/promotions/schema.d.ts +675 -0
- package/dist/promotions/schema.d.ts.map +1 -0
- package/dist/promotions/schema.js +126 -0
- package/dist/promotions/service-booking-confirmed.d.ts +77 -0
- package/dist/promotions/service-booking-confirmed.d.ts.map +1 -0
- package/dist/promotions/service-booking-confirmed.js +134 -0
- package/dist/promotions/service-boundary-scheduler.d.ts +85 -0
- package/dist/promotions/service-boundary-scheduler.d.ts.map +1 -0
- package/dist/promotions/service-boundary-scheduler.js +141 -0
- package/dist/promotions/service-catalog-evaluator.d.ts +22 -0
- package/dist/promotions/service-catalog-evaluator.d.ts.map +1 -0
- package/dist/promotions/service-catalog-evaluator.js +33 -0
- package/dist/promotions/service-catalog-plane-promotions.d.ts +73 -0
- package/dist/promotions/service-catalog-plane-promotions.d.ts.map +1 -0
- package/dist/promotions/service-catalog-plane-promotions.js +118 -0
- package/dist/promotions/service-evaluator.d.ts +134 -0
- package/dist/promotions/service-evaluator.d.ts.map +1 -0
- package/dist/promotions/service-evaluator.js +302 -0
- package/dist/promotions/service-storefront.d.ts +147 -0
- package/dist/promotions/service-storefront.d.ts.map +1 -0
- package/dist/promotions/service-storefront.js +326 -0
- package/dist/promotions/service.d.ts +143 -0
- package/dist/promotions/service.d.ts.map +1 -0
- package/dist/promotions/service.js +359 -0
- package/dist/promotions/validation.d.ts +195 -0
- package/dist/promotions/validation.d.ts.map +1 -0
- package/dist/promotions/validation.js +167 -0
- package/dist/promotions/workflow-bulk-reindex.d.ts +36 -0
- package/dist/promotions/workflow-bulk-reindex.d.ts.map +1 -0
- package/dist/promotions/workflow-bulk-reindex.js +53 -0
- package/dist/promotions/workflow-runtime.d.ts +17 -0
- package/dist/promotions/workflow-runtime.d.ts.map +1 -0
- package/dist/promotions/workflow-runtime.js +9 -0
- package/dist/runtime.d.ts +18 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +27 -0
- package/dist/runtime.test.d.ts +2 -0
- package/dist/runtime.test.d.ts.map +1 -0
- package/dist/runtime.test.js +25 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +4 -0
- package/dist/sellability/index.d.ts +13 -0
- package/dist/sellability/index.d.ts.map +1 -0
- package/dist/sellability/index.js +17 -0
- package/dist/sellability/routes.d.ts +2332 -0
- package/dist/sellability/routes.d.ts.map +1 -0
- package/dist/sellability/routes.js +166 -0
- package/dist/sellability/schema.d.ts +1716 -0
- package/dist/sellability/schema.d.ts.map +1 -0
- package/dist/sellability/schema.js +278 -0
- package/dist/sellability/service-records.d.ts +316 -0
- package/dist/sellability/service-records.d.ts.map +1 -0
- package/dist/sellability/service-records.js +253 -0
- package/dist/sellability/service-resolve.d.ts +72 -0
- package/dist/sellability/service-resolve.d.ts.map +1 -0
- package/dist/sellability/service-resolve.js +580 -0
- package/dist/sellability/service-shared.d.ts +124 -0
- package/dist/sellability/service-shared.d.ts.map +1 -0
- package/dist/sellability/service-shared.js +96 -0
- package/dist/sellability/service-snapshots.d.ts +191 -0
- package/dist/sellability/service-snapshots.d.ts.map +1 -0
- package/dist/sellability/service-snapshots.js +153 -0
- package/dist/sellability/service.d.ts +1038 -0
- package/dist/sellability/service.d.ts.map +1 -0
- package/dist/sellability/service.js +17 -0
- package/dist/sellability/validation.d.ts +477 -0
- package/dist/sellability/validation.d.ts.map +1 -0
- package/dist/sellability/validation.js +192 -0
- package/dist/types.d.ts +239 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation schemas for the promotions module.
|
|
3
|
+
*
|
|
4
|
+
* Per docs/architecture/commerce-architecture.md §3.2 (scope), §4.1 (offer
|
|
5
|
+
* fields), §11 (currency rules), §12.1 (conditions schema).
|
|
6
|
+
*
|
|
7
|
+
* The scope discriminated union is the source of truth for what an offer
|
|
8
|
+
* applies to; the materialized `promotional_offer_products` link table (§4.2)
|
|
9
|
+
* is rebuilt from it on every create / update.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
// ---------- Scope discriminated union (§3.2) ----------
|
|
13
|
+
//
|
|
14
|
+
// Audience literal inlined to avoid a back-edge from @voyant-travel/commerce to
|
|
15
|
+
// @voyant-travel/catalog (where Visibility lives). A unit test pins the literal
|
|
16
|
+
// set against catalog's Visibility export.
|
|
17
|
+
const audienceLiteral = z.enum(["staff", "customer", "partner", "supplier"]);
|
|
18
|
+
export const promotionalOfferScopeSchema = z.discriminatedUnion("kind", [
|
|
19
|
+
z.object({ kind: z.literal("global") }),
|
|
20
|
+
z.object({
|
|
21
|
+
kind: z.literal("products"),
|
|
22
|
+
productIds: z.array(z.string().min(1)).min(1),
|
|
23
|
+
}),
|
|
24
|
+
z.object({
|
|
25
|
+
kind: z.literal("categories"),
|
|
26
|
+
categoryIds: z.array(z.string().min(1)).min(1),
|
|
27
|
+
}),
|
|
28
|
+
z.object({
|
|
29
|
+
kind: z.literal("destinations"),
|
|
30
|
+
destinationIds: z.array(z.string().min(1)).min(1),
|
|
31
|
+
}),
|
|
32
|
+
z.object({
|
|
33
|
+
kind: z.literal("markets"),
|
|
34
|
+
marketIds: z.array(z.string().min(1)).min(1),
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
kind: z.literal("audiences"),
|
|
38
|
+
audiences: z.array(audienceLiteral).min(1),
|
|
39
|
+
}),
|
|
40
|
+
z.object({
|
|
41
|
+
kind: z.literal("fare_codes"),
|
|
42
|
+
fareCodes: z.array(z.string().min(1)).min(1),
|
|
43
|
+
}),
|
|
44
|
+
z.object({
|
|
45
|
+
kind: z.literal("cabin_grades"),
|
|
46
|
+
cabinGradeCodes: z.array(z.string().min(1)).min(1),
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
// ---------- Conditions (§12.1) ----------
|
|
50
|
+
//
|
|
51
|
+
// Typed JSONB validated by Zod. Date validity stays on the offer header
|
|
52
|
+
// (`validFrom` / `validUntil`) — not duplicated in `conditions` for v1.
|
|
53
|
+
export const promotionalOfferConditionsSchema = z.object({
|
|
54
|
+
/** Minimum total travelers. Catalog-plane evaluation surfaces this as a
|
|
55
|
+
* conditional offer when pax is unknown; checkout treats below-minimum
|
|
56
|
+
* as a hard exclusion. */
|
|
57
|
+
minPax: z.number().int().positive().optional(),
|
|
58
|
+
/** Requires a known past guest / loyalty customer signal. */
|
|
59
|
+
pastGuestOnly: z.boolean().optional(),
|
|
60
|
+
/** Requires a single-traveler party. Falls back to `pax === 1` when pax is known. */
|
|
61
|
+
soloTravelerOnly: z.boolean().optional(),
|
|
62
|
+
/** Requires at least one child traveler in the party. */
|
|
63
|
+
childTravelerOnly: z.boolean().optional(),
|
|
64
|
+
/** Requires the booking party to qualify as a family. */
|
|
65
|
+
familyOnly: z.boolean().optional(),
|
|
66
|
+
});
|
|
67
|
+
// ---------- Discount type / value cross-field rule (§11) ----------
|
|
68
|
+
//
|
|
69
|
+
// `percentage` requires `discountPercent`; `fixed_amount` requires
|
|
70
|
+
// `discountAmountCents` + `currency`. The other-flavor fields must be
|
|
71
|
+
// null/undefined to prevent operator confusion.
|
|
72
|
+
const discountTypeEnum = z.enum(["percentage", "fixed_amount"]);
|
|
73
|
+
const baseOfferShape = {
|
|
74
|
+
name: z.string().min(1).max(200),
|
|
75
|
+
slug: z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.max(200)
|
|
79
|
+
.regex(/^[a-z0-9-]+$/, "slug must be lowercase alphanumeric with hyphens"),
|
|
80
|
+
description: z.string().nullable().optional(),
|
|
81
|
+
discountType: discountTypeEnum,
|
|
82
|
+
discountPercent: z.number().positive().max(100).nullable().optional(),
|
|
83
|
+
discountAmountCents: z.number().int().positive().nullable().optional(),
|
|
84
|
+
currency: z
|
|
85
|
+
.string()
|
|
86
|
+
.length(3)
|
|
87
|
+
.regex(/^[A-Z]{3}$/, "currency must be a 3-letter ISO 4217 code")
|
|
88
|
+
.nullable()
|
|
89
|
+
.optional(),
|
|
90
|
+
scope: promotionalOfferScopeSchema,
|
|
91
|
+
conditions: promotionalOfferConditionsSchema.optional().default({}),
|
|
92
|
+
validFrom: z.coerce.date().nullable().optional(),
|
|
93
|
+
validUntil: z.coerce.date().nullable().optional(),
|
|
94
|
+
/** Stored lowercase. Provided in any case at the API; lowercased before insert. */
|
|
95
|
+
code: z
|
|
96
|
+
.string()
|
|
97
|
+
.min(1)
|
|
98
|
+
.max(80)
|
|
99
|
+
.regex(/^[A-Za-z0-9_-]+$/, "code must be alphanumeric (with - or _)")
|
|
100
|
+
.nullable()
|
|
101
|
+
.optional(),
|
|
102
|
+
stackable: z.boolean().optional().default(false),
|
|
103
|
+
active: z.boolean().optional().default(true),
|
|
104
|
+
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
105
|
+
};
|
|
106
|
+
function applyDiscountTypeRules(schema) {
|
|
107
|
+
return schema
|
|
108
|
+
.refine((value) => {
|
|
109
|
+
if (value.discountType !== "percentage")
|
|
110
|
+
return true;
|
|
111
|
+
return (value.discountPercent != null &&
|
|
112
|
+
value.discountAmountCents == null &&
|
|
113
|
+
value.currency == null);
|
|
114
|
+
}, {
|
|
115
|
+
message: "percentage offers require `discountPercent` and must not set `discountAmountCents` or `currency`",
|
|
116
|
+
path: ["discountType"],
|
|
117
|
+
})
|
|
118
|
+
.refine((value) => {
|
|
119
|
+
if (value.discountType !== "fixed_amount")
|
|
120
|
+
return true;
|
|
121
|
+
return (value.discountAmountCents != null &&
|
|
122
|
+
value.currency != null &&
|
|
123
|
+
value.discountPercent == null);
|
|
124
|
+
}, {
|
|
125
|
+
message: "fixed_amount offers require `discountAmountCents` and `currency` and must not set `discountPercent`",
|
|
126
|
+
path: ["discountType"],
|
|
127
|
+
})
|
|
128
|
+
.refine((value) => {
|
|
129
|
+
if (value.validFrom == null || value.validUntil == null)
|
|
130
|
+
return true;
|
|
131
|
+
return value.validFrom < value.validUntil;
|
|
132
|
+
}, { message: "`validFrom` must be earlier than `validUntil`", path: ["validFrom"] });
|
|
133
|
+
}
|
|
134
|
+
export const insertPromotionalOfferSchema = applyDiscountTypeRules(z.object(baseOfferShape));
|
|
135
|
+
export const updatePromotionalOfferSchema = applyDiscountTypeRules(z.object({
|
|
136
|
+
...baseOfferShape,
|
|
137
|
+
discountType: discountTypeEnum.optional(),
|
|
138
|
+
scope: promotionalOfferScopeSchema.optional(),
|
|
139
|
+
name: baseOfferShape.name.optional(),
|
|
140
|
+
slug: baseOfferShape.slug.optional(),
|
|
141
|
+
}));
|
|
142
|
+
export const promotionalOfferListQuerySchema = z.object({
|
|
143
|
+
active: z
|
|
144
|
+
.union([z.literal("true"), z.literal("false")])
|
|
145
|
+
.transform((v) => v === "true")
|
|
146
|
+
.optional(),
|
|
147
|
+
code: z.string().min(1).max(80).optional(),
|
|
148
|
+
search: z.string().trim().min(1).max(200).optional(),
|
|
149
|
+
applicationMode: z.enum(["auto", "code"]).optional(),
|
|
150
|
+
status: z.enum(["active", "scheduled", "expired", "archived"]).optional(),
|
|
151
|
+
scopeKind: z
|
|
152
|
+
.enum([
|
|
153
|
+
"global",
|
|
154
|
+
"products",
|
|
155
|
+
"categories",
|
|
156
|
+
"destinations",
|
|
157
|
+
"markets",
|
|
158
|
+
"audiences",
|
|
159
|
+
"fare_codes",
|
|
160
|
+
"cabin_grades",
|
|
161
|
+
])
|
|
162
|
+
.optional(),
|
|
163
|
+
validFrom: z.string().date().optional(),
|
|
164
|
+
validUntil: z.string().date().optional(),
|
|
165
|
+
limit: z.coerce.number().int().positive().max(200).optional().default(50),
|
|
166
|
+
offset: z.coerce.number().int().nonnegative().optional().default(0),
|
|
167
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk-reindex workflow + event filter for `affected.kind === "all"`.
|
|
3
|
+
*
|
|
4
|
+
* The `promotion.changed` payload's `affected` is a discriminated union:
|
|
5
|
+
* - `{ kind: "products", productIds }` — small, bounded set; the catalog
|
|
6
|
+
* bridge reindexes inline on the in-process EventBus subscriber.
|
|
7
|
+
* - `{ kind: "all" }` — every owned product (global / market / audience-
|
|
8
|
+
* scoped offers). Inline enumeration would burn the request handler's
|
|
9
|
+
* CPU budget on a sizeable catalog, so this branch routes through a
|
|
10
|
+
* workflow that breaks the work into one step per product. The
|
|
11
|
+
* orchestrator schedules them in parallel so each individual step stays
|
|
12
|
+
* inside Worker CPU limits.
|
|
13
|
+
*
|
|
14
|
+
* The workflow body delegates catalog access to a service the operator
|
|
15
|
+
* template registers under `BULK_REINDEX_SERVICE_KEY`. The promotions
|
|
16
|
+
* package stays catalog-agnostic: it knows nothing about Typesense / index
|
|
17
|
+
* slices / document builders. The seam is the same one used elsewhere in
|
|
18
|
+
* the codebase for cross-module behavior (workflow → ctx.services.resolve).
|
|
19
|
+
*/
|
|
20
|
+
import { type PromotionChangedSource } from "./events.js";
|
|
21
|
+
export interface BulkReindexProductsInput {
|
|
22
|
+
/** The offer that triggered the reindex (for logging / correlation). */
|
|
23
|
+
offerId: string;
|
|
24
|
+
source: PromotionChangedSource;
|
|
25
|
+
}
|
|
26
|
+
export interface BulkReindexProductsOutput {
|
|
27
|
+
reindexed: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const bulkReindexProductsWorkflow: import("@voyant-travel/workflows").WorkflowDefinition<BulkReindexProductsInput, BulkReindexProductsOutput>;
|
|
30
|
+
/**
|
|
31
|
+
* Routes `promotion.changed` envelopes whose `affected.kind === "all"` into
|
|
32
|
+
* the workflow above. Other shapes (`{ kind: "products", productIds }`) fall
|
|
33
|
+
* through to the in-process catalog-bridge subscriber.
|
|
34
|
+
*/
|
|
35
|
+
export declare const promotionAffectedAllFilter: import("@voyant-travel/workflows/events").EventFilterRuntimeEntry;
|
|
36
|
+
//# sourceMappingURL=workflow-bulk-reindex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-bulk-reindex.d.ts","sourceRoot":"","sources":["../../src/promotions/workflow-bulk-reindex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAA2B,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGlF,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,sBAAsB,CAAA;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAA;CAClB;AAKD,eAAO,MAAM,2BAA2B,4GAuBtC,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,mEAYtC,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk-reindex workflow + event filter for `affected.kind === "all"`.
|
|
3
|
+
*
|
|
4
|
+
* The `promotion.changed` payload's `affected` is a discriminated union:
|
|
5
|
+
* - `{ kind: "products", productIds }` — small, bounded set; the catalog
|
|
6
|
+
* bridge reindexes inline on the in-process EventBus subscriber.
|
|
7
|
+
* - `{ kind: "all" }` — every owned product (global / market / audience-
|
|
8
|
+
* scoped offers). Inline enumeration would burn the request handler's
|
|
9
|
+
* CPU budget on a sizeable catalog, so this branch routes through a
|
|
10
|
+
* workflow that breaks the work into one step per product. The
|
|
11
|
+
* orchestrator schedules them in parallel so each individual step stays
|
|
12
|
+
* inside Worker CPU limits.
|
|
13
|
+
*
|
|
14
|
+
* The workflow body delegates catalog access to a service the operator
|
|
15
|
+
* template registers under `BULK_REINDEX_SERVICE_KEY`. The promotions
|
|
16
|
+
* package stays catalog-agnostic: it knows nothing about Typesense / index
|
|
17
|
+
* slices / document builders. The seam is the same one used elsewhere in
|
|
18
|
+
* the codebase for cross-module behavior (workflow → ctx.services.resolve).
|
|
19
|
+
*/
|
|
20
|
+
import { trigger, workflow } from "@voyant-travel/workflows";
|
|
21
|
+
import { PROMOTION_CHANGED_EVENT } from "./events.js";
|
|
22
|
+
import { BULK_REINDEX_SERVICE_KEY } from "./workflow-runtime.js";
|
|
23
|
+
/** Cap on concurrent per-product reindex steps to avoid hammering the index. */
|
|
24
|
+
const REINDEX_CONCURRENCY = 8;
|
|
25
|
+
export const bulkReindexProductsWorkflow = workflow({
|
|
26
|
+
id: "promotions.reindex-all-products",
|
|
27
|
+
defaultRuntime: "edge",
|
|
28
|
+
async run(_input, ctx) {
|
|
29
|
+
const svc = ctx.services.resolve(BULK_REINDEX_SERVICE_KEY);
|
|
30
|
+
const ids = await ctx.step("list-product-ids", async () => svc.listAllProductIds());
|
|
31
|
+
if (ids.length === 0)
|
|
32
|
+
return { reindexed: 0 };
|
|
33
|
+
await ctx.parallel(ids, async (productId) => ctx.step(`reindex:${productId}`, async () => {
|
|
34
|
+
await svc.reindexProduct(productId);
|
|
35
|
+
}), { concurrency: REINDEX_CONCURRENCY });
|
|
36
|
+
return { reindexed: ids.length };
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Routes `promotion.changed` envelopes whose `affected.kind === "all"` into
|
|
41
|
+
* the workflow above. Other shapes (`{ kind: "products", productIds }`) fall
|
|
42
|
+
* through to the in-process catalog-bridge subscriber.
|
|
43
|
+
*/
|
|
44
|
+
export const promotionAffectedAllFilter = trigger.on(PROMOTION_CHANGED_EVENT, {
|
|
45
|
+
target: bulkReindexProductsWorkflow,
|
|
46
|
+
where: { eq: [{ path: "data.affected.kind" }, { lit: "all" }] },
|
|
47
|
+
input: {
|
|
48
|
+
object: {
|
|
49
|
+
offerId: { path: "data.offerId" },
|
|
50
|
+
source: { path: "data.source" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime constants and contracts that workflow hosts may import without
|
|
3
|
+
* loading the promotions module, routes, schemas, or workflow declarations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Service-container key the operator starter registers a concrete
|
|
7
|
+
* implementation against.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BULK_REINDEX_SERVICE_KEY: "promotions:bulk-reindex-products";
|
|
10
|
+
/**
|
|
11
|
+
* Contract the operator starter implements for the bulk-reindex workflow.
|
|
12
|
+
*/
|
|
13
|
+
export interface BulkReindexProductsService {
|
|
14
|
+
listAllProductIds(): Promise<string[]>;
|
|
15
|
+
reindexProduct(productId: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=workflow-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-runtime.d.ts","sourceRoot":"","sources":["../../src/promotions/workflow-runtime.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAG,kCAA2C,CAAA;AAEnF;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACtC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime constants and contracts that workflow hosts may import without
|
|
3
|
+
* loading the promotions module, routes, schemas, or workflow declarations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Service-container key the operator starter registers a concrete
|
|
7
|
+
* implementation against.
|
|
8
|
+
*/
|
|
9
|
+
export const BULK_REINDEX_SERVICE_KEY = "promotions:bulk-reindex-products";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HonoModule } from "@voyant-travel/hono/module";
|
|
2
|
+
import { createPromotionsStorefrontResolvers } from "./promotions/service-storefront.js";
|
|
3
|
+
import { type SellabilityRoutesOptions } from "./sellability/index.js";
|
|
4
|
+
export declare const commerceRuntimeModuleNames: readonly ["pricing", "markets", "sellability", "promotions"];
|
|
5
|
+
export type CommerceRuntimeModuleName = (typeof commerceRuntimeModuleNames)[number];
|
|
6
|
+
export interface CommerceHonoModulesOptions {
|
|
7
|
+
sellability?: SellabilityRoutesOptions;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Runtime consolidation for the Commerce Module.
|
|
11
|
+
*
|
|
12
|
+
* This keeps existing API route prefixes stable while allowing templates to
|
|
13
|
+
* declare one Commerce manifest entry. The old packages remain schema owners
|
|
14
|
+
* until explicit schema-move issues migrate their tables.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createCommerceHonoModules(options?: CommerceHonoModulesOptions): HonoModule[];
|
|
17
|
+
export declare const createCommerceStorefrontOfferResolvers: typeof createPromotionsStorefrontResolvers;
|
|
18
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAI5D,OAAO,EAAE,mCAAmC,EAAE,MAAM,oCAAoC,CAAA;AACxF,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,wBAAwB,CAAA;AAE/B,eAAO,MAAM,0BAA0B,8DAK7B,CAAA;AAEV,MAAM,MAAM,yBAAyB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnF,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,wBAAwB,CAAA;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,UAAU,EAAE,CAOhG;AAED,eAAO,MAAM,sCAAsC,4CAAsC,CAAA"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { marketsHonoModule } from "./markets/index.js";
|
|
2
|
+
import { pricingHonoModule } from "./pricing/index.js";
|
|
3
|
+
import { promotionsHonoModule } from "./promotions/index.js";
|
|
4
|
+
import { createPromotionsStorefrontResolvers } from "./promotions/service-storefront.js";
|
|
5
|
+
import { createSellabilityHonoModule, sellabilityHonoModule, } from "./sellability/index.js";
|
|
6
|
+
export const commerceRuntimeModuleNames = [
|
|
7
|
+
"pricing",
|
|
8
|
+
"markets",
|
|
9
|
+
"sellability",
|
|
10
|
+
"promotions",
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Runtime consolidation for the Commerce Module.
|
|
14
|
+
*
|
|
15
|
+
* This keeps existing API route prefixes stable while allowing templates to
|
|
16
|
+
* declare one Commerce manifest entry. The old packages remain schema owners
|
|
17
|
+
* until explicit schema-move issues migrate their tables.
|
|
18
|
+
*/
|
|
19
|
+
export function createCommerceHonoModules(options = {}) {
|
|
20
|
+
return [
|
|
21
|
+
pricingHonoModule,
|
|
22
|
+
marketsHonoModule,
|
|
23
|
+
options.sellability ? createSellabilityHonoModule(options.sellability) : sellabilityHonoModule,
|
|
24
|
+
promotionsHonoModule,
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
export const createCommerceStorefrontOfferResolvers = createPromotionsStorefrontResolvers;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.test.d.ts","sourceRoot":"","sources":["../src/runtime.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { commerceRuntimeModuleNames, createCommerceHonoModules, createCommerceStorefrontOfferResolvers, } from "./index.js";
|
|
3
|
+
import { marketsHonoModule } from "./markets/index.js";
|
|
4
|
+
import { pricingHonoModule } from "./pricing/index.js";
|
|
5
|
+
import { promotionsHonoModule } from "./promotions/index.js";
|
|
6
|
+
import { sellabilityHonoModule } from "./sellability/index.js";
|
|
7
|
+
describe("commerce runtime", () => {
|
|
8
|
+
it("expands to the commercial runtime modules in stable order", () => {
|
|
9
|
+
const modules = createCommerceHonoModules();
|
|
10
|
+
expect(modules.map((mod) => mod.module.name)).toEqual([...commerceRuntimeModuleNames]);
|
|
11
|
+
expect(modules).toEqual([
|
|
12
|
+
pricingHonoModule,
|
|
13
|
+
marketsHonoModule,
|
|
14
|
+
sellabilityHonoModule,
|
|
15
|
+
promotionsHonoModule,
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
it("exports a Commerce-named storefront offer resolver factory", () => {
|
|
19
|
+
const resolvers = createCommerceStorefrontOfferResolvers();
|
|
20
|
+
expect(typeof resolvers.listApplicableOffers).toBe("function");
|
|
21
|
+
expect(typeof resolvers.getOfferBySlug).toBe("function");
|
|
22
|
+
expect(typeof resolvers.applyOffer).toBe("function");
|
|
23
|
+
expect(typeof resolvers.redeemOffer).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
});
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA;AACtC,cAAc,yBAAyB,CAAA"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Module } from "@voyant-travel/core";
|
|
2
|
+
import type { HonoModule } from "@voyant-travel/hono/module";
|
|
3
|
+
import { type SellabilityRoutesOptions } from "./routes.js";
|
|
4
|
+
export type { SellabilityRoutes, SellabilityRoutesOptions } from "./routes.js";
|
|
5
|
+
export declare const sellabilityModule: Module;
|
|
6
|
+
export declare const sellabilityHonoModule: HonoModule;
|
|
7
|
+
export declare function createSellabilityHonoModule(options?: SellabilityRoutesOptions): HonoModule;
|
|
8
|
+
export type { NewOfferExpirationEvent, NewOfferRefreshRun, NewSellabilityExplanation, NewSellabilityPolicy, NewSellabilityPolicyResult, NewSellabilitySnapshot, NewSellabilitySnapshotItem, OfferExpirationEvent, OfferRefreshRun, SellabilityExplanation, SellabilityPolicy, SellabilityPolicyResult, SellabilitySnapshot, SellabilitySnapshotItem, } from "./schema.js";
|
|
9
|
+
export { offerExpirationEventStatusEnum, offerExpirationEvents, offerRefreshRunStatusEnum, offerRefreshRuns, sellabilityExplanations, sellabilityExplanationTypeEnum, sellabilityPolicies, sellabilityPolicyResultStatusEnum, sellabilityPolicyResults, sellabilityPolicyScopeEnum, sellabilityPolicyTypeEnum, sellabilitySnapshotComponentKindEnum, sellabilitySnapshotItems, sellabilitySnapshotStatusEnum, sellabilitySnapshots, } from "./schema.js";
|
|
10
|
+
export type { SellabilityServiceOptions } from "./service.js";
|
|
11
|
+
export { createSellabilityService, sellabilityService } from "./service.js";
|
|
12
|
+
export { insertOfferExpirationEventSchema, insertOfferRefreshRunSchema, insertSellabilityExplanationSchema, insertSellabilityPolicyResultSchema, insertSellabilityPolicySchema, offerExpirationEventListQuerySchema, offerExpirationEventStatusSchema, offerRefreshRunListQuerySchema, offerRefreshRunStatusSchema, sellabilityExplanationListQuerySchema, sellabilityExplanationTypeSchema, sellabilityPersistSnapshotSchema, sellabilityPolicyListQuerySchema, sellabilityPolicyResultListQuerySchema, sellabilityPolicyResultStatusSchema, sellabilityPolicyScopeSchema, sellabilityPolicyTypeSchema, sellabilityResolveQuerySchema, sellabilitySnapshotItemListQuerySchema, sellabilitySnapshotListQuerySchema, sellabilitySnapshotStatusSchema, updateOfferExpirationEventSchema, updateOfferRefreshRunSchema, updateSellabilityExplanationSchema, updateSellabilityPolicyResultSchema, updateSellabilityPolicySchema, } from "./validation.js";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sellability/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAE5D,OAAO,EAEL,KAAK,wBAAwB,EAE9B,MAAM,aAAa,CAAA;AAEpB,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAE9E,eAAO,MAAM,iBAAiB,EAAE,MAE/B,CAAA;AAED,eAAO,MAAM,qBAAqB,EAAE,UAGnC,CAAA;AAED,wBAAgB,2BAA2B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,UAAU,CAK1F;AAED,YAAY,EACV,uBAAuB,EACvB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,EAC1B,oBAAoB,EACpB,eAAe,EACf,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,gBAAgB,EAChB,uBAAuB,EACvB,8BAA8B,EAC9B,mBAAmB,EACnB,iCAAiC,EACjC,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,EACzB,oCAAoC,EACpC,wBAAwB,EACxB,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,EAC7B,mCAAmC,EACnC,gCAAgC,EAChC,8BAA8B,EAC9B,2BAA2B,EAC3B,qCAAqC,EACrC,gCAAgC,EAChC,gCAAgC,EAChC,gCAAgC,EAChC,sCAAsC,EACtC,mCAAmC,EACnC,4BAA4B,EAC5B,2BAA2B,EAC3B,6BAA6B,EAC7B,sCAAsC,EACtC,kCAAkC,EAClC,+BAA+B,EAC/B,gCAAgC,EAChC,2BAA2B,EAC3B,kCAAkC,EAClC,mCAAmC,EACnC,6BAA6B,GAC9B,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createSellabilityRoutes, sellabilityRoutes, } from "./routes.js";
|
|
2
|
+
export const sellabilityModule = {
|
|
3
|
+
name: "sellability",
|
|
4
|
+
};
|
|
5
|
+
export const sellabilityHonoModule = {
|
|
6
|
+
module: sellabilityModule,
|
|
7
|
+
routes: sellabilityRoutes,
|
|
8
|
+
};
|
|
9
|
+
export function createSellabilityHonoModule(options) {
|
|
10
|
+
return {
|
|
11
|
+
module: sellabilityModule,
|
|
12
|
+
routes: createSellabilityRoutes(options),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export { offerExpirationEventStatusEnum, offerExpirationEvents, offerRefreshRunStatusEnum, offerRefreshRuns, sellabilityExplanations, sellabilityExplanationTypeEnum, sellabilityPolicies, sellabilityPolicyResultStatusEnum, sellabilityPolicyResults, sellabilityPolicyScopeEnum, sellabilityPolicyTypeEnum, sellabilitySnapshotComponentKindEnum, sellabilitySnapshotItems, sellabilitySnapshotStatusEnum, sellabilitySnapshots, } from "./schema.js";
|
|
16
|
+
export { createSellabilityService, sellabilityService } from "./service.js";
|
|
17
|
+
export { insertOfferExpirationEventSchema, insertOfferRefreshRunSchema, insertSellabilityExplanationSchema, insertSellabilityPolicyResultSchema, insertSellabilityPolicySchema, offerExpirationEventListQuerySchema, offerExpirationEventStatusSchema, offerRefreshRunListQuerySchema, offerRefreshRunStatusSchema, sellabilityExplanationListQuerySchema, sellabilityExplanationTypeSchema, sellabilityPersistSnapshotSchema, sellabilityPolicyListQuerySchema, sellabilityPolicyResultListQuerySchema, sellabilityPolicyResultStatusSchema, sellabilityPolicyScopeSchema, sellabilityPolicyTypeSchema, sellabilityResolveQuerySchema, sellabilitySnapshotItemListQuerySchema, sellabilitySnapshotListQuerySchema, sellabilitySnapshotStatusSchema, updateOfferExpirationEventSchema, updateOfferRefreshRunSchema, updateSellabilityExplanationSchema, updateSellabilityPolicyResultSchema, updateSellabilityPolicySchema, } from "./validation.js";
|