@voyantjs/products 0.20.0 → 0.21.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/dist/booking-engine/handler.d.ts +203 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +330 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/catalog-policy.d.ts.map +1 -1
- package/dist/catalog-policy.js +15 -1
- package/dist/content-shape.d.ts +217 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +159 -0
- package/dist/draft-shape.d.ts +43 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +46 -0
- package/dist/events.d.ts +37 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/routes-content.d.ts +74 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +117 -0
- package/dist/routes.d.ts +40 -20
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +83 -13
- package/dist/schema-core.d.ts +240 -1
- package/dist/schema-core.d.ts.map +1 -1
- package/dist/schema-core.js +49 -0
- package/dist/schema-itinerary.d.ts +18 -1
- package/dist/schema-itinerary.d.ts.map +1 -1
- package/dist/schema-itinerary.js +1 -0
- package/dist/schema-settings.d.ts +1 -1
- package/dist/schema-sourced-content.d.ts +262 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +69 -0
- package/dist/schema-taxonomy.d.ts +17 -0
- package/dist/schema-taxonomy.d.ts.map +1 -1
- package/dist/schema-taxonomy.js +13 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service-catalog-plane.d.ts.map +1 -1
- package/dist/service-catalog-plane.js +1 -0
- package/dist/service-content-owned.d.ts +68 -0
- package/dist/service-content-owned.d.ts.map +1 -0
- package/dist/service-content-owned.js +224 -0
- package/dist/service-content-synthesizer.d.ts +90 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +171 -0
- package/dist/service-content.d.ts +106 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +365 -0
- package/dist/service.d.ts +76 -22
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -0
- package/dist/tasks/brochures.d.ts +1 -0
- package/dist/tasks/brochures.d.ts.map +1 -1
- package/dist/tasks/brochures.js +3 -0
- package/dist/validation-catalog.d.ts +4 -4
- package/dist/validation-config.d.ts +3 -3
- package/dist/validation-content.d.ts +34 -4
- package/dist/validation-content.d.ts.map +1 -1
- package/dist/validation-content.js +13 -0
- package/dist/validation-core.d.ts +53 -3
- package/dist/validation-core.d.ts.map +1 -1
- package/dist/validation-core.js +16 -0
- package/dist/validation-public.d.ts +9 -9
- package/dist/validation-shared.d.ts +4 -4
- package/package.json +12 -7
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` and stored in `products_sourced_content.payload`.
|
|
4
|
+
*
|
|
5
|
+
* Schema versions are managed by this module: the constant
|
|
6
|
+
* `PRODUCTS_CONTENT_SCHEMA_VERSION` stamps every cache write; reads
|
|
7
|
+
* skip rows with an unrecognized version (treated as cache miss). Bump
|
|
8
|
+
* the version when the shape changes; old cache rows are then evicted
|
|
9
|
+
* by a single `DELETE WHERE content_schema_version != current`.
|
|
10
|
+
*
|
|
11
|
+
* Pure types + Zod + a vertical-specific `mergeOverlaysIntoProductContent`
|
|
12
|
+
* that wraps the catalog plane's content-shape-aware merger with this
|
|
13
|
+
* vertical's validator.
|
|
14
|
+
*
|
|
15
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
|
|
16
|
+
*/
|
|
17
|
+
import { type ContentOverlay, type MergeOverlaysOptions } from "@voyantjs/catalog";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
/**
|
|
20
|
+
* The current content-schema version. Stamped on every cache write.
|
|
21
|
+
* Bump when the `productContentSchema` shape changes incompatibly.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PRODUCTS_CONTENT_SCHEMA_VERSION = "products/v1";
|
|
24
|
+
/**
|
|
25
|
+
* Top-level product summary fields. Maps loosely to the owned `products`
|
|
26
|
+
* table — the read service synthesizes from indexed projection + overlay
|
|
27
|
+
* for thin adapters, or stores adapter-served data for rich ones.
|
|
28
|
+
*/
|
|
29
|
+
export declare const productSummarySchema: z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
name: z.ZodString;
|
|
32
|
+
status: z.ZodOptional<z.ZodString>;
|
|
33
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
34
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
35
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
36
|
+
duration_days: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
37
|
+
start_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
end_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
sell_currency: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
supplier: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
departure_city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
43
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export declare const productMediaItemSchema: z.ZodObject<{
|
|
46
|
+
url: z.ZodString;
|
|
47
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
48
|
+
image: "image";
|
|
49
|
+
video: "video";
|
|
50
|
+
document: "document";
|
|
51
|
+
}>>;
|
|
52
|
+
caption: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
53
|
+
alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export declare const productOptionUnitSchema: z.ZodObject<{
|
|
56
|
+
id: z.ZodString;
|
|
57
|
+
type: z.ZodString;
|
|
58
|
+
label: z.ZodOptional<z.ZodString>;
|
|
59
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export declare const productOptionSchema: z.ZodObject<{
|
|
63
|
+
id: z.ZodString;
|
|
64
|
+
name: z.ZodString;
|
|
65
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
66
|
+
units: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
67
|
+
id: z.ZodString;
|
|
68
|
+
type: z.ZodString;
|
|
69
|
+
label: z.ZodOptional<z.ZodString>;
|
|
70
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
72
|
+
}, z.core.$strip>>>>;
|
|
73
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
export declare const productDaySchema: z.ZodObject<{
|
|
76
|
+
day_number: z.ZodNumber;
|
|
77
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
78
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
location: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
|
+
services: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export declare const productPolicySchema: z.ZodObject<{
|
|
83
|
+
kind: z.ZodEnum<{
|
|
84
|
+
supplier_notes: "supplier_notes";
|
|
85
|
+
cancellation: "cancellation";
|
|
86
|
+
payment: "payment";
|
|
87
|
+
requirements: "requirements";
|
|
88
|
+
}>;
|
|
89
|
+
body: z.ZodString;
|
|
90
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
/**
|
|
93
|
+
* A single bookable departure / time slot — the "when" surface of the
|
|
94
|
+
* product. ISO 8601 timestamps for `starts_at` / `ends_at` so locale
|
|
95
|
+
* formatting happens at render time, never in the cache.
|
|
96
|
+
*
|
|
97
|
+
* Owned products derive these from `availability_slots`; sourced
|
|
98
|
+
* adapters return them via `getContent`. Empty array = "always-on"
|
|
99
|
+
* product (e.g. an evergreen transfer service) or one whose schedule
|
|
100
|
+
* is on-request.
|
|
101
|
+
*/
|
|
102
|
+
export declare const productDepartureSchema: z.ZodObject<{
|
|
103
|
+
id: z.ZodString;
|
|
104
|
+
starts_at: z.ZodString;
|
|
105
|
+
ends_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
106
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
107
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
108
|
+
remaining: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
109
|
+
lowest_price_cents: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
110
|
+
currency: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
111
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
/**
|
|
114
|
+
* The product content payload. Cache writes validate against this
|
|
115
|
+
* schema; cache reads skip rows that don't validate (treated as cache
|
|
116
|
+
* miss to surface adapter integration bugs without corrupting reads).
|
|
117
|
+
*/
|
|
118
|
+
export declare const productContentSchema: z.ZodObject<{
|
|
119
|
+
product: z.ZodObject<{
|
|
120
|
+
id: z.ZodString;
|
|
121
|
+
name: z.ZodString;
|
|
122
|
+
status: z.ZodOptional<z.ZodString>;
|
|
123
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
124
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
125
|
+
hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
126
|
+
duration_days: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
127
|
+
start_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
128
|
+
end_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
129
|
+
sell_currency: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
130
|
+
supplier: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
131
|
+
country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
132
|
+
departure_city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
133
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
134
|
+
}, z.core.$strip>;
|
|
135
|
+
options: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
136
|
+
id: z.ZodString;
|
|
137
|
+
name: z.ZodString;
|
|
138
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
139
|
+
units: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
140
|
+
id: z.ZodString;
|
|
141
|
+
type: z.ZodString;
|
|
142
|
+
label: z.ZodOptional<z.ZodString>;
|
|
143
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
144
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
145
|
+
}, z.core.$strip>>>>;
|
|
146
|
+
inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
147
|
+
}, z.core.$strip>>>;
|
|
148
|
+
days: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
149
|
+
day_number: z.ZodNumber;
|
|
150
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
151
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
152
|
+
location: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
153
|
+
services: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
154
|
+
}, z.core.$strip>>>;
|
|
155
|
+
media: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
156
|
+
url: z.ZodString;
|
|
157
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
158
|
+
image: "image";
|
|
159
|
+
video: "video";
|
|
160
|
+
document: "document";
|
|
161
|
+
}>>;
|
|
162
|
+
caption: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
163
|
+
alt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
164
|
+
}, z.core.$strip>>>;
|
|
165
|
+
policies: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
166
|
+
kind: z.ZodEnum<{
|
|
167
|
+
supplier_notes: "supplier_notes";
|
|
168
|
+
cancellation: "cancellation";
|
|
169
|
+
payment: "payment";
|
|
170
|
+
requirements: "requirements";
|
|
171
|
+
}>;
|
|
172
|
+
body: z.ZodString;
|
|
173
|
+
rules: z.ZodOptional<z.ZodUnknown>;
|
|
174
|
+
}, z.core.$strip>>>;
|
|
175
|
+
departures: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
176
|
+
id: z.ZodString;
|
|
177
|
+
starts_at: z.ZodString;
|
|
178
|
+
ends_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
179
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
180
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
181
|
+
remaining: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
182
|
+
lowest_price_cents: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
183
|
+
currency: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
184
|
+
note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
185
|
+
}, z.core.$strip>>>;
|
|
186
|
+
}, z.core.$strip>;
|
|
187
|
+
export type ProductContent = z.infer<typeof productContentSchema>;
|
|
188
|
+
export type ProductSummary = z.infer<typeof productSummarySchema>;
|
|
189
|
+
export type ProductMediaItem = z.infer<typeof productMediaItemSchema>;
|
|
190
|
+
export type ProductOption = z.infer<typeof productOptionSchema>;
|
|
191
|
+
export type ProductDeparture = z.infer<typeof productDepartureSchema>;
|
|
192
|
+
export type ProductDay = z.infer<typeof productDaySchema>;
|
|
193
|
+
export type ProductPolicy = z.infer<typeof productPolicySchema>;
|
|
194
|
+
/**
|
|
195
|
+
* Validate a `ProductContent` payload. Returns the parsed result on
|
|
196
|
+
* success or a structured failure on rejection. Used by the cache write
|
|
197
|
+
* path and by `mergeOverlaysIntoProductContent` to gate overlay merges.
|
|
198
|
+
*/
|
|
199
|
+
export declare function validateProductContent(payload: unknown): {
|
|
200
|
+
valid: true;
|
|
201
|
+
content: ProductContent;
|
|
202
|
+
} | {
|
|
203
|
+
valid: false;
|
|
204
|
+
reason: string;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Apply a list of editorial overlays to a product content payload via
|
|
208
|
+
* RFC 6901 JSON pointers. Validates the merged result against the
|
|
209
|
+
* vertical's Zod schema; overlays that produce an invalid payload are
|
|
210
|
+
* rolled back and reported via `onOverlayError`.
|
|
211
|
+
*
|
|
212
|
+
* Per sourced-content §3.5.4, this is the "content-shape-aware merger"
|
|
213
|
+
* — the catalog plane stays neutral about the content shape; the
|
|
214
|
+
* vertical plugs in its validator here.
|
|
215
|
+
*/
|
|
216
|
+
export declare function mergeOverlaysIntoProductContent(payload: ProductContent, overlays: ReadonlyArray<ContentOverlay>, options?: Pick<MergeOverlaysOptions, "onOverlayError">): ProductContent;
|
|
217
|
+
//# sourceMappingURL=content-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-shape.d.ts","sourceRoot":"","sources":["../src/content-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;GAGG;AACH,eAAO,MAAM,+BAA+B,gBAAgB,CAAA;AAE5D;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;iBAe/B,CAAA;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;iBAKjC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;iBAMlC,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAM9B,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;iBAM3B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;iBAK9B,CAAA;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;iBAejC,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO/B,CAAA;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACjE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AACjE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAC/D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AACrE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AACzD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAE/D;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAY7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,EACvC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,CAAM,GACzD,cAAc,CAchB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products content shape — the rich detail-page content shape returned
|
|
3
|
+
* by `getContent` and stored in `products_sourced_content.payload`.
|
|
4
|
+
*
|
|
5
|
+
* Schema versions are managed by this module: the constant
|
|
6
|
+
* `PRODUCTS_CONTENT_SCHEMA_VERSION` stamps every cache write; reads
|
|
7
|
+
* skip rows with an unrecognized version (treated as cache miss). Bump
|
|
8
|
+
* the version when the shape changes; old cache rows are then evicted
|
|
9
|
+
* by a single `DELETE WHERE content_schema_version != current`.
|
|
10
|
+
*
|
|
11
|
+
* Pure types + Zod + a vertical-specific `mergeOverlaysIntoProductContent`
|
|
12
|
+
* that wraps the catalog plane's content-shape-aware merger with this
|
|
13
|
+
* vertical's validator.
|
|
14
|
+
*
|
|
15
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
|
|
16
|
+
*/
|
|
17
|
+
import { mergeOverlaysIntoContent, } from "@voyantjs/catalog";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
/**
|
|
20
|
+
* The current content-schema version. Stamped on every cache write.
|
|
21
|
+
* Bump when the `productContentSchema` shape changes incompatibly.
|
|
22
|
+
*/
|
|
23
|
+
export const PRODUCTS_CONTENT_SCHEMA_VERSION = "products/v1";
|
|
24
|
+
/**
|
|
25
|
+
* Top-level product summary fields. Maps loosely to the owned `products`
|
|
26
|
+
* table — the read service synthesizes from indexed projection + overlay
|
|
27
|
+
* for thin adapters, or stores adapter-served data for rich ones.
|
|
28
|
+
*/
|
|
29
|
+
export const productSummarySchema = z.object({
|
|
30
|
+
id: z.string(),
|
|
31
|
+
name: z.string(),
|
|
32
|
+
status: z.string().optional(),
|
|
33
|
+
description: z.string().nullable().optional(),
|
|
34
|
+
highlights: z.array(z.string()).optional(),
|
|
35
|
+
hero_image_url: z.string().nullable().optional(),
|
|
36
|
+
duration_days: z.number().int().nonnegative().nullable().optional(),
|
|
37
|
+
start_date: z.string().nullable().optional(),
|
|
38
|
+
end_date: z.string().nullable().optional(),
|
|
39
|
+
sell_currency: z.string().nullable().optional(),
|
|
40
|
+
supplier: z.string().nullable().optional(),
|
|
41
|
+
country: z.string().nullable().optional(),
|
|
42
|
+
departure_city: z.string().nullable().optional(),
|
|
43
|
+
tags: z.array(z.string()).optional(),
|
|
44
|
+
});
|
|
45
|
+
export const productMediaItemSchema = z.object({
|
|
46
|
+
url: z.string(),
|
|
47
|
+
type: z.enum(["image", "video", "document"]).default("image"),
|
|
48
|
+
caption: z.string().nullable().optional(),
|
|
49
|
+
alt: z.string().nullable().optional(),
|
|
50
|
+
});
|
|
51
|
+
export const productOptionUnitSchema = z.object({
|
|
52
|
+
id: z.string(),
|
|
53
|
+
type: z.string(),
|
|
54
|
+
label: z.string().optional(),
|
|
55
|
+
description: z.string().nullable().optional(),
|
|
56
|
+
capacity: z.number().int().nonnegative().nullable().optional(),
|
|
57
|
+
});
|
|
58
|
+
export const productOptionSchema = z.object({
|
|
59
|
+
id: z.string(),
|
|
60
|
+
name: z.string(),
|
|
61
|
+
description: z.string().nullable().optional(),
|
|
62
|
+
units: z.array(productOptionUnitSchema).optional().default([]),
|
|
63
|
+
inclusions: z.array(z.string()).optional().default([]),
|
|
64
|
+
});
|
|
65
|
+
export const productDaySchema = z.object({
|
|
66
|
+
day_number: z.number().int().positive(),
|
|
67
|
+
title: z.string().nullable().optional(),
|
|
68
|
+
description: z.string().nullable().optional(),
|
|
69
|
+
location: z.string().nullable().optional(),
|
|
70
|
+
services: z.array(z.string()).optional().default([]),
|
|
71
|
+
});
|
|
72
|
+
export const productPolicySchema = z.object({
|
|
73
|
+
kind: z.enum(["cancellation", "payment", "supplier_notes", "requirements"]),
|
|
74
|
+
body: z.string(),
|
|
75
|
+
/** Optional structured rules — vertical-specific. */
|
|
76
|
+
rules: z.unknown().optional(),
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* A single bookable departure / time slot — the "when" surface of the
|
|
80
|
+
* product. ISO 8601 timestamps for `starts_at` / `ends_at` so locale
|
|
81
|
+
* formatting happens at render time, never in the cache.
|
|
82
|
+
*
|
|
83
|
+
* Owned products derive these from `availability_slots`; sourced
|
|
84
|
+
* adapters return them via `getContent`. Empty array = "always-on"
|
|
85
|
+
* product (e.g. an evergreen transfer service) or one whose schedule
|
|
86
|
+
* is on-request.
|
|
87
|
+
*/
|
|
88
|
+
export const productDepartureSchema = z.object({
|
|
89
|
+
id: z.string(),
|
|
90
|
+
starts_at: z.string(),
|
|
91
|
+
ends_at: z.string().nullable().optional(),
|
|
92
|
+
/** "open" | "limited" | "sold_out" | "closed" | "on_request" — display only. */
|
|
93
|
+
status: z.string().nullable().optional(),
|
|
94
|
+
/** Total capacity for the slot, when known. */
|
|
95
|
+
capacity: z.number().int().nonnegative().nullable().optional(),
|
|
96
|
+
/** Remaining capacity. Null = unknown / not surfaced; 0 = sold out. */
|
|
97
|
+
remaining: z.number().int().nonnegative().nullable().optional(),
|
|
98
|
+
/** Lowest pricing hint in cents — display only. Real price comes via liveResolve. */
|
|
99
|
+
lowest_price_cents: z.number().int().nonnegative().nullable().optional(),
|
|
100
|
+
currency: z.string().nullable().optional(),
|
|
101
|
+
/** Free-form note (weather caveat, sales window, etc). */
|
|
102
|
+
note: z.string().nullable().optional(),
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* The product content payload. Cache writes validate against this
|
|
106
|
+
* schema; cache reads skip rows that don't validate (treated as cache
|
|
107
|
+
* miss to surface adapter integration bugs without corrupting reads).
|
|
108
|
+
*/
|
|
109
|
+
export const productContentSchema = z.object({
|
|
110
|
+
product: productSummarySchema,
|
|
111
|
+
options: z.array(productOptionSchema).default([]),
|
|
112
|
+
days: z.array(productDaySchema).default([]),
|
|
113
|
+
media: z.array(productMediaItemSchema).default([]),
|
|
114
|
+
policies: z.array(productPolicySchema).default([]),
|
|
115
|
+
departures: z.array(productDepartureSchema).default([]),
|
|
116
|
+
});
|
|
117
|
+
/**
|
|
118
|
+
* Validate a `ProductContent` payload. Returns the parsed result on
|
|
119
|
+
* success or a structured failure on rejection. Used by the cache write
|
|
120
|
+
* path and by `mergeOverlaysIntoProductContent` to gate overlay merges.
|
|
121
|
+
*/
|
|
122
|
+
export function validateProductContent(payload) {
|
|
123
|
+
const result = productContentSchema.safeParse(payload);
|
|
124
|
+
if (result.success) {
|
|
125
|
+
return { valid: true, content: result.data };
|
|
126
|
+
}
|
|
127
|
+
// Take the first issue's message — that's enough signal for ops; full
|
|
128
|
+
// detail is available on `result.error.issues` if a caller cares.
|
|
129
|
+
const issue = result.error.issues[0];
|
|
130
|
+
return {
|
|
131
|
+
valid: false,
|
|
132
|
+
reason: issue ? `${issue.path.join(".")}: ${issue.message}` : "validation failed",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Apply a list of editorial overlays to a product content payload via
|
|
137
|
+
* RFC 6901 JSON pointers. Validates the merged result against the
|
|
138
|
+
* vertical's Zod schema; overlays that produce an invalid payload are
|
|
139
|
+
* rolled back and reported via `onOverlayError`.
|
|
140
|
+
*
|
|
141
|
+
* Per sourced-content §3.5.4, this is the "content-shape-aware merger"
|
|
142
|
+
* — the catalog plane stays neutral about the content shape; the
|
|
143
|
+
* vertical plugs in its validator here.
|
|
144
|
+
*/
|
|
145
|
+
export function mergeOverlaysIntoProductContent(payload, overlays, options = {}) {
|
|
146
|
+
const merged = mergeOverlaysIntoContent(payload, overlays, {
|
|
147
|
+
validate(p) {
|
|
148
|
+
const result = validateProductContent(p);
|
|
149
|
+
if (result.valid) {
|
|
150
|
+
return { valid: true };
|
|
151
|
+
}
|
|
152
|
+
return { valid: false, reason: result.reason };
|
|
153
|
+
},
|
|
154
|
+
onOverlayError: options.onOverlayError,
|
|
155
|
+
});
|
|
156
|
+
// The validator gates merges, so a successful merge always parses —
|
|
157
|
+
// re-parse here to satisfy the return type without an unsafe cast.
|
|
158
|
+
return productContentSchema.parse(merged);
|
|
159
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project a `ProductContent` payload into a `BookingDraftShape` so
|
|
3
|
+
* the journey wizard can render the correct sub-steps for a sourced
|
|
4
|
+
* product.
|
|
5
|
+
*
|
|
6
|
+
* Tour-style products typically need:
|
|
7
|
+
* - Configure: occupancy band selection (adult / child / infant)
|
|
8
|
+
* and optionally a date / departure picker if `days[]` carries
|
|
9
|
+
* scheduled departures.
|
|
10
|
+
* - Travelers: per-pax fields (first / last / email; passport when
|
|
11
|
+
* the supplier requires it — surfaced via overlay when known).
|
|
12
|
+
* - Add-ons: the product's own `options[]` projected as add-on
|
|
13
|
+
* offers.
|
|
14
|
+
*
|
|
15
|
+
* No accommodation sub-step today (multi-day tours w/ rooms route
|
|
16
|
+
* through hospitality, not products). Pricing flows through
|
|
17
|
+
* liveResolve at quote time, not the descriptor.
|
|
18
|
+
*
|
|
19
|
+
* See `docs/architecture/booking-journey-architecture.md` §3 + §F.
|
|
20
|
+
*/
|
|
21
|
+
import { type BookingDraftShape, type PaxBandSpec } from "@voyantjs/catalog/booking-engine";
|
|
22
|
+
import type { ProductContent } from "./content-shape.js";
|
|
23
|
+
export interface BuildProductDraftShapeOptions {
|
|
24
|
+
/** Locale — used for option-label fallback. Defaults to `"en-GB"`. */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Override the default pax bands. Use when the supplier mandates
|
|
28
|
+
* specific age cutoffs (rare for tour products; common for cruises
|
|
29
|
+
* and family-oriented packages).
|
|
30
|
+
*/
|
|
31
|
+
paxBands?: ReadonlyArray<PaxBandSpec>;
|
|
32
|
+
/**
|
|
33
|
+
* Override the maximum total pax. Defaults to `paxBands` sum.
|
|
34
|
+
* Useful when supplier capacity is < combined band max (e.g. a
|
|
35
|
+
* 6-pax max party on a private tour).
|
|
36
|
+
*/
|
|
37
|
+
paxBandsAllowedTotal?: {
|
|
38
|
+
min: number;
|
|
39
|
+
max: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function buildProductDraftShape(content: ProductContent, options?: BuildProductDraftShapeOptions): BookingDraftShape;
|
|
43
|
+
//# sourceMappingURL=draft-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft-shape.d.ts","sourceRoot":"","sources":["../src/draft-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EAKtB,KAAK,WAAW,EAEjB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,6BAA6B;IAC5C,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IACrC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACpD;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,6BAAkC,GAC1C,iBAAiB,CA0BnB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project a `ProductContent` payload into a `BookingDraftShape` so
|
|
3
|
+
* the journey wizard can render the correct sub-steps for a sourced
|
|
4
|
+
* product.
|
|
5
|
+
*
|
|
6
|
+
* Tour-style products typically need:
|
|
7
|
+
* - Configure: occupancy band selection (adult / child / infant)
|
|
8
|
+
* and optionally a date / departure picker if `days[]` carries
|
|
9
|
+
* scheduled departures.
|
|
10
|
+
* - Travelers: per-pax fields (first / last / email; passport when
|
|
11
|
+
* the supplier requires it — surfaced via overlay when known).
|
|
12
|
+
* - Add-ons: the product's own `options[]` projected as add-on
|
|
13
|
+
* offers.
|
|
14
|
+
*
|
|
15
|
+
* No accommodation sub-step today (multi-day tours w/ rooms route
|
|
16
|
+
* through hospitality, not products). Pricing flows through
|
|
17
|
+
* liveResolve at quote time, not the descriptor.
|
|
18
|
+
*
|
|
19
|
+
* See `docs/architecture/booking-journey-architecture.md` §3 + §F.
|
|
20
|
+
*/
|
|
21
|
+
import { DEFAULT_PAX_BANDS, defaultBookingFields, defaultDraftShapeFlags, defaultTravelerFields, paxBandsAllowedTotalFrom, } from "@voyantjs/catalog/booking-engine";
|
|
22
|
+
export function buildProductDraftShape(content, options = {}) {
|
|
23
|
+
const paxBands = options.paxBands ?? DEFAULT_PAX_BANDS;
|
|
24
|
+
const total = options.paxBandsAllowedTotal ?? paxBandsAllowedTotalFrom(paxBands);
|
|
25
|
+
// Project the product's own options into add-on offers. Each
|
|
26
|
+
// option becomes an extras-type add-on; verticals with grouped
|
|
27
|
+
// catalogs (cruise excursions) override.
|
|
28
|
+
const addonItems = content.options.map((opt) => ({
|
|
29
|
+
id: opt.id,
|
|
30
|
+
name: opt.name,
|
|
31
|
+
description: opt.description ?? null,
|
|
32
|
+
kind: "extras",
|
|
33
|
+
pricingMode: null,
|
|
34
|
+
}));
|
|
35
|
+
return {
|
|
36
|
+
...defaultDraftShapeFlags(),
|
|
37
|
+
showsAddons: addonItems.length > 0,
|
|
38
|
+
paxBands,
|
|
39
|
+
paxBandsAllowedTotal: total,
|
|
40
|
+
travelerFields: defaultTravelerFields(),
|
|
41
|
+
bookingFields: defaultBookingFields(),
|
|
42
|
+
addons: addonItems.length > 0 ? { catalog: addonItems } : undefined,
|
|
43
|
+
paymentIntents: ["hold", "card"],
|
|
44
|
+
configureSubSteps: [{ kind: "occupancy", bands: paxBands }],
|
|
45
|
+
};
|
|
46
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product domain events.
|
|
3
|
+
*
|
|
4
|
+
* Emitted by the products module on lifecycle and content changes.
|
|
5
|
+
* `product.content.changed` is the load-bearing channel-push signal —
|
|
6
|
+
* fired when ANY content axis (description, itinerary, media, options,
|
|
7
|
+
* days) changes.
|
|
8
|
+
*
|
|
9
|
+
* Per docs/architecture/channel-push-architecture.md §6.
|
|
10
|
+
*/
|
|
11
|
+
import type { EventBus } from "@voyantjs/core";
|
|
12
|
+
/** Stable event identifier. */
|
|
13
|
+
export declare const PRODUCT_CONTENT_CHANGED_EVENT: "product.content.changed";
|
|
14
|
+
export interface ProductContentChangedEvent {
|
|
15
|
+
/** Product id whose content changed. */
|
|
16
|
+
id: string;
|
|
17
|
+
/**
|
|
18
|
+
* The content axis that changed, when known. Diagnostic only — channel
|
|
19
|
+
* push hashes the full current content at push time so this field is
|
|
20
|
+
* not load-bearing for correctness.
|
|
21
|
+
*/
|
|
22
|
+
axis?: "product" | "option" | "day" | "media" | "feature" | "faq" | "location" | "destination" | "translation";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Helper for route handlers / services to fire `product.content.changed`
|
|
26
|
+
* with a stable shape. Fire-and-forget per the EventBus contract; the
|
|
27
|
+
* caller does not await this if it's already on a hot path.
|
|
28
|
+
*
|
|
29
|
+
* v1 wiring: every product service/route mutation that touches a
|
|
30
|
+
* content-affecting field should call this helper. The top-level PATCH
|
|
31
|
+
* route already calls it (alongside the legacy `product.updated`); child
|
|
32
|
+
* routes can be wired incrementally. Until full coverage exists, the
|
|
33
|
+
* channel-push reconciler (§13) catches missed events by hashing
|
|
34
|
+
* current content per (product, channel).
|
|
35
|
+
*/
|
|
36
|
+
export declare function emitProductContentChanged(eventBus: EventBus | undefined, payload: ProductContentChangedEvent): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,+BAA+B;AAC/B,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAA;AAE/E,MAAM,WAAW,0BAA0B;IACzC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV;;;;OAIG;IACH,IAAI,CAAC,EACD,SAAS,GACT,QAAQ,GACR,KAAK,GACL,OAAO,GACP,SAAS,GACT,KAAK,GACL,UAAU,GACV,aAAa,GACb,aAAa,CAAA;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product domain events.
|
|
3
|
+
*
|
|
4
|
+
* Emitted by the products module on lifecycle and content changes.
|
|
5
|
+
* `product.content.changed` is the load-bearing channel-push signal —
|
|
6
|
+
* fired when ANY content axis (description, itinerary, media, options,
|
|
7
|
+
* days) changes.
|
|
8
|
+
*
|
|
9
|
+
* Per docs/architecture/channel-push-architecture.md §6.
|
|
10
|
+
*/
|
|
11
|
+
/** Stable event identifier. */
|
|
12
|
+
export const PRODUCT_CONTENT_CHANGED_EVENT = "product.content.changed";
|
|
13
|
+
/**
|
|
14
|
+
* Helper for route handlers / services to fire `product.content.changed`
|
|
15
|
+
* with a stable shape. Fire-and-forget per the EventBus contract; the
|
|
16
|
+
* caller does not await this if it's already on a hot path.
|
|
17
|
+
*
|
|
18
|
+
* v1 wiring: every product service/route mutation that touches a
|
|
19
|
+
* content-affecting field should call this helper. The top-level PATCH
|
|
20
|
+
* route already calls it (alongside the legacy `product.updated`); child
|
|
21
|
+
* routes can be wired incrementally. Until full coverage exists, the
|
|
22
|
+
* channel-push reconciler (§13) catches missed events by hashing
|
|
23
|
+
* current content per (product, channel).
|
|
24
|
+
*/
|
|
25
|
+
export async function emitProductContentChanged(eventBus, payload) {
|
|
26
|
+
if (!eventBus)
|
|
27
|
+
return;
|
|
28
|
+
await eventBus.emit(PRODUCT_CONTENT_CHANGED_EVENT, payload, {
|
|
29
|
+
category: "domain",
|
|
30
|
+
source: "service",
|
|
31
|
+
});
|
|
32
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LinkableDefinition, Module } from "@voyantjs/core";
|
|
2
2
|
import type { HonoModule } from "@voyantjs/hono/module";
|
|
3
3
|
export { productsBookingExtension } from "./booking-extension.js";
|
|
4
|
+
export { emitProductContentChanged, PRODUCT_CONTENT_CHANGED_EVENT, type ProductContentChangedEvent, } from "./events.js";
|
|
4
5
|
export type { ProductRoutes } from "./routes.js";
|
|
5
6
|
export type { PublicProductRoutes } from "./routes-public.js";
|
|
6
7
|
export { publicProductRoutes } from "./routes-public.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { productRoutes } from "./routes.js";
|
|
2
2
|
import { publicProductRoutes } from "./routes-public.js";
|
|
3
3
|
export { productsBookingExtension } from "./booking-extension.js";
|
|
4
|
+
export { emitProductContentChanged, PRODUCT_CONTENT_CHANGED_EVENT, } from "./events.js";
|
|
4
5
|
export { publicProductRoutes } from "./routes-public.js";
|
|
5
6
|
export { productsService } from "./service.js";
|
|
6
7
|
export { catalogProductsService } from "./service-catalog.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product content routes — unified owned + sourced detail endpoint.
|
|
3
|
+
*
|
|
4
|
+
* GET /:id/content
|
|
5
|
+
*
|
|
6
|
+
* Returns the full `ProductContent` payload for ANY product:
|
|
7
|
+
* - **Sourced**: cache hit → cached row + overlay merge; cache miss
|
|
8
|
+
* with rich adapter → adapter fetch + write-through; cache miss
|
|
9
|
+
* with thin adapter → synthesizer fallback (sourced-content §3.3,
|
|
10
|
+
* §3.4, §3.6).
|
|
11
|
+
* - **Owned**: read from the products module's own tables and
|
|
12
|
+
* project to ProductContent. Overlay merge applies the same way.
|
|
13
|
+
* Marked `source: "owned"` in the response.
|
|
14
|
+
*
|
|
15
|
+
* 404 only when the entity doesn't exist (no sourced-entry row AND
|
|
16
|
+
* no owned product row). The catalog detail sheet calls this on
|
|
17
|
+
* click to enrich the indexed projection with itinerary, media,
|
|
18
|
+
* options, and policies.
|
|
19
|
+
*
|
|
20
|
+
* Templates mount this router under their preferred prefix; the
|
|
21
|
+
* factory takes a `resolveRegistry` callback so the catalog
|
|
22
|
+
* `SourceAdapterRegistry` stays template-owned (singleton lifetime,
|
|
23
|
+
* adapters carry HTTP clients).
|
|
24
|
+
*
|
|
25
|
+
* See `docs/architecture/catalog-sourced-content.md` §3.3.
|
|
26
|
+
*/
|
|
27
|
+
import type { SourceAdapterRegistry } from "@voyantjs/catalog/booking-engine";
|
|
28
|
+
import type { AnyDrizzleDb } from "@voyantjs/db";
|
|
29
|
+
import type { Context } from "hono";
|
|
30
|
+
import { Hono } from "hono";
|
|
31
|
+
export interface ProductContentRoutesEnv {
|
|
32
|
+
Variables: {
|
|
33
|
+
db: AnyDrizzleDb;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface CreateProductContentRoutesOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the catalog `SourceAdapterRegistry` for the current
|
|
39
|
+
* request. Templates typically return a process-local singleton
|
|
40
|
+
* built lazily from env (mirroring the booking-engine registry
|
|
41
|
+
* pattern).
|
|
42
|
+
*/
|
|
43
|
+
resolveRegistry: (c: Context) => SourceAdapterRegistry;
|
|
44
|
+
/**
|
|
45
|
+
* Optional sink for overlay-merge diagnostics. When set, called
|
|
46
|
+
* once per overlay that fails to apply. Defaults to silent (the
|
|
47
|
+
* read still succeeds; the bad overlay is skipped).
|
|
48
|
+
*/
|
|
49
|
+
onOverlayError?: (event: {
|
|
50
|
+
field_path: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
}) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Optional override for `acceptMachineTranslated`. Defaults to
|
|
55
|
+
* `true` — storefront-friendly. Operator surfaces typically set
|
|
56
|
+
* `false` so ops sees authored content before deciding to override.
|
|
57
|
+
*/
|
|
58
|
+
defaultAcceptMachineTranslated?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the product content router. Returns a Hono instance that
|
|
62
|
+
* exposes a single `GET /:id/content` route. Templates mount it under
|
|
63
|
+
* `/v1/admin/products` or `/v1/public/products` as appropriate.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createProductContentRoutes(options: CreateProductContentRoutesOptions): Hono<ProductContentRoutesEnv>;
|
|
66
|
+
/**
|
|
67
|
+
* Parse an `Accept-Language` header into an ordered list of BCP 47
|
|
68
|
+
* tags. Quality factors are honored — higher-q first; ties keep
|
|
69
|
+
* insertion order. Lifted out of the route handler so it's testable
|
|
70
|
+
* in isolation.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseAcceptLanguage(header: string): string[];
|
|
73
|
+
export type ProductContentRoutes = ReturnType<typeof createProductContentRoutes>;
|
|
74
|
+
//# sourceMappingURL=routes-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-content.d.ts","sourceRoot":"","sources":["../src/routes-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE;QACT,EAAE,EAAE,YAAY,CAAA;KACjB,CAAA;CACF;AAED,MAAM,WAAW,iCAAiC;IAChD;;;;;OAKG;IACH,eAAe,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,qBAAqB,CAAA;IACtD;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IACxE;;;;OAIG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAA;CACzC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iCAAiC,GACzC,IAAI,CAAC,uBAAuB,CAAC,CA+D/B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB5D;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAA"}
|