@voyant-travel/cruises 0.118.2

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 (210) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +50 -0
  3. package/dist/adapters/connect-compat.d.ts +20 -0
  4. package/dist/adapters/connect-compat.d.ts.map +1 -0
  5. package/dist/adapters/connect-compat.js +71 -0
  6. package/dist/adapters/contract-fixture.d.ts +32 -0
  7. package/dist/adapters/contract-fixture.d.ts.map +1 -0
  8. package/dist/adapters/contract-fixture.js +152 -0
  9. package/dist/adapters/index.d.ts +331 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +16 -0
  12. package/dist/adapters/memoize.d.ts +28 -0
  13. package/dist/adapters/memoize.d.ts.map +1 -0
  14. package/dist/adapters/memoize.js +131 -0
  15. package/dist/adapters/mock.d.ts +44 -0
  16. package/dist/adapters/mock.d.ts.map +1 -0
  17. package/dist/adapters/mock.js +192 -0
  18. package/dist/adapters/registry.d.ts +26 -0
  19. package/dist/adapters/registry.d.ts.map +1 -0
  20. package/dist/adapters/registry.js +42 -0
  21. package/dist/adapters/source-adapter-shim.d.ts +80 -0
  22. package/dist/adapters/source-adapter-shim.d.ts.map +1 -0
  23. package/dist/adapters/source-adapter-shim.js +390 -0
  24. package/dist/booking-engine/handler.d.ts +108 -0
  25. package/dist/booking-engine/handler.d.ts.map +1 -0
  26. package/dist/booking-engine/handler.js +225 -0
  27. package/dist/booking-engine/index.d.ts +9 -0
  28. package/dist/booking-engine/index.d.ts.map +1 -0
  29. package/dist/booking-engine/index.js +8 -0
  30. package/dist/booking-extension.d.ts +1179 -0
  31. package/dist/booking-extension.d.ts.map +1 -0
  32. package/dist/booking-extension.js +342 -0
  33. package/dist/cabin-features.d.ts +8 -0
  34. package/dist/cabin-features.d.ts.map +1 -0
  35. package/dist/cabin-features.js +7 -0
  36. package/dist/catalog-policy-cabins.d.ts +18 -0
  37. package/dist/catalog-policy-cabins.d.ts.map +1 -0
  38. package/dist/catalog-policy-cabins.js +96 -0
  39. package/dist/catalog-policy-core.d.ts +3 -0
  40. package/dist/catalog-policy-core.d.ts.map +1 -0
  41. package/dist/catalog-policy-core.js +247 -0
  42. package/dist/catalog-policy-structure.d.ts +3 -0
  43. package/dist/catalog-policy-structure.d.ts.map +1 -0
  44. package/dist/catalog-policy-structure.js +387 -0
  45. package/dist/catalog-policy.d.ts +15 -0
  46. package/dist/catalog-policy.d.ts.map +1 -0
  47. package/dist/catalog-policy.js +19 -0
  48. package/dist/content-shape.d.ts +5 -0
  49. package/dist/content-shape.d.ts.map +1 -0
  50. package/dist/content-shape.js +13 -0
  51. package/dist/draft-shape.d.ts +59 -0
  52. package/dist/draft-shape.d.ts.map +1 -0
  53. package/dist/draft-shape.js +98 -0
  54. package/dist/events.d.ts +21 -0
  55. package/dist/events.d.ts.map +1 -0
  56. package/dist/events.js +21 -0
  57. package/dist/index.d.ts +43 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +66 -0
  60. package/dist/lib/key.d.ts +41 -0
  61. package/dist/lib/key.d.ts.map +1 -0
  62. package/dist/lib/key.js +100 -0
  63. package/dist/routes-booking-payloads.d.ts +133 -0
  64. package/dist/routes-booking-payloads.d.ts.map +1 -0
  65. package/dist/routes-booking-payloads.js +142 -0
  66. package/dist/routes-content.d.ts +53 -0
  67. package/dist/routes-content.d.ts.map +1 -0
  68. package/dist/routes-content.js +158 -0
  69. package/dist/routes-core.d.ts +4 -0
  70. package/dist/routes-core.d.ts.map +1 -0
  71. package/dist/routes-core.js +68 -0
  72. package/dist/routes-detail.d.ts +4 -0
  73. package/dist/routes-detail.d.ts.map +1 -0
  74. package/dist/routes-detail.js +261 -0
  75. package/dist/routes-env.d.ts +13 -0
  76. package/dist/routes-env.d.ts.map +1 -0
  77. package/dist/routes-env.js +1 -0
  78. package/dist/routes-keying.d.ts +28 -0
  79. package/dist/routes-keying.d.ts.map +1 -0
  80. package/dist/routes-keying.js +70 -0
  81. package/dist/routes-public.d.ts +911 -0
  82. package/dist/routes-public.d.ts.map +1 -0
  83. package/dist/routes-public.js +252 -0
  84. package/dist/routes-sailings-prices.d.ts +4 -0
  85. package/dist/routes-sailings-prices.d.ts.map +1 -0
  86. package/dist/routes-sailings-prices.js +278 -0
  87. package/dist/routes-search-index.d.ts +4 -0
  88. package/dist/routes-search-index.d.ts.map +1 -0
  89. package/dist/routes-search-index.js +25 -0
  90. package/dist/routes-ships.d.ts +4 -0
  91. package/dist/routes-ships.d.ts.map +1 -0
  92. package/dist/routes-ships.js +147 -0
  93. package/dist/routes-voyage-groups.d.ts +4 -0
  94. package/dist/routes-voyage-groups.d.ts.map +1 -0
  95. package/dist/routes-voyage-groups.js +85 -0
  96. package/dist/routes.d.ts +5 -0
  97. package/dist/routes.d.ts.map +1 -0
  98. package/dist/routes.js +14 -0
  99. package/dist/schema-cabins.d.ts +1098 -0
  100. package/dist/schema-cabins.d.ts.map +1 -0
  101. package/dist/schema-cabins.js +105 -0
  102. package/dist/schema-content.d.ts +577 -0
  103. package/dist/schema-content.d.ts.map +1 -0
  104. package/dist/schema-content.js +63 -0
  105. package/dist/schema-core.d.ts +1790 -0
  106. package/dist/schema-core.d.ts.map +1 -0
  107. package/dist/schema-core.js +171 -0
  108. package/dist/schema-itinerary.d.ts +556 -0
  109. package/dist/schema-itinerary.d.ts.map +1 -0
  110. package/dist/schema-itinerary.js +50 -0
  111. package/dist/schema-pricing.d.ts +633 -0
  112. package/dist/schema-pricing.d.ts.map +1 -0
  113. package/dist/schema-pricing.js +73 -0
  114. package/dist/schema-search.d.ts +611 -0
  115. package/dist/schema-search.d.ts.map +1 -0
  116. package/dist/schema-search.js +64 -0
  117. package/dist/schema-shared.d.ts +23 -0
  118. package/dist/schema-shared.d.ts.map +1 -0
  119. package/dist/schema-shared.js +107 -0
  120. package/dist/schema-sourced-content.d.ts +247 -0
  121. package/dist/schema-sourced-content.d.ts.map +1 -0
  122. package/dist/schema-sourced-content.js +38 -0
  123. package/dist/schema.d.ts +10 -0
  124. package/dist/schema.d.ts.map +1 -0
  125. package/dist/schema.js +9 -0
  126. package/dist/service-booking-helpers.d.ts +12 -0
  127. package/dist/service-booking-helpers.d.ts.map +1 -0
  128. package/dist/service-booking-helpers.js +94 -0
  129. package/dist/service-booking-types.d.ts +101 -0
  130. package/dist/service-booking-types.d.ts.map +1 -0
  131. package/dist/service-booking-types.js +1 -0
  132. package/dist/service-bookings.d.ts +46 -0
  133. package/dist/service-bookings.d.ts.map +1 -0
  134. package/dist/service-bookings.js +420 -0
  135. package/dist/service-catalog-plane-cabins.d.ts +24 -0
  136. package/dist/service-catalog-plane-cabins.d.ts.map +1 -0
  137. package/dist/service-catalog-plane-cabins.js +90 -0
  138. package/dist/service-catalog-plane.d.ts +74 -0
  139. package/dist/service-catalog-plane.d.ts.map +1 -0
  140. package/dist/service-catalog-plane.js +194 -0
  141. package/dist/service-content-synthesizer.d.ts +42 -0
  142. package/dist/service-content-synthesizer.d.ts.map +1 -0
  143. package/dist/service-content-synthesizer.js +144 -0
  144. package/dist/service-content.d.ts +74 -0
  145. package/dist/service-content.d.ts.map +1 -0
  146. package/dist/service-content.js +315 -0
  147. package/dist/service-core.d.ts +134 -0
  148. package/dist/service-core.d.ts.map +1 -0
  149. package/dist/service-core.js +257 -0
  150. package/dist/service-detach.d.ts +18 -0
  151. package/dist/service-detach.d.ts.map +1 -0
  152. package/dist/service-detach.js +199 -0
  153. package/dist/service-enrichment.d.ts +11 -0
  154. package/dist/service-enrichment.d.ts.map +1 -0
  155. package/dist/service-enrichment.js +47 -0
  156. package/dist/service-external-refresh.d.ts +39 -0
  157. package/dist/service-external-refresh.d.ts.map +1 -0
  158. package/dist/service-external-refresh.js +47 -0
  159. package/dist/service-itinerary.d.ts +22 -0
  160. package/dist/service-itinerary.d.ts.map +1 -0
  161. package/dist/service-itinerary.js +34 -0
  162. package/dist/service-prices.d.ts +46 -0
  163. package/dist/service-prices.d.ts.map +1 -0
  164. package/dist/service-prices.js +89 -0
  165. package/dist/service-pricing.d.ts +97 -0
  166. package/dist/service-pricing.d.ts.map +1 -0
  167. package/dist/service-pricing.js +198 -0
  168. package/dist/service-sailings.d.ts +48 -0
  169. package/dist/service-sailings.d.ts.map +1 -0
  170. package/dist/service-sailings.js +145 -0
  171. package/dist/service-search-types.d.ts +54 -0
  172. package/dist/service-search-types.d.ts.map +1 -0
  173. package/dist/service-search-types.js +1 -0
  174. package/dist/service-search.d.ts +65 -0
  175. package/dist/service-search.d.ts.map +1 -0
  176. package/dist/service-search.js +467 -0
  177. package/dist/service-shared.d.ts +22 -0
  178. package/dist/service-shared.d.ts.map +1 -0
  179. package/dist/service-shared.js +22 -0
  180. package/dist/service-ships.d.ts +47 -0
  181. package/dist/service-ships.d.ts.map +1 -0
  182. package/dist/service-ships.js +156 -0
  183. package/dist/service.d.ts +255 -0
  184. package/dist/service.d.ts.map +1 -0
  185. package/dist/service.js +12 -0
  186. package/dist/validation-cabins.d.ts +267 -0
  187. package/dist/validation-cabins.d.ts.map +1 -0
  188. package/dist/validation-cabins.js +77 -0
  189. package/dist/validation-content.d.ts +123 -0
  190. package/dist/validation-content.d.ts.map +1 -0
  191. package/dist/validation-content.js +40 -0
  192. package/dist/validation-core.d.ts +393 -0
  193. package/dist/validation-core.d.ts.map +1 -0
  194. package/dist/validation-core.js +162 -0
  195. package/dist/validation-itinerary.d.ts +123 -0
  196. package/dist/validation-itinerary.d.ts.map +1 -0
  197. package/dist/validation-itinerary.js +47 -0
  198. package/dist/validation-pricing.d.ts +137 -0
  199. package/dist/validation-pricing.d.ts.map +1 -0
  200. package/dist/validation-pricing.js +49 -0
  201. package/dist/validation-search.d.ts +118 -0
  202. package/dist/validation-search.d.ts.map +1 -0
  203. package/dist/validation-search.js +60 -0
  204. package/dist/validation-shared.d.ts +123 -0
  205. package/dist/validation-shared.d.ts.map +1 -0
  206. package/dist/validation-shared.js +103 -0
  207. package/dist/validation.d.ts +8 -0
  208. package/dist/validation.d.ts.map +1 -0
  209. package/dist/validation.js +7 -0
  210. package/package.json +146 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"booking-extension.d.ts","sourceRoot":"","sources":["../src/booking-extension.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAa/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAElF,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAI7E,eAAO,MAAM,qBAAqB,8DAAwD,CAAA;AAE1F;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,8EAInC,CAAA;AAIF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+DhC,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,oBAAoB,CAAC,YAAY,CAAA;AAC1E,MAAM,MAAM,sBAAsB,GAAG,OAAO,oBAAoB,CAAC,YAAY,CAAA;AAE7E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwBrC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,OAAO,yBAAyB,CAAC,YAAY,CAAA;AACpF,MAAM,MAAM,2BAA2B,GAAG,OAAO,yBAAyB,CAAC,YAAY,CAAA;AAqBvF,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsD1B,CAAA;AAEJ,QAAA,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;iBAoC/B,CAAA;AAIJ,eAAO,MAAM,2BAA2B;YACxB,kBAAkB,aAAa,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;eAUnF,kBAAkB,aACX,MAAM,QACX,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,GAC7C,OAAO,CAAC,mBAAmB,CAAC;eA4Bd,kBAAkB,aAAa,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAO1E,CAAA;AAED,eAAO,MAAM,gCAAgC;YAErC,kBAAkB,kBACN,MAAM,GACrB,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;eAUrC,kBAAkB,kBACN,MAAM,QAChB,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,GAClD,OAAO,CAAC,wBAAwB,CAAC;eAwBnB,kBAAkB,kBAAkB,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAO/E,CAAA;AAID,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0CAoCtC,CAAA;AASJ,eAAO,MAAM,uBAAuB,EAAE,aAGrC,CAAA"}
