@voyantjs/products 0.19.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.
Files changed (72) hide show
  1. package/dist/booking-engine/handler.d.ts +203 -0
  2. package/dist/booking-engine/handler.d.ts.map +1 -0
  3. package/dist/booking-engine/handler.js +330 -0
  4. package/dist/booking-engine/index.d.ts +8 -0
  5. package/dist/booking-engine/index.d.ts.map +1 -0
  6. package/dist/booking-engine/index.js +7 -0
  7. package/dist/catalog-policy.d.ts +33 -0
  8. package/dist/catalog-policy.d.ts.map +1 -0
  9. package/dist/catalog-policy.js +421 -0
  10. package/dist/content-shape.d.ts +217 -0
  11. package/dist/content-shape.d.ts.map +1 -0
  12. package/dist/content-shape.js +159 -0
  13. package/dist/draft-shape.d.ts +43 -0
  14. package/dist/draft-shape.d.ts.map +1 -0
  15. package/dist/draft-shape.js +46 -0
  16. package/dist/events.d.ts +37 -0
  17. package/dist/events.d.ts.map +1 -0
  18. package/dist/events.js +32 -0
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -0
  22. package/dist/routes-content.d.ts +74 -0
  23. package/dist/routes-content.d.ts.map +1 -0
  24. package/dist/routes-content.js +117 -0
  25. package/dist/routes.d.ts +47 -26
  26. package/dist/routes.d.ts.map +1 -1
  27. package/dist/routes.js +88 -16
  28. package/dist/schema-core.d.ts +240 -1
  29. package/dist/schema-core.d.ts.map +1 -1
  30. package/dist/schema-core.js +49 -0
  31. package/dist/schema-itinerary.d.ts +18 -1
  32. package/dist/schema-itinerary.d.ts.map +1 -1
  33. package/dist/schema-itinerary.js +1 -0
  34. package/dist/schema-settings.d.ts +1 -1
  35. package/dist/schema-sourced-content.d.ts +262 -0
  36. package/dist/schema-sourced-content.d.ts.map +1 -0
  37. package/dist/schema-sourced-content.js +69 -0
  38. package/dist/schema-taxonomy.d.ts +17 -0
  39. package/dist/schema-taxonomy.d.ts.map +1 -1
  40. package/dist/schema-taxonomy.js +13 -0
  41. package/dist/schema.d.ts +1 -0
  42. package/dist/schema.d.ts.map +1 -1
  43. package/dist/schema.js +1 -0
  44. package/dist/service-catalog-plane.d.ts +129 -0
  45. package/dist/service-catalog-plane.d.ts.map +1 -0
  46. package/dist/service-catalog-plane.js +212 -0
  47. package/dist/service-content-owned.d.ts +68 -0
  48. package/dist/service-content-owned.d.ts.map +1 -0
  49. package/dist/service-content-owned.js +224 -0
  50. package/dist/service-content-synthesizer.d.ts +90 -0
  51. package/dist/service-content-synthesizer.d.ts.map +1 -0
  52. package/dist/service-content-synthesizer.js +171 -0
  53. package/dist/service-content.d.ts +106 -0
  54. package/dist/service-content.d.ts.map +1 -0
  55. package/dist/service-content.js +365 -0
  56. package/dist/service.d.ts +82 -28
  57. package/dist/service.d.ts.map +1 -1
  58. package/dist/service.js +4 -0
  59. package/dist/tasks/brochures.d.ts +2 -1
  60. package/dist/tasks/brochures.d.ts.map +1 -1
  61. package/dist/tasks/brochures.js +3 -0
  62. package/dist/validation-catalog.d.ts +4 -4
  63. package/dist/validation-config.d.ts +3 -3
  64. package/dist/validation-content.d.ts +34 -4
  65. package/dist/validation-content.d.ts.map +1 -1
  66. package/dist/validation-content.js +13 -0
  67. package/dist/validation-core.d.ts +53 -3
  68. package/dist/validation-core.d.ts.map +1 -1
  69. package/dist/validation-core.js +16 -0
  70. package/dist/validation-public.d.ts +9 -9
  71. package/dist/validation-shared.d.ts +4 -4
  72. package/package.json +12 -6
