@voyantjs/hospitality 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/booking-engine/handler.d.ts +105 -0
  2. package/dist/booking-engine/handler.d.ts.map +1 -0
  3. package/dist/booking-engine/handler.js +255 -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.map +1 -1
  8. package/dist/catalog-policy.js +15 -1
  9. package/dist/content-shape.d.ts +185 -0
  10. package/dist/content-shape.d.ts.map +1 -0
  11. package/dist/content-shape.js +122 -0
  12. package/dist/draft-shape.d.ts +35 -0
  13. package/dist/draft-shape.d.ts.map +1 -0
  14. package/dist/draft-shape.js +84 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -0
  18. package/dist/routes-accommodation.d.ts +12 -4
  19. package/dist/routes-accommodation.d.ts.map +1 -1
  20. package/dist/routes-inventory.d.ts +16 -16
  21. package/dist/routes-operations.d.ts +29 -29
  22. package/dist/routes-stays.d.ts +13 -13
  23. package/dist/routes.d.ts +41 -33
  24. package/dist/routes.d.ts.map +1 -1
  25. package/dist/schema-bookings.d.ts +3 -3
  26. package/dist/schema-inventory.d.ts +34 -0
  27. package/dist/schema-inventory.d.ts.map +1 -1
  28. package/dist/schema-inventory.js +10 -0
  29. package/dist/schema-operations.d.ts +1 -1
  30. package/dist/schema-sourced-content.d.ts +254 -0
  31. package/dist/schema-sourced-content.d.ts.map +1 -0
  32. package/dist/schema-sourced-content.js +49 -0
  33. package/dist/schema.d.ts +1 -0
  34. package/dist/schema.d.ts.map +1 -1
  35. package/dist/schema.js +1 -0
  36. package/dist/service-bookings.d.ts +111 -0
  37. package/dist/service-bookings.d.ts.map +1 -0
  38. package/dist/service-bookings.js +147 -0
  39. package/dist/service-catalog-plane.d.ts.map +1 -1
  40. package/dist/service-catalog-plane.js +1 -0
  41. package/dist/service-content-synthesizer.d.ts +43 -0
  42. package/dist/service-content-synthesizer.d.ts.map +1 -0
  43. package/dist/service-content-synthesizer.js +149 -0
  44. package/dist/service-content.d.ts +45 -0
  45. package/dist/service-content.d.ts.map +1 -0
  46. package/dist/service-content.js +254 -0
  47. package/dist/service.d.ts +41 -33
  48. package/dist/service.d.ts.map +1 -1
  49. package/dist/service.js +2 -0
  50. package/dist/validation-accommodation.d.ts +4 -0
  51. package/dist/validation-accommodation.d.ts.map +1 -1
  52. package/dist/validation-accommodation.js +2 -0
  53. package/dist/validation-operations.d.ts +20 -20
  54. package/dist/validation-shared.d.ts +5 -5
  55. package/package.json +18 -8
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Owned-arm booking handler for the `hospitality` vertical.
3
+ *
4
+ * Per `docs/architecture/booking-journey-architecture.md` §6.
5
+ *
6
+ * Phase B scope (deliberately narrow):
7
+ * - `computeQuote` projects the property's hospitality content
8
+ * into a `BookingDraftShape` with date-range + occupancy
9
+ * sub-steps and a Rooms accommodation step. Pricing is best-
10
+ * effort: `nights × room_count × baseRateHint` when the content
11
+ * surfaces a rate hint; otherwise no pricing returned (the
12
+ * wizard hides the total until a real quote lands).
13
+ * - `commit` returns `failed:not_yet_implemented` — the real
14
+ * stay-booking-items + daily-rates write is a follow-up that
15
+ * belongs in the hospitality module's own commit primitive.
16
+ *
17
+ * Templates wire this handler at boot the same way they wire the
18
+ * products handler. Once `commit` ships, the registry is genuinely
19
+ * multi-vertical with no further dispatch changes.
20
+ */
21
+ import type { OwnedBookingHandler, OwnedHandlerContext } from "@voyantjs/catalog/booking-engine";
22
+ import type { HospitalityContent } from "../content-shape.js";
23
+ /**
24
+ * Caller-supplied loader — keeps the handler free of a hard
25
+ * dependency on the template's content service wiring (which spans
26
+ * sourced + owned + overlays). Templates wire this to
27
+ * `getHospitalityContent` from `@voyantjs/hospitality/service-content`.
28
+ */
29
+ export type HospitalityContentLoader = (ctx: OwnedHandlerContext, entityId: string) => Promise<HospitalityContent | null>;
30
+ /**
31
+ * Subset of `hospitalityBookingsService.createStayBooking`'s
32
+ * input. Structural so the handler stays free of a self-import
33
+ * cycle.
34
+ */
35
+ export interface HospitalityCommitBridgeInput {
36
+ propertyId: string;
37
+ roomTypeId: string;
38
+ ratePlanId: string;
39
+ mealPlanId?: string | null;
40
+ checkInDate: string;
41
+ checkOutDate: string;
42
+ roomCount?: number;
43
+ adults?: number;
44
+ children?: number;
45
+ infants?: number;
46
+ dailyRates: Array<{
47
+ sellCurrency: string;
48
+ sellAmountCents?: number | null;
49
+ costCurrency?: string | null;
50
+ costAmountCents?: number | null;
51
+ }>;
52
+ personId?: string | null;
53
+ organizationId?: string | null;
54
+ contact: {
55
+ firstName: string;
56
+ lastName: string;
57
+ email?: string | null;
58
+ phone?: string | null;
59
+ country?: string | null;
60
+ };
61
+ passengers: Array<{
62
+ firstName: string;
63
+ lastName: string;
64
+ email?: string | null;
65
+ phone?: string | null;
66
+ travelerCategory?: "adult" | "child" | "infant" | "senior" | "other" | null;
67
+ isPrimary?: boolean | null;
68
+ }>;
69
+ notes?: string | null;
70
+ }
71
+ export interface HospitalityCommitBridgeResult {
72
+ status: "ok" | "failed";
73
+ bookingId?: string;
74
+ bookingNumber?: string;
75
+ reason?: string;
76
+ }
77
+ export type HospitalityCommitBridge = (input: HospitalityCommitBridgeInput, options?: {
78
+ userId?: string;
79
+ }) => Promise<HospitalityCommitBridgeResult>;
80
+ export interface CreateHospitalityBookingHandlerOptions {
81
+ /** Loader for the property's content payload. */
82
+ loadContent: HospitalityContentLoader;
83
+ /**
84
+ * Default min/max nights when the supplier hasn't declared bounds.
85
+ * The journey's date-range sub-step uses these as guard rails.
86
+ */
87
+ defaultMinNights?: number;
88
+ defaultMaxNights?: number;
89
+ /**
90
+ * Caller-supplied bridge to
91
+ * `hospitalityBookingsService.createStayBooking`. When omitted,
92
+ * commit returns `failed:hospitality_commit_bridge_not_wired`.
93
+ *
94
+ * The journey doesn't currently surface room-type / rate-plan
95
+ * picks at the granularity reserveStay needs (specifically
96
+ * `ratePlanId` and per-night `dailyRates`). Until the journey's
97
+ * Accommodation step + content shape extend to expose those, the
98
+ * caller may map drafts to a "best guess" room/rate from the
99
+ * descriptor's room-options projection — we leave that decision
100
+ * to the template.
101
+ */
102
+ commitBridge?: HospitalityCommitBridge;
103
+ }
104
+ export declare function createHospitalityBookingHandler(options: CreateHospitalityBookingHandlerOptions): OwnedBookingHandler;
105
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/booking-engine/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAMV,mBAAmB,EACnB,mBAAmB,EAEpB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAuB7D;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;AAEvC;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,KAAK,CAAC;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAChC,CAAC,CAAA;IACF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KACxB,CAAA;IACD,UAAU,EAAE,KAAK,CAAC;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,gBAAgB,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAA;QAC3E,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAC3B,CAAC,CAAA;IACF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,uBAAuB,GAAG,CACpC,KAAK,EAAE,4BAA4B,EACnC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1B,OAAO,CAAC,6BAA6B,CAAC,CAAA;AAE3C,MAAM,WAAW,sCAAsC;IACrD,iDAAiD;IACjD,WAAW,EAAE,wBAAwB,CAAA;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAA;CACvC;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,sCAAsC,GAC9C,mBAAmB,CAkLrB"}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Owned-arm booking handler for the `hospitality` vertical.
3
+ *
4
+ * Per `docs/architecture/booking-journey-architecture.md` §6.
5
+ *
6
+ * Phase B scope (deliberately narrow):
7
+ * - `computeQuote` projects the property's hospitality content
8
+ * into a `BookingDraftShape` with date-range + occupancy
9
+ * sub-steps and a Rooms accommodation step. Pricing is best-
10
+ * effort: `nights × room_count × baseRateHint` when the content
11
+ * surfaces a rate hint; otherwise no pricing returned (the
12
+ * wizard hides the total until a real quote lands).
13
+ * - `commit` returns `failed:not_yet_implemented` — the real
14
+ * stay-booking-items + daily-rates write is a follow-up that
15
+ * belongs in the hospitality module's own commit primitive.
16
+ *
17
+ * Templates wire this handler at boot the same way they wire the
18
+ * products handler. Once `commit` ships, the registry is genuinely
19
+ * multi-vertical with no further dispatch changes.
20
+ */
21
+ import { buildHospitalityDraftShape } from "../draft-shape.js";
22
+ export function createHospitalityBookingHandler(options) {
23
+ return {
24
+ entityModule: "hospitality",
25
+ async computeQuote(ctx, request) {
26
+ const content = await options.loadContent(ctx, request.entityId);
27
+ if (!content) {
28
+ return { available: false, invalidReason: "property_not_found" };
29
+ }
30
+ const shape = buildHospitalityDraftShape(content, {
31
+ minNights: options.defaultMinNights,
32
+ maxNights: options.defaultMaxNights,
33
+ });
34
+ const draft = (request.draft ?? {});
35
+ const pricing = computeBestEffortPricing(shape, draft);
36
+ return {
37
+ available: true,
38
+ pricing,
39
+ shape,
40
+ };
41
+ },
42
+ async commit(_ctx, request) {
43
+ if (!options.commitBridge) {
44
+ return {
45
+ status: "failed",
46
+ orderRef: "",
47
+ upstreamPayload: { reason: "hospitality_commit_bridge_not_wired" },
48
+ };
49
+ }
50
+ const draft = (request.draft ?? {});
51
+ const range = draft.configure?.dateRange;
52
+ if (!range?.checkIn || !range?.checkOut) {
53
+ return {
54
+ status: "failed",
55
+ orderRef: "",
56
+ upstreamPayload: {
57
+ reason: "hospitality_commit_missing_inputs",
58
+ need: ["dateRange.checkIn", "dateRange.checkOut"],
59
+ },
60
+ };
61
+ }
62
+ // Map the draft's room selection into a single (roomType, ratePlan) pair.
63
+ // The journey's Accommodation step picks an option_unit_id (which is
64
+ // hospitality's `roomTypeId`); rate plan defaults to the room's first
65
+ // available — templates can override the default by augmenting the
66
+ // bridge output. When no room is picked yet, the commit fails fast.
67
+ const firstRoom = draft.accommodation?.rooms?.[0];
68
+ if (!firstRoom) {
69
+ return {
70
+ status: "failed",
71
+ orderRef: "",
72
+ upstreamPayload: {
73
+ reason: "hospitality_commit_missing_inputs",
74
+ need: ["accommodation.rooms[0]"],
75
+ },
76
+ };
77
+ }
78
+ const adults = draft.configure?.pax?.adult ?? draft.travelers?.length ?? 1;
79
+ const children = draft.configure?.pax?.child ?? 0;
80
+ const infants = draft.configure?.pax?.infant ?? 0;
81
+ const billing = draft.billing ?? {};
82
+ const contact = {
83
+ firstName: billing.contact?.firstName ?? "",
84
+ lastName: billing.contact?.lastName ?? "",
85
+ email: billing.contact?.email ?? null,
86
+ phone: billing.contact?.phone ?? null,
87
+ country: billing.address?.country ?? null,
88
+ };
89
+ const passengers = (draft.travelers ?? []).map((t, idx) => ({
90
+ firstName: t.firstName,
91
+ lastName: t.lastName,
92
+ travelerCategory: t.band === "child" || t.band === "infant"
93
+ ? t.band
94
+ : "adult",
95
+ isPrimary: idx === 0,
96
+ }));
97
+ if (passengers.length === 0) {
98
+ passengers.push({
99
+ firstName: contact.firstName,
100
+ lastName: contact.lastName,
101
+ travelerCategory: "adult",
102
+ isPrimary: true,
103
+ });
104
+ }
105
+ // Per-night rate hint from the draft's pricing breakdown
106
+ // (computed by the handler earlier). The bridge expects
107
+ // `dailyRates[]` with one entry per night — we replicate the
108
+ // averaged hint across nights as a placeholder; production
109
+ // needs supplier-provided rate-plan rows.
110
+ const nights = nightsBetween(range.checkIn, range.checkOut);
111
+ const totalCents = readPricingTotalCents(request.pricing);
112
+ const perNightCents = nights > 0 && totalCents > 0 ? Math.round(totalCents / nights / firstRoom.quantity) : 0;
113
+ const currency = readPricingCurrency(request.pricing) ?? "EUR";
114
+ const dailyRates = Array.from({ length: nights }, () => ({
115
+ sellCurrency: currency,
116
+ sellAmountCents: perNightCents,
117
+ }));
118
+ // The journey now surfaces rate-plan choice via the descriptor's
119
+ // `RoomOption.ratePlans`. When the user hasn't picked one — e.g.
120
+ // the property has no rate plans configured — the commit fails
121
+ // with a helpful reason; the bridge no longer guesses.
122
+ if (!firstRoom.ratePlanId) {
123
+ return {
124
+ status: "failed",
125
+ orderRef: "",
126
+ upstreamPayload: {
127
+ reason: "hospitality_commit_missing_inputs",
128
+ need: ["accommodation.rooms[0].ratePlanId"],
129
+ },
130
+ };
131
+ }
132
+ const bridge = await options.commitBridge({
133
+ propertyId: request.entityId,
134
+ roomTypeId: firstRoom.optionUnitId,
135
+ ratePlanId: firstRoom.ratePlanId,
136
+ checkInDate: range.checkIn,
137
+ checkOutDate: range.checkOut,
138
+ roomCount: firstRoom.quantity,
139
+ adults,
140
+ children,
141
+ infants,
142
+ dailyRates,
143
+ personId: extractPersonId(request.party),
144
+ organizationId: extractOrganizationId(request.party),
145
+ contact,
146
+ passengers,
147
+ notes: typeof draft.internalNotes === "string" ? draft.internalNotes : null,
148
+ });
149
+ if (bridge.status !== "ok" || !bridge.bookingId) {
150
+ return {
151
+ status: "failed",
152
+ orderRef: "",
153
+ upstreamPayload: { reason: bridge.reason ?? "hospitality_commit_failed" },
154
+ };
155
+ }
156
+ return {
157
+ status: "held",
158
+ orderRef: bridge.bookingNumber ?? bridge.bookingId,
159
+ pricing: request.pricing,
160
+ upstreamPayload: { bridgeBookingId: bridge.bookingId },
161
+ };
162
+ },
163
+ async placeHold(_ctx, request) {
164
+ const token = request.draftId ?? `hosp_${Date.now().toString(36)}`;
165
+ return {
166
+ holdToken: token,
167
+ expiresAt: new Date(Date.now() + request.ttlMs),
168
+ };
169
+ },
170
+ async releaseHold(_ctx, _holdToken) {
171
+ // No-op until inventory reservation against rate plans + room
172
+ // inventory lands.
173
+ },
174
+ };
175
+ }
176
+ // ─────────────────────────────────────────────────────────────────
177
+ // Pricing heuristic
178
+ // ─────────────────────────────────────────────────────────────────
179
+ function computeBestEffortPricing(shape, draft) {
180
+ const range = draft.configure?.dateRange;
181
+ if (!range?.checkIn || !range?.checkOut)
182
+ return undefined;
183
+ const nights = nightsBetween(range.checkIn, range.checkOut);
184
+ if (nights <= 0)
185
+ return undefined;
186
+ const rooms = draft.accommodation?.rooms ?? [];
187
+ const totalRooms = rooms.length === 0 ? 1 : rooms.reduce((sum, r) => sum + r.quantity, 0);
188
+ if (totalRooms <= 0)
189
+ return undefined;
190
+ // Pick the cheapest available room option's hint (when surfaced).
191
+ // The journey's Configure step doesn't pin a rate until the user
192
+ // picks a room — this is just a starter total.
193
+ const roomOptions = (shape.accommodation?.roomOptions ?? []);
194
+ const hint = roomOptions
195
+ .map((r) => r.baseRateHint ?? 0)
196
+ .filter((n) => n > 0)
197
+ .sort((a, b) => a - b)[0];
198
+ if (!hint)
199
+ return undefined;
200
+ const totalCents = hint * nights * totalRooms;
201
+ return {
202
+ base_amount: totalCents,
203
+ taxes: 0,
204
+ fees: 0,
205
+ surcharges: 0,
206
+ // Currency is unknown until the rate plan is picked — fall back
207
+ // to EUR as the most common storefront default. Real commits
208
+ // override at quote time.
209
+ currency: "EUR",
210
+ breakdown: {
211
+ lines: [
212
+ {
213
+ kind: "accommodation",
214
+ label: `${totalRooms} room × ${nights} night${nights === 1 ? "" : "s"}`,
215
+ quantity: nights * totalRooms,
216
+ unitAmount: hint,
217
+ totalAmount: totalCents,
218
+ },
219
+ ],
220
+ subtotal: totalCents,
221
+ taxTotal: 0,
222
+ total: totalCents,
223
+ nights,
224
+ rooms: totalRooms,
225
+ },
226
+ };
227
+ }
228
+ function nightsBetween(checkIn, checkOut) {
229
+ const inDate = new Date(checkIn);
230
+ const outDate = new Date(checkOut);
231
+ if (Number.isNaN(inDate.getTime()) || Number.isNaN(outDate.getTime()))
232
+ return 0;
233
+ const ms = outDate.getTime() - inDate.getTime();
234
+ return Math.round(ms / (1000 * 60 * 60 * 24));
235
+ }
236
+ function extractPersonId(party) {
237
+ if (!party)
238
+ return undefined;
239
+ const v = party.personId;
240
+ return typeof v === "string" && v.length > 0 ? v : undefined;
241
+ }
242
+ function extractOrganizationId(party) {
243
+ if (!party)
244
+ return undefined;
245
+ const v = party.organizationId;
246
+ return typeof v === "string" && v.length > 0 ? v : undefined;
247
+ }
248
+ function readPricingTotalCents(pricing) {
249
+ if (!pricing)
250
+ return 0;
251
+ return (pricing.base_amount ?? 0) + (pricing.taxes ?? 0);
252
+ }
253
+ function readPricingCurrency(pricing) {
254
+ return pricing?.currency;
255
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@voyantjs/hospitality/booking-engine` — owned-arm booking
3
+ * handler for the hospitality vertical.
4
+ *
5
+ * Per `docs/architecture/booking-journey-architecture.md` §6.
6
+ */
7
+ export { type CreateHospitalityBookingHandlerOptions, createHospitalityBookingHandler, type HospitalityCommitBridge, type HospitalityCommitBridgeInput, type HospitalityCommitBridgeResult, type HospitalityContentLoader, } from "./handler.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/booking-engine/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,sCAAsC,EAC3C,+BAA+B,EAC/B,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,6BAA6B,EAClC,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@voyantjs/hospitality/booking-engine` — owned-arm booking
3
+ * handler for the hospitality vertical.
4
+ *
5
+ * Per `docs/architecture/booking-journey-architecture.md` §6.
6
+ */
7
+ export { createHospitalityBookingHandler, } from "./handler.js";
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,wBAAwB,EAAE,gBAAgB,EA0X/C,CAAA;AAED,eAAO,MAAM,wBAAwB,2CAA8C,CAAA;AAEnF,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
1
+ {"version":3,"file":"catalog-policy.d.ts","sourceRoot":"","sources":["../src/catalog-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAErF,QAAA,MAAM,wBAAwB,EAAE,gBAAgB,EAwY/C,CAAA;AAED,eAAO,MAAM,wBAAwB,2CAA8C,CAAA;AAEnF,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
@@ -25,7 +25,7 @@ const HOSPITALITY_FIELD_POLICY = [
25
25
  class: "managed",
26
26
  merge: "source-only",
27
27
  drift: "critical",
28
- reindex: "entry",
28
+ reindex: "facet-affecting",
29
29
  snapshot: "on-book",
30
30
  query: "indexed-column",
31
31
  localized: false,
@@ -134,6 +134,20 @@ const HOSPITALITY_FIELD_POLICY = [
134
134
  overrideFriction: "none",
135
135
  sourceFreshness: "sync",
136
136
  },
137
+ {
138
+ path: "supplierId",
139
+ class: "structural",
140
+ merge: "source-only",
141
+ drift: "high",
142
+ reindex: "facet-affecting",
143
+ snapshot: "on-book",
144
+ query: "indexed-column",
145
+ localized: false,
146
+ visibility: ["staff"],
147
+ editRole: "none",
148
+ overrideFriction: "none",
149
+ sourceFreshness: "sync",
150
+ },
137
151
  // ── Merchandisable / marketing ──────────────────────────────────────────
138
152
  {
139
153
  path: "name",
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Hospitality content shape — the rich detail-page content shape
3
+ * returned by `getContent` for sourced room types (bedbanks like
4
+ * Hotelbeds / Expedia, direct-property feeds, hotel groups via Voyant
5
+ * Connect).
6
+ *
7
+ * Per sourced-content §3.6, the hospitality content aggregate is
8
+ * `{ hotel, room_types[], rate_plans[], meal_plans[], amenities[],
9
+ * policies[] }` — one payload returned by a single `getContent`.
10
+ * Pricing stays out (volatile-live, flows through `liveResolve`).
11
+ *
12
+ * The aggregate is **per property** — one row per property × locale ×
13
+ * market — even though the sellable catalog entry is a room type.
14
+ * That's because bedbanks return whole-property payloads and splitting
15
+ * by room type would multiply cache writes and refresh work without
16
+ * benefit. The vertical's read service projects the cached property
17
+ * payload to the requested room-type detail page.
18
+ *
19
+ * See `docs/architecture/catalog-sourced-content.md` §3.2, §3.5.4, §3.6.
20
+ */
21
+ import { type ContentOverlay, type MergeOverlaysOptions } from "@voyantjs/catalog";
22
+ import { z } from "zod";
23
+ export declare const HOSPITALITY_CONTENT_SCHEMA_VERSION = "hospitality/v1";
24
+ export declare const hotelSummarySchema: z.ZodObject<{
25
+ id: z.ZodString;
26
+ name: z.ZodString;
27
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
+ star_rating: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
29
+ hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
+ brand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
32
+ country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
34
+ address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
35
+ postal_code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
36
+ latitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
37
+ longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
38
+ check_in_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
+ check_out_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
+ }, z.core.$strip>;
41
+ export declare const hospitalityRoomTypeSchema: z.ZodObject<{
42
+ id: z.ZodString;
43
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
44
+ name: z.ZodString;
45
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
46
+ room_class: z.ZodOptional<z.ZodNullable<z.ZodString>>;
47
+ view: z.ZodOptional<z.ZodNullable<z.ZodString>>;
48
+ bedrooms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
49
+ beds: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
50
+ size_sqm: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
51
+ max_adults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
52
+ max_children: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
53
+ max_occupancy: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
54
+ amenities: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
55
+ images: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
56
+ }, z.core.$strip>;
57
+ export declare const hospitalityRatePlanSchema: z.ZodObject<{
58
+ id: z.ZodString;
59
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
60
+ name: z.ZodString;
61
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
62
+ charge_frequency: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
63
+ per_night: "per_night";
64
+ per_stay: "per_stay";
65
+ }>>>;
66
+ applies_to_room_type_ids: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
67
+ cancellation_policy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
68
+ inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
69
+ }, z.core.$strip>;
70
+ export declare const hospitalityMealPlanSchema: z.ZodObject<{
71
+ id: z.ZodString;
72
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
73
+ name: z.ZodString;
74
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
75
+ basis: z.ZodString;
76
+ inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
77
+ }, z.core.$strip>;
78
+ export declare const hospitalityAmenitySchema: z.ZodObject<{
79
+ id: z.ZodString;
80
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
81
+ name: z.ZodString;
82
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
83
+ is_free: z.ZodOptional<z.ZodBoolean>;
84
+ }, z.core.$strip>;
85
+ export declare const hospitalityPolicySchema: z.ZodObject<{
86
+ kind: z.ZodEnum<{
87
+ supplier_notes: "supplier_notes";
88
+ cancellation: "cancellation";
89
+ payment: "payment";
90
+ requirements: "requirements";
91
+ check_in: "check_in";
92
+ }>;
93
+ body: z.ZodString;
94
+ rules: z.ZodOptional<z.ZodUnknown>;
95
+ }, z.core.$strip>;
96
+ export declare const hospitalityContentSchema: z.ZodObject<{
97
+ hotel: z.ZodObject<{
98
+ id: z.ZodString;
99
+ name: z.ZodString;
100
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
101
+ star_rating: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
102
+ hero_image_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
103
+ highlights: z.ZodOptional<z.ZodArray<z.ZodString>>;
104
+ brand: z.ZodOptional<z.ZodNullable<z.ZodString>>;
105
+ country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
106
+ city: z.ZodOptional<z.ZodNullable<z.ZodString>>;
107
+ address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
108
+ postal_code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
109
+ latitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
110
+ longitude: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
111
+ check_in_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
112
+ check_out_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
113
+ }, z.core.$strip>;
114
+ room_types: z.ZodDefault<z.ZodArray<z.ZodObject<{
115
+ id: z.ZodString;
116
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
117
+ name: z.ZodString;
118
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
119
+ room_class: z.ZodOptional<z.ZodNullable<z.ZodString>>;
120
+ view: z.ZodOptional<z.ZodNullable<z.ZodString>>;
121
+ bedrooms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
122
+ beds: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
123
+ size_sqm: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
124
+ max_adults: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
125
+ max_children: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
126
+ max_occupancy: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
127
+ amenities: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
128
+ images: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
129
+ }, z.core.$strip>>>;
130
+ rate_plans: z.ZodDefault<z.ZodArray<z.ZodObject<{
131
+ id: z.ZodString;
132
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
133
+ name: z.ZodString;
134
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
135
+ charge_frequency: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
136
+ per_night: "per_night";
137
+ per_stay: "per_stay";
138
+ }>>>;
139
+ applies_to_room_type_ids: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
140
+ cancellation_policy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
141
+ inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
142
+ }, z.core.$strip>>>;
143
+ meal_plans: z.ZodDefault<z.ZodArray<z.ZodObject<{
144
+ id: z.ZodString;
145
+ code: z.ZodOptional<z.ZodNullable<z.ZodString>>;
146
+ name: z.ZodString;
147
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
148
+ basis: z.ZodString;
149
+ inclusions: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
150
+ }, z.core.$strip>>>;
151
+ amenities: z.ZodDefault<z.ZodArray<z.ZodObject<{
152
+ id: z.ZodString;
153
+ category: z.ZodOptional<z.ZodNullable<z.ZodString>>;
154
+ name: z.ZodString;
155
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
156
+ is_free: z.ZodOptional<z.ZodBoolean>;
157
+ }, z.core.$strip>>>;
158
+ policies: z.ZodDefault<z.ZodArray<z.ZodObject<{
159
+ kind: z.ZodEnum<{
160
+ supplier_notes: "supplier_notes";
161
+ cancellation: "cancellation";
162
+ payment: "payment";
163
+ requirements: "requirements";
164
+ check_in: "check_in";
165
+ }>;
166
+ body: z.ZodString;
167
+ rules: z.ZodOptional<z.ZodUnknown>;
168
+ }, z.core.$strip>>>;
169
+ }, z.core.$strip>;
170
+ export type HospitalityContent = z.infer<typeof hospitalityContentSchema>;
171
+ export type HotelSummary = z.infer<typeof hotelSummarySchema>;
172
+ export type HospitalityRoomType = z.infer<typeof hospitalityRoomTypeSchema>;
173
+ export type HospitalityRatePlan = z.infer<typeof hospitalityRatePlanSchema>;
174
+ export type HospitalityMealPlan = z.infer<typeof hospitalityMealPlanSchema>;
175
+ export type HospitalityAmenity = z.infer<typeof hospitalityAmenitySchema>;
176
+ export type HospitalityPolicy = z.infer<typeof hospitalityPolicySchema>;
177
+ export declare function validateHospitalityContent(payload: unknown): {
178
+ valid: true;
179
+ content: HospitalityContent;
180
+ } | {
181
+ valid: false;
182
+ reason: string;
183
+ };
184
+ export declare function mergeOverlaysIntoHospitalityContent(payload: HospitalityContent, overlays: ReadonlyArray<ContentOverlay>, options?: Pick<MergeOverlaysOptions, "onOverlayError">): HospitalityContent;
185
+ //# 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;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAE1B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kCAAkC,mBAAmB,CAAA;AAElE,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;iBAkB7B,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;iBAgBpC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAWpC,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;iBAQpC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;iBAOnC,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;;iBAIlC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOnC,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC7D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AACzE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAA;AAEvE,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAUjF;AAED,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,EACvC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,CAAM,GACzD,kBAAkB,CASpB"}