@@ -0,0 +1,342 @@
1
+ import { typeIdRef } from "@voyant-travel/db/lib/typeid-column";
2
+ import { parseJsonBody } from "@voyant-travel/hono";
3
+ import { eq } from "drizzle-orm";
4
+ import { char, index, jsonb, numeric, pgEnum, pgTable, smallint, text, timestamp, } from "drizzle-orm/pg-core";
5
+ import { Hono } from "hono";
6
+ import { z } from "zod";
7
+ import { cruiseSourceEnum, priceFareVariantEnum } from "./schema-shared.js";
8
+ // ---------- enums ----------
9
+ export const cruiseBookingModeEnum = pgEnum("cruise_booking_mode", ["inquiry", "reserve"]);
10
+ /**
11
+ * Air-arrangement choice on a cruise booking. Per
12
+ * booking-journey-architecture §7. The cruise booking carries the
13
+ * intent; the flight booking line itself lives in the flights
14
+ * vertical (or with the customer when "independent").
15
+ */
16
+ export const cruiseAirArrangementEnum = pgEnum("cruise_air_arrangement", [
17
+ "cruise_line",
18
+ "independent",
19
+ "none",
20
+ ]);
21
+ // ---------- schemas ----------
22
+ export const bookingCruiseDetails = pgTable("booking_cruise_details", {
23
+ bookingId: text("booking_id").primaryKey(),
24
+ // Provenance — 'local' bookings reference local sailing/cabin rows;
25
+ // 'external' bookings carry a sourceRef back to the upstream adapter
26
+ // and have nullable local FK columns.
27
+ source: cruiseSourceEnum("source").notNull().default("local"),
28
+ sourceProvider: text("source_provider"),
29
+ sourceRef: jsonb("source_ref").$type(),
30
+ sailingId: typeIdRef("sailing_id"),
31
+ cabinCategoryId: typeIdRef("cabin_category_id"),
32
+ cabinId: typeIdRef("cabin_id"),
33
+ // Display / search hints — populated for both local and external so the UI
34
+ // can render booking history without resolving the sailing every time.
35
+ sailingDisplayName: text("sailing_display_name"),
36
+ cabinDisplayName: text("cabin_display_name"),
37
+ occupancy: smallint("occupancy").notNull(),
38
+ fareCode: text("fare_code"),
39
+ fareVariant: priceFareVariantEnum("fare_variant").notNull().default("cruise_only"),
40
+ mode: cruiseBookingModeEnum("mode").notNull().default("inquiry"),
41
+ quotedPricePerPerson: numeric("quoted_price_per_person", { precision: 12, scale: 2 }).notNull(),
42
+ quotedTotalForCabin: numeric("quoted_total_for_cabin", { precision: 12, scale: 2 }).notNull(),
43
+ quotedCurrency: char("quoted_currency", { length: 3 }).notNull(),
44
+ quotedComponentsJson: jsonb("quoted_components_json").$type().default([]),
45
+ bookingTermsSnapshotJson: jsonb("booking_terms_snapshot_json").$type(),
46
+ passengerCompositionSnapshotJson: jsonb("passenger_composition_snapshot_json").$type(),
47
+ connectorBookingRef: text("connector_booking_ref"),
48
+ connectorStatus: text("connector_status"),
49
+ /**
50
+ * Air-arrangement choice carried alongside the cruise. Per
51
+ * booking-journey-architecture §7 — cruises take the booking
52
+ * line for the cabin; the actual flight booking lives in the
53
+ * flights vertical and is the multi-line composer's concern.
54
+ * This column captures the customer's intent so ops can route
55
+ * follow-up:
56
+ * - "cruise_line" — operator must arrange flights with the
57
+ * cruise line's air desk.
58
+ * - "independent" — customer is booking flights themselves
59
+ * (or via a separate flight-booking line
60
+ * orchestrated by the composer).
61
+ * - "none" — no flights needed (regional / drive-to).
62
+ * - null — choice not collected (legacy bookings).
63
+ */
64
+ airArrangement: cruiseAirArrangementEnum("air_arrangement"),
65
+ /** Free-form pointer to the linked flight booking when the
66
+ * composer ties cabin + flight lines together. Plain text per
67
+ * schema-discipline (cross-domain refs go through links). */
68
+ linkedFlightBookingId: text("linked_flight_booking_id"),
69
+ notes: text("notes"),
70
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
71
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
72
+ }, (t) => [
73
+ index("idx_bcd_source").on(t.source),
74
+ index("idx_bcd_sailing").on(t.sailingId),
75
+ index("idx_bcd_cabin_category").on(t.cabinCategoryId),
76
+ index("idx_bcd_connector_ref").on(t.connectorBookingRef),
77
+ index("idx_bcd_provider").on(t.sourceProvider),
78
+ index("idx_bcd_air_arrangement").on(t.airArrangement),
79
+ ]);
80
+ export const bookingGroupCruiseDetails = pgTable("booking_group_cruise_details", {
81
+ bookingGroupId: text("booking_group_id").primaryKey(),
82
+ source: cruiseSourceEnum("source").notNull().default("local"),
83
+ sourceProvider: text("source_provider"),
84
+ sourceRef: jsonb("source_ref").$type(),
85
+ sailingId: typeIdRef("sailing_id"),
86
+ sailingDisplayName: text("sailing_display_name"),
87
+ cabinCount: smallint("cabin_count").notNull(),
88
+ totalQuotedAmount: numeric("total_quoted_amount", { precision: 12, scale: 2 }).notNull(),
89
+ quotedCurrency: char("quoted_currency", { length: 3 }).notNull(),
90
+ connectorBookingRef: text("connector_booking_ref"),
91
+ bookingTermsSnapshotJson: jsonb("booking_terms_snapshot_json").$type(),
92
+ notes: text("notes"),
93
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
94
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
95
+ }, (t) => [
96
+ index("idx_bgcd_source").on(t.source),
97
+ index("idx_bgcd_sailing").on(t.sailingId),
98
+ index("idx_bgcd_connector_ref").on(t.connectorBookingRef),
99
+ index("idx_bgcd_provider").on(t.sourceProvider),
100
+ ]);
101
+ // ---------- validation ----------
102
+ const sourceRefValueSchema = z
103
+ .object({
104
+ connectionId: z.string().optional(),
105
+ externalId: z.string(),
106
+ })
107
+ .catchall(z.unknown());
108
+ const passengerCompositionValueSchema = z
109
+ .object({
110
+ adults: z.number().int().min(0),
111
+ children: z.number().int().min(0).optional(),
112
+ infants: z.number().int().min(0).optional(),
113
+ seniors: z.number().int().min(0).optional(),
114
+ childAges: z.array(z.number().int().min(0).max(17)).optional(),
115
+ })
116
+ .catchall(z.unknown());
117
+ const cruiseDetailUpsertSchema = z
118
+ .object({
119
+ source: z.enum(["local", "external"]).default("local"),
120
+ sourceProvider: z.string().optional().nullable(),
121
+ sourceRef: sourceRefValueSchema.optional().nullable(),
122
+ sailingId: z.string().optional().nullable(),
123
+ cabinCategoryId: z.string().optional().nullable(),
124
+ cabinId: z.string().optional().nullable(),
125
+ sailingDisplayName: z.string().optional().nullable(),
126
+ cabinDisplayName: z.string().optional().nullable(),
127
+ occupancy: z.number().int().min(1).max(8),
128
+ fareCode: z.string().optional().nullable(),
129
+ fareVariant: z.enum(["cruise_only", "air_inclusive"]).default("cruise_only"),
130
+ mode: z.enum(["inquiry", "reserve"]).default("inquiry"),
131
+ quotedPricePerPerson: z.string().regex(/^-?\d+(\.\d{1,2})?$/),
132
+ quotedTotalForCabin: z.string().regex(/^-?\d+(\.\d{1,2})?$/),
133
+ quotedCurrency: z.string().length(3),
134
+ quotedComponentsJson: z.array(z.unknown()).optional().nullable(),
135
+ bookingTermsSnapshotJson: z.record(z.string(), z.unknown()).optional().nullable(),
136
+ passengerCompositionSnapshotJson: passengerCompositionValueSchema.optional().nullable(),
137
+ connectorBookingRef: z.string().optional().nullable(),
138
+ connectorStatus: z.string().optional().nullable(),
139
+ airArrangement: z.enum(["cruise_line", "independent", "none"]).optional().nullable(),
140
+ linkedFlightBookingId: z.string().optional().nullable(),
141
+ notes: z.string().optional().nullable(),
142
+ })
143
+ .superRefine((value, ctx) => {
144
+ if (value.source === "local") {
145
+ if (!value.sailingId)
146
+ ctx.addIssue({
147
+ code: "custom",
148
+ path: ["sailingId"],
149
+ message: "sailingId is required when source='local'",
150
+ });
151
+ if (!value.cabinCategoryId)
152
+ ctx.addIssue({
153
+ code: "custom",
154
+ path: ["cabinCategoryId"],
155
+ message: "cabinCategoryId is required when source='local'",
156
+ });
157
+ }
158
+ else {
159
+ if (!value.sourceProvider)
160
+ ctx.addIssue({
161
+ code: "custom",
162
+ path: ["sourceProvider"],
163
+ message: "sourceProvider is required when source='external'",
164
+ });
165
+ if (!value.sourceRef)
166
+ ctx.addIssue({
167
+ code: "custom",
168
+ path: ["sourceRef"],
169
+ message: "sourceRef is required when source='external'",
170
+ });
171
+ }
172
+ });
173
+ const groupCruiseDetailUpsertSchema = z
174
+ .object({
175
+ source: z.enum(["local", "external"]).default("local"),
176
+ sourceProvider: z.string().optional().nullable(),
177
+ sourceRef: sourceRefValueSchema.optional().nullable(),
178
+ sailingId: z.string().optional().nullable(),
179
+ sailingDisplayName: z.string().optional().nullable(),
180
+ cabinCount: z.number().int().min(1).max(20),
181
+ totalQuotedAmount: z.string().regex(/^-?\d+(\.\d{1,2})?$/),
182
+ quotedCurrency: z.string().length(3),
183
+ connectorBookingRef: z.string().optional().nullable(),
184
+ bookingTermsSnapshotJson: z.record(z.string(), z.unknown()).optional().nullable(),
185
+ notes: z.string().optional().nullable(),
186
+ })
187
+ .superRefine((value, ctx) => {
188
+ if (value.source === "local" && !value.sailingId) {
189
+ ctx.addIssue({
190
+ code: "custom",
191
+ path: ["sailingId"],
192
+ message: "sailingId is required when source='local'",
193
+ });
194
+ }
195
+ if (value.source === "external") {
196
+ if (!value.sourceProvider)
197
+ ctx.addIssue({
198
+ code: "custom",
199
+ path: ["sourceProvider"],
200
+ message: "sourceProvider is required when source='external'",
201
+ });
202
+ if (!value.sourceRef)
203
+ ctx.addIssue({
204
+ code: "custom",
205
+ path: ["sourceRef"],
206
+ message: "sourceRef is required when source='external'",
207
+ });
208
+ }
209
+ });
210
+ // ---------- services ----------
211
+ export const bookingCruiseDetailsService = {
212
+ async get(db, bookingId) {
213
+ const [row] = await db
214
+ .select()
215
+ .from(bookingCruiseDetails)
216
+ .where(eq(bookingCruiseDetails.bookingId, bookingId))
217
+ .limit(1);
218
+ return row ?? null;
219
+ },
220
+ async upsert(db, bookingId, data) {
221
+ const [existing] = await db
222
+ .select()
223
+ .from(bookingCruiseDetails)
224
+ .where(eq(bookingCruiseDetails.bookingId, bookingId))
225
+ .limit(1);
226
+ const payload = {
227
+ ...data,
228
+ bookingId,
229
+ quotedComponentsJson: (data.quotedComponentsJson ?? []),
230
+ };
231
+ if (existing) {
232
+ const [row] = await db
233
+ .update(bookingCruiseDetails)
234
+ .set({ ...payload, updatedAt: new Date() })
235
+ .where(eq(bookingCruiseDetails.bookingId, bookingId))
236
+ .returning();
237
+ if (!row)
238
+ throw new Error("Failed to update booking cruise details");
239
+ return row;
240
+ }
241
+ const [row] = await db.insert(bookingCruiseDetails).values(payload).returning();
242
+ if (!row)
243
+ throw new Error("Failed to insert booking cruise details");
244
+ return row;
245
+ },
246
+ async remove(db, bookingId) {
247
+ const result = await db
248
+ .delete(bookingCruiseDetails)
249
+ .where(eq(bookingCruiseDetails.bookingId, bookingId))
250
+ .returning({ id: bookingCruiseDetails.bookingId });
251
+ return result.length > 0;
252
+ },
253
+ };
254
+ export const bookingGroupCruiseDetailsService = {
255
+ async get(db, bookingGroupId) {
256
+ const [row] = await db
257
+ .select()
258
+ .from(bookingGroupCruiseDetails)
259
+ .where(eq(bookingGroupCruiseDetails.bookingGroupId, bookingGroupId))
260
+ .limit(1);
261
+ return row ?? null;
262
+ },
263
+ async upsert(db, bookingGroupId, data) {
264
+ const [existing] = await db
265
+ .select()
266
+ .from(bookingGroupCruiseDetails)
267
+ .where(eq(bookingGroupCruiseDetails.bookingGroupId, bookingGroupId))
268
+ .limit(1);
269
+ const payload = { ...data, bookingGroupId };
270
+ if (existing) {
271
+ const [row] = await db
272
+ .update(bookingGroupCruiseDetails)
273
+ .set({ ...payload, updatedAt: new Date() })
274
+ .where(eq(bookingGroupCruiseDetails.bookingGroupId, bookingGroupId))
275
+ .returning();
276
+ if (!row)
277
+ throw new Error("Failed to update booking group cruise details");
278
+ return row;
279
+ }
280
+ const [row] = await db.insert(bookingGroupCruiseDetails).values(payload).returning();
281
+ if (!row)
282
+ throw new Error("Failed to insert booking group cruise details");
283
+ return row;
284
+ },
285
+ async remove(db, bookingGroupId) {
286
+ const result = await db
287
+ .delete(bookingGroupCruiseDetails)
288
+ .where(eq(bookingGroupCruiseDetails.bookingGroupId, bookingGroupId))
289
+ .returning({ id: bookingGroupCruiseDetails.bookingGroupId });
290
+ return result.length > 0;
291
+ },
292
+ };
293
+ export const cruisesBookingExtensionRoutes = new Hono()
294
+ .get("/:bookingId/cruise-details", async (c) => {
295
+ const bookingId = c.req.param("bookingId");
296
+ const row = await bookingCruiseDetailsService.get(c.get("db"), bookingId);
297
+ if (!row)
298
+ return c.json({ error: "not_found" }, 404);
299
+ return c.json({ data: row });
300
+ })
301
+ .put("/:bookingId/cruise-details", async (c) => {
302
+ const bookingId = c.req.param("bookingId");
303
+ const data = await parseJsonBody(c, cruiseDetailUpsertSchema);
304
+ const row = await bookingCruiseDetailsService.upsert(c.get("db"), bookingId, data);
305
+ return c.json({ data: row });
306
+ })
307
+ .delete("/:bookingId/cruise-details", async (c) => {
308
+ const bookingId = c.req.param("bookingId");
309
+ const ok = await bookingCruiseDetailsService.remove(c.get("db"), bookingId);
310
+ if (!ok)
311
+ return c.json({ error: "not_found" }, 404);
312
+ return c.body(null, 204);
313
+ })
314
+ .get("/groups/:groupId/cruise-details", async (c) => {
315
+ const groupId = c.req.param("groupId");
316
+ const row = await bookingGroupCruiseDetailsService.get(c.get("db"), groupId);
317
+ if (!row)
318
+ return c.json({ error: "not_found" }, 404);
319
+ return c.json({ data: row });
320
+ })
321
+ .put("/groups/:groupId/cruise-details", async (c) => {
322
+ const groupId = c.req.param("groupId");
323
+ const data = await parseJsonBody(c, groupCruiseDetailUpsertSchema);
324
+ const row = await bookingGroupCruiseDetailsService.upsert(c.get("db"), groupId, data);
325
+ return c.json({ data: row });
326
+ })
327
+ .delete("/groups/:groupId/cruise-details", async (c) => {
328
+ const groupId = c.req.param("groupId");
329
+ const ok = await bookingGroupCruiseDetailsService.remove(c.get("db"), groupId);
330
+ if (!ok)
331
+ return c.json({ error: "not_found" }, 404);
332
+ return c.body(null, 204);
333
+ });
334
+ // ---------- HonoExtension export ----------
335
+ const cruisesBookingExtensionDef = {
336
+ name: "cruises-booking",
337
+ module: "bookings",
338
+ };
339
+ export const cruisesBookingExtension = {
340
+ extension: cruisesBookingExtensionDef,
341
+ adminRoutes: cruisesBookingExtensionRoutes,
342
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cabin facet vocabularies now live in `@voyant-travel/cruises-contracts` so
3
+ * external adapter authors can validate cabin facets without the cruises
4
+ * runtime. Re-exported here to keep the existing `@voyant-travel/cruises`
5
+ * import path stable for the runtime catalog/schema/validation modules.
6
+ */
7
+ export * from "@voyant-travel/cruises-contracts/cabin-features";
8
+ //# sourceMappingURL=cabin-features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cabin-features.d.ts","sourceRoot":"","sources":["../src/cabin-features.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,iDAAiD,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cabin facet vocabularies now live in `@voyant-travel/cruises-contracts` so
3
+ * external adapter authors can validate cabin facets without the cruises
4
+ * runtime. Re-exported here to keep the existing `@voyant-travel/cruises`
5
+ * import path stable for the runtime catalog/schema/validation modules.
6
+ */
7
+ export * from "@voyant-travel/cruises-contracts/cabin-features";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Catalog plane field policies for cruise cabin/deck facets.
3
+ *
4
+ * Cabin categories and decks are promoted child entities under catalog
5
+ * architecture §6.2: they have an independent query surface and the booking
6
+ * captures the selected cabin category. The root cruise search document also
7
+ * receives denormalized facet arrays so storefront browse can answer
8
+ * "show cruises with balcony cabins", "king bed", or "accessible shower"
9
+ * without traversing the ship graph at read time.
10
+ */
11
+ import { type FieldPolicyInput } from "@voyant-travel/catalog/contract";
12
+ export declare const CRUISE_CABIN_FACETS_FIELD_POLICY: FieldPolicyInput[];
13
+ export declare const CRUISE_CABIN_CATEGORY_FIELD_POLICY: FieldPolicyInput[];
14
+ export declare const CRUISE_DECK_FIELD_POLICY: FieldPolicyInput[];
15
+ export declare const cruiseCabinFacetsCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
16
+ export declare const cruiseCabinCategoryCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
17
+ export declare const cruiseDeckCatalogPolicy: import("@voyant-travel/catalog").FieldPolicy[];
18
+ //# sourceMappingURL=catalog-policy-cabins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-policy-cabins.d.ts","sourceRoot":"","sources":["../src/catalog-policy-cabins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAgD1F,eAAO,MAAM,gCAAgC,EAAE,gBAAgB,EAY9D,CAAA;AAED,eAAO,MAAM,kCAAkC,EAAE,gBAAgB,EAmBhE,CAAA;AAED,eAAO,MAAM,wBAAwB,EAAE,gBAAgB,EAOtD,CAAA;AAED,eAAO,MAAM,8BAA8B,gDAAsD,CAAA;AACjG,eAAO,MAAM,gCAAgC,gDAE5C,CAAA;AACD,eAAO,MAAM,uBAAuB,gDAA8C,CAAA"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Catalog plane field policies for cruise cabin/deck facets.
3
+ *
4
+ * Cabin categories and decks are promoted child entities under catalog
5
+ * architecture §6.2: they have an independent query surface and the booking
6
+ * captures the selected cabin category. The root cruise search document also
7
+ * receives denormalized facet arrays so storefront browse can answer
8
+ * "show cruises with balcony cabins", "king bed", or "accessible shower"
9
+ * without traversing the ship graph at read time.
10
+ */
11
+ import { defineFieldPolicy } from "@voyant-travel/catalog/contract";
12
+ const CUSTOMER_FACING = ["staff", "customer", "partner"];
13
+ const managedFacet = (path) => ({
14
+ path,
15
+ class: "structural",
16
+ merge: "source-only",
17
+ drift: "low",
18
+ reindex: "facet-affecting",
19
+ snapshot: "on-book",
20
+ query: "indexed-column",
21
+ localized: false,
22
+ visibility: [...CUSTOMER_FACING],
23
+ editRole: "none",
24
+ overrideFriction: "none",
25
+ sourceFreshness: "sync",
26
+ });
27
+ const managedIndexed = (path) => ({
28
+ ...managedFacet(path),
29
+ class: "managed",
30
+ reindex: "entry",
31
+ });
32
+ const staffTimestamp = (path) => ({
33
+ ...managedFacet(path),
34
+ class: "managed",
35
+ reindex: "none",
36
+ visibility: ["staff"],
37
+ sourceFreshness: "static",
38
+ });
39
+ const merchandisableText = (path) => ({
40
+ path,
41
+ class: "merchandisable",
42
+ merge: "replace",
43
+ drift: "low",
44
+ reindex: "entry-locale",
45
+ snapshot: "on-book",
46
+ query: "indexed-column",
47
+ localized: true,
48
+ visibility: [...CUSTOMER_FACING],
49
+ editRole: "marketing",
50
+ overrideFriction: "none",
51
+ sourceFreshness: "sync",
52
+ });
53
+ export const CRUISE_CABIN_FACETS_FIELD_POLICY = [
54
+ managedFacet("cabinCategoryIds[]"),
55
+ managedFacet("cabinCategoryCodes[]"),
56
+ managedFacet("cabinGradeCodes[]"),
57
+ managedFacet("cabinRoomTypes[]"),
58
+ managedFacet("cabinFeatureCodes[]"),
59
+ managedFacet("cabinBedConfigurations[]"),
60
+ managedFacet("cabinAccessibilityFeatures[]"),
61
+ managedFacet("cabinViewTypes[]"),
62
+ managedFacet("deckIds[]"),
63
+ managedFacet("deckNames[]"),
64
+ managedFacet("deckLevels[]"),
65
+ ];
66
+ export const CRUISE_CABIN_CATEGORY_FIELD_POLICY = [
67
+ managedIndexed("id"),
68
+ managedFacet("shipId"),
69
+ managedFacet("code"),
70
+ merchandisableText("name"),
71
+ merchandisableText("description"),
72
+ managedFacet("roomType"),
73
+ managedFacet("minOccupancy"),
74
+ managedFacet("maxOccupancy"),
75
+ managedFacet("squareFeet"),
76
+ managedFacet("wheelchairAccessible"),
77
+ managedFacet("featureCodes[]"),
78
+ managedFacet("bedConfigurations[]"),
79
+ managedFacet("accessibilityFeatures[]"),
80
+ managedFacet("viewType"),
81
+ merchandisableText("amenities[]"),
82
+ managedFacet("gradeCodes[]"),
83
+ staffTimestamp("createdAt"),
84
+ staffTimestamp("updatedAt"),
85
+ ];
86
+ export const CRUISE_DECK_FIELD_POLICY = [
87
+ managedIndexed("id"),
88
+ managedFacet("shipId"),
89
+ merchandisableText("name"),
90
+ managedFacet("level"),
91
+ staffTimestamp("createdAt"),
92
+ staffTimestamp("updatedAt"),
93
+ ];
94
+ export const cruiseCabinFacetsCatalogPolicy = defineFieldPolicy(CRUISE_CABIN_FACETS_FIELD_POLICY);
95
+ export const cruiseCabinCategoryCatalogPolicy = defineFieldPolicy(CRUISE_CABIN_CATEGORY_FIELD_POLICY);
96
+ export const cruiseDeckCatalogPolicy = defineFieldPolicy(CRUISE_DECK_FIELD_POLICY);
@@ -0,0 +1,3 @@
1
+ import type { FieldPolicyInput } from "@voyant-travel/catalog/contract";
2
+ export declare const CRUISE_CORE_FIELD_POLICY: FieldPolicyInput[];
3
+ //# sourceMappingURL=catalog-policy-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-policy-core.d.ts","sourceRoot":"","sources":["../src/catalog-policy-core.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAEvE,eAAO,MAAM,wBAAwB,EAAE,gBAAgB,EAwPtD,CAAA"}