@@ -0,0 +1,421 @@
1
+ /**
2
+ * Catalog plane field policy for `packages/products`.
3
+ *
4
+ * Declares every product field's governance under the
5
+ * `@voyantjs/catalog` 12-attribute contract. Phase B shake-out
6
+ * adoption — see `docs/architecture/catalog-architecture.md` §9.1.
7
+ *
8
+ * Scope of this file:
9
+ * - The root `products` table (from `schema-core.ts`).
10
+ * - Provenance + identity fields the catalog plane needs to track.
11
+ *
12
+ * Out of scope (deferred to follow-up adoption passes):
13
+ * - `productOptions`, `optionUnits`, `productDays`, `productNotes`,
14
+ * `productVersions` — promoted child entities (per composition rule §6.2);
15
+ * each gets its own micro-registry when wired in.
16
+ * - The split of `tags` into `marketing_tags` + `facet_tags` per the
17
+ * human-readable / machine-evaluable rule (§7.1). Today's schema has a
18
+ * single `tags` column; declared here as merchandisable with a TODO.
19
+ */
20
+ import { defineFieldPolicy } from "@voyantjs/catalog/contract";
21
+ /**
22
+ * Field-policy declarations for `products`. Pass through `defineFieldPolicy`
23
+ * to apply inheritance and produce the runtime registry.
24
+ */
25
+ const PRODUCT_FIELD_POLICY = [
26
+ // ── Source pointer / provenance ─────────────────────────────────────────
27
+ // These are not columns on the `products` table; they live on the parallel
28
+ // catalog Provenance row. Declared here so the indexer / overlay resolver
29
+ // know how to treat them.
30
+ {
31
+ path: "source.kind",
32
+ class: "managed",
33
+ merge: "source-only",
34
+ drift: "critical",
35
+ reindex: "facet-affecting",
36
+ snapshot: "on-book",
37
+ query: "indexed-column",
38
+ localized: false,
39
+ visibility: ["staff"],
40
+ editRole: "none",
41
+ overrideFriction: "none",
42
+ sourceFreshness: "sync",
43
+ },
44
+ {
45
+ path: "source.ref",
46
+ class: "managed",
47
+ merge: "source-only",
48
+ drift: "critical",
49
+ reindex: "none",
50
+ snapshot: "on-book",
51
+ query: "indexed-column",
52
+ localized: false,
53
+ visibility: ["staff"],
54
+ editRole: "none",
55
+ overrideFriction: "none",
56
+ sourceFreshness: "sync",
57
+ },
58
+ {
59
+ path: "seller.operator_id",
60
+ class: "managed",
61
+ merge: "source-only",
62
+ drift: "critical",
63
+ reindex: "none",
64
+ snapshot: "on-book",
65
+ query: "indexed-column",
66
+ localized: false,
67
+ visibility: ["staff"],
68
+ editRole: "none",
69
+ overrideFriction: "none",
70
+ sourceFreshness: "static",
71
+ },
72
+ // ── Identity / lifecycle ────────────────────────────────────────────────
73
+ {
74
+ path: "id",
75
+ class: "managed",
76
+ merge: "source-only",
77
+ drift: "critical",
78
+ reindex: "none",
79
+ snapshot: "on-book",
80
+ query: "first-class-table",
81
+ localized: false,
82
+ visibility: ["staff", "customer", "partner"],
83
+ editRole: "none",
84
+ overrideFriction: "none",
85
+ sourceFreshness: "static",
86
+ },
87
+ {
88
+ path: "createdAt",
89
+ class: "managed",
90
+ merge: "source-only",
91
+ drift: "none",
92
+ reindex: "none",
93
+ snapshot: "on-book",
94
+ query: "indexed-column",
95
+ localized: false,
96
+ visibility: ["staff"],
97
+ editRole: "none",
98
+ overrideFriction: "none",
99
+ sourceFreshness: "static",
100
+ },
101
+ {
102
+ path: "updatedAt",
103
+ class: "managed",
104
+ merge: "source-only",
105
+ drift: "none",
106
+ reindex: "none",
107
+ snapshot: "never",
108
+ query: "indexed-column",
109
+ localized: false,
110
+ visibility: ["staff"],
111
+ editRole: "none",
112
+ overrideFriction: "none",
113
+ sourceFreshness: "sync",
114
+ },
115
+ // ── Merchandisable / marketing ──────────────────────────────────────────
116
+ // Note: `name` maps to "title" in catalog vocabulary. The existing schema
117
+ // column stays `name` for backwards compatibility; the field-policy path
118
+ // uses the schema column name so the indexer can map it directly.
119
+ {
120
+ path: "name",
121
+ class: "merchandisable",
122
+ merge: "replace",
123
+ drift: "medium",
124
+ reindex: "entry-locale",
125
+ snapshot: "on-book",
126
+ query: "indexed-column",
127
+ localized: true,
128
+ visibility: ["staff", "customer", "partner"],
129
+ editRole: "marketing",
130
+ overrideFriction: "none",
131
+ sourceFreshness: "sync",
132
+ },
133
+ {
134
+ path: "description",
135
+ class: "merchandisable",
136
+ merge: "replace",
137
+ drift: "low",
138
+ reindex: "entry-locale",
139
+ snapshot: "on-book",
140
+ query: "blob-only",
141
+ localized: true,
142
+ visibility: ["staff", "customer", "partner"],
143
+ editRole: "marketing",
144
+ overrideFriction: "none",
145
+ sourceFreshness: "sync",
146
+ },
147
+ // TODO(catalog): split into marketing_tags + facet_tags per architecture
148
+ // §7.1 (human-readable + machine-evaluable rule). Today's schema has one
149
+ // `tags` column; declared here as merchandisable + additive-set so
150
+ // marketing can extend the source set without trampling it. Until the
151
+ // split lands, search-facet leakage is possible (marketing edits affect
152
+ // search facets); flag for follow-up.
153
+ {
154
+ path: "tags[]",
155
+ class: "merchandisable",
156
+ merge: "additive-set",
157
+ drift: "low",
158
+ reindex: "entry",
159
+ snapshot: "on-book",
160
+ query: "indexed-column",
161
+ localized: false,
162
+ visibility: ["staff", "customer", "partner"],
163
+ editRole: "marketing",
164
+ overrideFriction: "none",
165
+ sourceFreshness: "sync",
166
+ },
167
+ // ── Structural / facet-affecting ───────────────────────────────────────
168
+ {
169
+ path: "status",
170
+ class: "structural",
171
+ merge: "source-only",
172
+ drift: "high",
173
+ reindex: "facet-affecting",
174
+ snapshot: "on-book",
175
+ query: "indexed-column",
176
+ localized: false,
177
+ visibility: ["staff"],
178
+ editRole: "none",
179
+ overrideFriction: "none",
180
+ sourceFreshness: "sync",
181
+ },
182
+ {
183
+ path: "bookingMode",
184
+ class: "structural",
185
+ merge: "source-only",
186
+ drift: "high",
187
+ reindex: "facet-affecting",
188
+ snapshot: "on-book",
189
+ query: "indexed-column",
190
+ localized: false,
191
+ visibility: ["staff", "customer", "partner"],
192
+ editRole: "none",
193
+ overrideFriction: "none",
194
+ sourceFreshness: "sync",
195
+ },
196
+ {
197
+ path: "capacityMode",
198
+ class: "structural",
199
+ merge: "source-only",
200
+ drift: "medium",
201
+ reindex: "entry",
202
+ snapshot: "on-book",
203
+ query: "indexed-column",
204
+ localized: false,
205
+ visibility: ["staff"],
206
+ editRole: "none",
207
+ overrideFriction: "none",
208
+ sourceFreshness: "sync",
209
+ },
210
+ {
211
+ // The `visibility` *column* on the products table — distinct from the
212
+ // catalog plane's audience-visibility axis.
213
+ path: "visibility",
214
+ class: "structural",
215
+ merge: "source-only",
216
+ drift: "high",
217
+ reindex: "facet-affecting",
218
+ snapshot: "on-book",
219
+ query: "indexed-column",
220
+ localized: false,
221
+ visibility: ["staff"],
222
+ editRole: "none",
223
+ overrideFriction: "none",
224
+ sourceFreshness: "sync",
225
+ },
226
+ {
227
+ path: "activated",
228
+ class: "structural",
229
+ merge: "source-only",
230
+ drift: "high",
231
+ reindex: "facet-affecting",
232
+ snapshot: "on-book",
233
+ query: "indexed-column",
234
+ localized: false,
235
+ visibility: ["staff"],
236
+ editRole: "none",
237
+ overrideFriction: "none",
238
+ sourceFreshness: "sync",
239
+ },
240
+ {
241
+ path: "productTypeId",
242
+ class: "structural",
243
+ merge: "source-only",
244
+ drift: "medium",
245
+ reindex: "facet-affecting",
246
+ snapshot: "on-book",
247
+ query: "indexed-column",
248
+ localized: false,
249
+ visibility: ["staff", "customer", "partner"],
250
+ editRole: "none",
251
+ overrideFriction: "none",
252
+ sourceFreshness: "sync",
253
+ },
254
+ {
255
+ path: "facilityId",
256
+ class: "structural",
257
+ merge: "source-only",
258
+ drift: "medium",
259
+ reindex: "entry",
260
+ snapshot: "on-book",
261
+ query: "indexed-column",
262
+ localized: false,
263
+ visibility: ["staff"],
264
+ editRole: "none",
265
+ overrideFriction: "none",
266
+ sourceFreshness: "sync",
267
+ },
268
+ {
269
+ path: "supplierId",
270
+ class: "structural",
271
+ merge: "source-only",
272
+ drift: "high",
273
+ reindex: "facet-affecting",
274
+ snapshot: "on-book",
275
+ query: "indexed-column",
276
+ localized: false,
277
+ visibility: ["staff"],
278
+ editRole: "none",
279
+ overrideFriction: "none",
280
+ sourceFreshness: "sync",
281
+ },
282
+ {
283
+ path: "pax",
284
+ class: "structural",
285
+ merge: "source-only",
286
+ drift: "medium",
287
+ reindex: "entry",
288
+ snapshot: "on-book",
289
+ query: "indexed-column",
290
+ localized: false,
291
+ visibility: ["staff", "customer", "partner"],
292
+ editRole: "none",
293
+ overrideFriction: "none",
294
+ sourceFreshness: "sync",
295
+ },
296
+ {
297
+ path: "startDate",
298
+ class: "structural",
299
+ merge: "source-only",
300
+ drift: "medium",
301
+ reindex: "facet-affecting",
302
+ snapshot: "on-book",
303
+ query: "indexed-column",
304
+ localized: false,
305
+ visibility: ["staff", "customer", "partner"],
306
+ editRole: "none",
307
+ overrideFriction: "none",
308
+ sourceFreshness: "sync",
309
+ },
310
+ {
311
+ path: "endDate",
312
+ class: "structural",
313
+ merge: "source-only",
314
+ drift: "medium",
315
+ reindex: "facet-affecting",
316
+ snapshot: "on-book",
317
+ query: "indexed-column",
318
+ localized: false,
319
+ visibility: ["staff", "customer", "partner"],
320
+ editRole: "none",
321
+ overrideFriction: "none",
322
+ sourceFreshness: "sync",
323
+ },
324
+ {
325
+ path: "timezone",
326
+ class: "managed",
327
+ merge: "source-only",
328
+ drift: "low",
329
+ reindex: "none",
330
+ snapshot: "on-book",
331
+ query: "blob-only",
332
+ localized: false,
333
+ visibility: ["staff", "customer", "partner"],
334
+ editRole: "none",
335
+ overrideFriction: "none",
336
+ sourceFreshness: "sync",
337
+ },
338
+ {
339
+ path: "reservationTimeoutMinutes",
340
+ class: "structural",
341
+ merge: "source-only",
342
+ drift: "low",
343
+ reindex: "none",
344
+ snapshot: "never",
345
+ query: "blob-only",
346
+ localized: false,
347
+ visibility: ["staff"],
348
+ editRole: "none",
349
+ overrideFriction: "none",
350
+ sourceFreshness: "sync",
351
+ },
352
+ // ── Pricing (configured defaults — not the live quote) ──────────────────
353
+ // These are the operator's configured prices on the product. The live
354
+ // quote engine resolves volatile-live `quote_price` separately at quote
355
+ // time and is captured at booking commit (snapshot mode handled by the
356
+ // pricing module's own field policy when it adopts).
357
+ {
358
+ path: "sellAmountCents",
359
+ class: "structural",
360
+ merge: "source-only",
361
+ drift: "high",
362
+ reindex: "entry",
363
+ snapshot: "on-quote-and-book",
364
+ query: "indexed-column",
365
+ localized: false,
366
+ visibility: ["staff", "customer", "partner"],
367
+ editRole: "none",
368
+ overrideFriction: "none",
369
+ sourceFreshness: "sync",
370
+ },
371
+ {
372
+ path: "sellCurrency",
373
+ class: "managed",
374
+ merge: "source-only",
375
+ drift: "high",
376
+ reindex: "entry",
377
+ snapshot: "on-quote-and-book",
378
+ query: "indexed-column",
379
+ localized: false,
380
+ visibility: ["staff", "customer", "partner"],
381
+ editRole: "none",
382
+ overrideFriction: "none",
383
+ sourceFreshness: "sync",
384
+ },
385
+ // ── Internal / staff-only ──────────────────────────────────────────────
386
+ {
387
+ path: "costAmountCents",
388
+ class: "managed",
389
+ merge: "source-only",
390
+ drift: "medium",
391
+ reindex: "none",
392
+ snapshot: "never",
393
+ query: "blob-only",
394
+ localized: false,
395
+ visibility: ["staff"],
396
+ editRole: "none",
397
+ overrideFriction: "none",
398
+ sourceFreshness: "sync",
399
+ },
400
+ {
401
+ path: "marginPercent",
402
+ class: "managed",
403
+ merge: "source-only",
404
+ drift: "medium",
405
+ reindex: "none",
406
+ snapshot: "never",
407
+ query: "blob-only",
408
+ localized: false,
409
+ visibility: ["staff"],
410
+ editRole: "none",
411
+ overrideFriction: "none",
412
+ sourceFreshness: "sync",
413
+ },
414
+ ];
415
+ /**
416
+ * Resolved field-policy registry for products. Verticals adopt the catalog
417
+ * plane by exporting this; templates wire it into the indexer, overlay
418
+ * resolver, and snapshot capture pipeline.
419
+ */
420
+ export const productCatalogPolicy = defineFieldPolicy(PRODUCT_FIELD_POLICY);
421
+ export { PRODUCT_FIELD_POLICY };
@@ -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"}