@voyant-travel/catalog 0.117.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 (243) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +190 -0
  3. package/dist/adapter/booking-forwarding.d.ts +2 -0
  4. package/dist/adapter/booking-forwarding.d.ts.map +1 -0
  5. package/dist/adapter/booking-forwarding.js +1 -0
  6. package/dist/adapter/channel-push-contracts.d.ts +2 -0
  7. package/dist/adapter/channel-push-contracts.d.ts.map +1 -0
  8. package/dist/adapter/channel-push-contracts.js +1 -0
  9. package/dist/adapter/contract.d.ts +2 -0
  10. package/dist/adapter/contract.d.ts.map +1 -0
  11. package/dist/adapter/contract.js +1 -0
  12. package/dist/adapter/contract.test.d.ts +2 -0
  13. package/dist/adapter/contract.test.d.ts.map +1 -0
  14. package/dist/adapter/contract.test.js +390 -0
  15. package/dist/adapter/provider-contracts.d.ts +2 -0
  16. package/dist/adapter/provider-contracts.d.ts.map +1 -0
  17. package/dist/adapter/provider-contracts.js +1 -0
  18. package/dist/adapter/provider-contracts.test.d.ts +2 -0
  19. package/dist/adapter/provider-contracts.test.d.ts.map +1 -0
  20. package/dist/adapter/provider-contracts.test.js +206 -0
  21. package/dist/adapter/schemas.d.ts +2 -0
  22. package/dist/adapter/schemas.d.ts.map +1 -0
  23. package/dist/adapter/schemas.js +1 -0
  24. package/dist/adapter/schemas.test.d.ts +2 -0
  25. package/dist/adapter/schemas.test.d.ts.map +1 -0
  26. package/dist/adapter/schemas.test.js +344 -0
  27. package/dist/booking-engine/book.d.ts +124 -0
  28. package/dist/booking-engine/book.d.ts.map +1 -0
  29. package/dist/booking-engine/book.js +311 -0
  30. package/dist/booking-engine/cancel.d.ts +40 -0
  31. package/dist/booking-engine/cancel.d.ts.map +1 -0
  32. package/dist/booking-engine/cancel.js +56 -0
  33. package/dist/booking-engine/checkout-finalize.d.ts +146 -0
  34. package/dist/booking-engine/checkout-finalize.d.ts.map +1 -0
  35. package/dist/booking-engine/checkout-finalize.js +132 -0
  36. package/dist/booking-engine/contracts.d.ts +9 -0
  37. package/dist/booking-engine/contracts.d.ts.map +1 -0
  38. package/dist/booking-engine/contracts.js +8 -0
  39. package/dist/booking-engine/contracts.test.d.ts +2 -0
  40. package/dist/booking-engine/contracts.test.d.ts.map +1 -0
  41. package/dist/booking-engine/contracts.test.js +116 -0
  42. package/dist/booking-engine/draft-shape.d.ts +10 -0
  43. package/dist/booking-engine/draft-shape.d.ts.map +1 -0
  44. package/dist/booking-engine/draft-shape.js +9 -0
  45. package/dist/booking-engine/draft-shape.test.d.ts +2 -0
  46. package/dist/booking-engine/draft-shape.test.d.ts.map +1 -0
  47. package/dist/booking-engine/draft-shape.test.js +74 -0
  48. package/dist/booking-engine/drafts-schema.d.ts +302 -0
  49. package/dist/booking-engine/drafts-schema.d.ts.map +1 -0
  50. package/dist/booking-engine/drafts-schema.js +53 -0
  51. package/dist/booking-engine/drafts-service.d.ts +41 -0
  52. package/dist/booking-engine/drafts-service.d.ts.map +1 -0
  53. package/dist/booking-engine/drafts-service.js +108 -0
  54. package/dist/booking-engine/errors.d.ts +81 -0
  55. package/dist/booking-engine/errors.d.ts.map +1 -0
  56. package/dist/booking-engine/errors.js +113 -0
  57. package/dist/booking-engine/index.d.ts +36 -0
  58. package/dist/booking-engine/index.d.ts.map +1 -0
  59. package/dist/booking-engine/index.js +34 -0
  60. package/dist/booking-engine/orders.d.ts +41 -0
  61. package/dist/booking-engine/orders.d.ts.map +1 -0
  62. package/dist/booking-engine/orders.js +49 -0
  63. package/dist/booking-engine/owned-handler.d.ts +166 -0
  64. package/dist/booking-engine/owned-handler.d.ts.map +1 -0
  65. package/dist/booking-engine/owned-handler.js +50 -0
  66. package/dist/booking-engine/owned-handler.test.d.ts +2 -0
  67. package/dist/booking-engine/owned-handler.test.d.ts.map +1 -0
  68. package/dist/booking-engine/owned-handler.test.js +63 -0
  69. package/dist/booking-engine/promotions-contract.d.ts +8 -0
  70. package/dist/booking-engine/promotions-contract.d.ts.map +1 -0
  71. package/dist/booking-engine/promotions-contract.js +7 -0
  72. package/dist/booking-engine/quote-enricher.test.d.ts +12 -0
  73. package/dist/booking-engine/quote-enricher.test.d.ts.map +1 -0
  74. package/dist/booking-engine/quote-enricher.test.js +138 -0
  75. package/dist/booking-engine/quote.d.ts +163 -0
  76. package/dist/booking-engine/quote.d.ts.map +1 -0
  77. package/dist/booking-engine/quote.js +259 -0
  78. package/dist/booking-engine/registry.d.ts +85 -0
  79. package/dist/booking-engine/registry.d.ts.map +1 -0
  80. package/dist/booking-engine/registry.js +118 -0
  81. package/dist/booking-engine/registry.test.d.ts +2 -0
  82. package/dist/booking-engine/registry.test.d.ts.map +1 -0
  83. package/dist/booking-engine/registry.test.js +132 -0
  84. package/dist/booking-engine/routes-contracts.d.ts +169 -0
  85. package/dist/booking-engine/routes-contracts.d.ts.map +1 -0
  86. package/dist/booking-engine/routes-contracts.js +63 -0
  87. package/dist/booking-engine/routes.d.ts +7 -0
  88. package/dist/booking-engine/routes.d.ts.map +1 -0
  89. package/dist/booking-engine/routes.js +443 -0
  90. package/dist/booking-engine/routes.test.d.ts +2 -0
  91. package/dist/booking-engine/routes.test.d.ts.map +1 -0
  92. package/dist/booking-engine/routes.test.js +304 -0
  93. package/dist/booking-engine/schema.d.ts +455 -0
  94. package/dist/booking-engine/schema.d.ts.map +1 -0
  95. package/dist/booking-engine/schema.js +75 -0
  96. package/dist/booking-engine/snapshot-content.d.ts +120 -0
  97. package/dist/booking-engine/snapshot-content.d.ts.map +1 -0
  98. package/dist/booking-engine/snapshot-content.js +110 -0
  99. package/dist/booking-engine/snapshot-content.test.d.ts +2 -0
  100. package/dist/booking-engine/snapshot-content.test.d.ts.map +1 -0
  101. package/dist/booking-engine/snapshot-content.test.js +213 -0
  102. package/dist/booking-engine/sync.d.ts +136 -0
  103. package/dist/booking-engine/sync.d.ts.map +1 -0
  104. package/dist/booking-engine/sync.js +177 -0
  105. package/dist/booking-engine/sync.test.d.ts +2 -0
  106. package/dist/booking-engine/sync.test.d.ts.map +1 -0
  107. package/dist/booking-engine/sync.test.js +377 -0
  108. package/dist/contract.d.ts +2 -0
  109. package/dist/contract.d.ts.map +1 -0
  110. package/dist/contract.js +1 -0
  111. package/dist/contract.test.d.ts +2 -0
  112. package/dist/contract.test.d.ts.map +1 -0
  113. package/dist/contract.test.js +107 -0
  114. package/dist/drift/events.d.ts +2 -0
  115. package/dist/drift/events.d.ts.map +1 -0
  116. package/dist/drift/events.js +1 -0
  117. package/dist/drift/events.test.d.ts +2 -0
  118. package/dist/drift/events.test.d.ts.map +1 -0
  119. package/dist/drift/events.test.js +100 -0
  120. package/dist/embeddings/contract.d.ts +85 -0
  121. package/dist/embeddings/contract.d.ts.map +1 -0
  122. package/dist/embeddings/contract.js +42 -0
  123. package/dist/embeddings/contract.test.d.ts +2 -0
  124. package/dist/embeddings/contract.test.d.ts.map +1 -0
  125. package/dist/embeddings/contract.test.js +30 -0
  126. package/dist/embeddings/gemini.d.ts +110 -0
  127. package/dist/embeddings/gemini.d.ts.map +1 -0
  128. package/dist/embeddings/gemini.js +118 -0
  129. package/dist/embeddings/gemini.test.d.ts +2 -0
  130. package/dist/embeddings/gemini.test.d.ts.map +1 -0
  131. package/dist/embeddings/gemini.test.js +132 -0
  132. package/dist/embeddings/model-registry.d.ts +62 -0
  133. package/dist/embeddings/model-registry.d.ts.map +1 -0
  134. package/dist/embeddings/model-registry.js +78 -0
  135. package/dist/embeddings/model-registry.test.d.ts +2 -0
  136. package/dist/embeddings/model-registry.test.d.ts.map +1 -0
  137. package/dist/embeddings/model-registry.test.js +81 -0
  138. package/dist/embeddings/openai.d.ts +81 -0
  139. package/dist/embeddings/openai.d.ts.map +1 -0
  140. package/dist/embeddings/openai.js +123 -0
  141. package/dist/embeddings/openai.test.d.ts +2 -0
  142. package/dist/embeddings/openai.test.d.ts.map +1 -0
  143. package/dist/embeddings/openai.test.js +164 -0
  144. package/dist/events/taxonomy.d.ts +158 -0
  145. package/dist/events/taxonomy.d.ts.map +1 -0
  146. package/dist/events/taxonomy.js +99 -0
  147. package/dist/events/taxonomy.test.d.ts +2 -0
  148. package/dist/events/taxonomy.test.d.ts.map +1 -0
  149. package/dist/events/taxonomy.test.js +48 -0
  150. package/dist/index.d.ts +27 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +39 -0
  153. package/dist/indexer/contract.d.ts +203 -0
  154. package/dist/indexer/contract.d.ts.map +1 -0
  155. package/dist/indexer/contract.js +16 -0
  156. package/dist/indexer/typesense-search-query.d.ts +31 -0
  157. package/dist/indexer/typesense-search-query.d.ts.map +1 -0
  158. package/dist/indexer/typesense-search-query.js +185 -0
  159. package/dist/indexer/typesense.d.ts +105 -0
  160. package/dist/indexer/typesense.d.ts.map +1 -0
  161. package/dist/indexer/typesense.js +394 -0
  162. package/dist/indexer/typesense.test.d.ts +2 -0
  163. package/dist/indexer/typesense.test.d.ts.map +1 -0
  164. package/dist/indexer/typesense.test.js +253 -0
  165. package/dist/overlay/resolver.d.ts +101 -0
  166. package/dist/overlay/resolver.d.ts.map +1 -0
  167. package/dist/overlay/resolver.js +167 -0
  168. package/dist/overlay/resolver.test.d.ts +2 -0
  169. package/dist/overlay/resolver.test.d.ts.map +1 -0
  170. package/dist/overlay/resolver.test.js +179 -0
  171. package/dist/overlay/schema.d.ts +266 -0
  172. package/dist/overlay/schema.d.ts.map +1 -0
  173. package/dist/overlay/schema.js +71 -0
  174. package/dist/provenance.d.ts +2 -0
  175. package/dist/provenance.d.ts.map +1 -0
  176. package/dist/provenance.js +1 -0
  177. package/dist/schema-sourced-entries.d.ts +344 -0
  178. package/dist/schema-sourced-entries.d.ts.map +1 -0
  179. package/dist/schema-sourced-entries.js +75 -0
  180. package/dist/schema.d.ts +21 -0
  181. package/dist/schema.d.ts.map +1 -0
  182. package/dist/schema.js +20 -0
  183. package/dist/search/federate.d.ts +58 -0
  184. package/dist/search/federate.d.ts.map +1 -0
  185. package/dist/search/federate.js +103 -0
  186. package/dist/search/federate.test.d.ts +2 -0
  187. package/dist/search/federate.test.d.ts.map +1 -0
  188. package/dist/search/federate.test.js +146 -0
  189. package/dist/search/rerank.d.ts +77 -0
  190. package/dist/search/rerank.d.ts.map +1 -0
  191. package/dist/search/rerank.js +68 -0
  192. package/dist/search/rerank.test.d.ts +2 -0
  193. package/dist/search/rerank.test.d.ts.map +1 -0
  194. package/dist/search/rerank.test.js +60 -0
  195. package/dist/search/routes.d.ts +144 -0
  196. package/dist/search/routes.d.ts.map +1 -0
  197. package/dist/search/routes.js +288 -0
  198. package/dist/search/routes.test.d.ts +2 -0
  199. package/dist/search/routes.test.d.ts.map +1 -0
  200. package/dist/search/routes.test.js +322 -0
  201. package/dist/search/semantic.d.ts +63 -0
  202. package/dist/search/semantic.d.ts.map +1 -0
  203. package/dist/search/semantic.js +75 -0
  204. package/dist/search/semantic.test.d.ts +2 -0
  205. package/dist/search/semantic.test.d.ts.map +1 -0
  206. package/dist/search/semantic.test.js +143 -0
  207. package/dist/services/build-indexer-document.test.d.ts +2 -0
  208. package/dist/services/build-indexer-document.test.d.ts.map +1 -0
  209. package/dist/services/build-indexer-document.test.js +102 -0
  210. package/dist/services/content-service.d.ts +125 -0
  211. package/dist/services/content-service.d.ts.map +1 -0
  212. package/dist/services/content-service.js +139 -0
  213. package/dist/services/content-service.test.d.ts +2 -0
  214. package/dist/services/content-service.test.d.ts.map +1 -0
  215. package/dist/services/content-service.test.js +322 -0
  216. package/dist/services/indexer-service.d.ts +109 -0
  217. package/dist/services/indexer-service.d.ts.map +1 -0
  218. package/dist/services/indexer-service.js +123 -0
  219. package/dist/services/indexer-service.test.d.ts +2 -0
  220. package/dist/services/indexer-service.test.d.ts.map +1 -0
  221. package/dist/services/indexer-service.test.js +176 -0
  222. package/dist/services/overlay-service.d.ts +108 -0
  223. package/dist/services/overlay-service.d.ts.map +1 -0
  224. package/dist/services/overlay-service.js +211 -0
  225. package/dist/services/overlay-service.test.d.ts +2 -0
  226. package/dist/services/overlay-service.test.d.ts.map +1 -0
  227. package/dist/services/overlay-service.test.js +79 -0
  228. package/dist/services/snapshot-builder.test.d.ts +2 -0
  229. package/dist/services/snapshot-builder.test.d.ts.map +1 -0
  230. package/dist/services/snapshot-builder.test.js +93 -0
  231. package/dist/services/snapshot-service.d.ts +78 -0
  232. package/dist/services/snapshot-service.d.ts.map +1 -0
  233. package/dist/services/snapshot-service.js +165 -0
  234. package/dist/services/sourced-entry-service.d.ts +142 -0
  235. package/dist/services/sourced-entry-service.d.ts.map +1 -0
  236. package/dist/services/sourced-entry-service.js +203 -0
  237. package/dist/services/sourced-entry-service.test.d.ts +10 -0
  238. package/dist/services/sourced-entry-service.test.d.ts.map +1 -0
  239. package/dist/services/sourced-entry-service.test.js +66 -0
  240. package/dist/snapshot/schema.d.ts +362 -0
  241. package/dist/snapshot/schema.d.ts.map +1 -0
  242. package/dist/snapshot/schema.js +102 -0
  243. package/package.json +210 -0
@@ -0,0 +1,344 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { adapterCapabilitiesSchema, cancelRequestSchema, cancelResultSchema, catalogProjectionSchema, connectionStateSchema, discoveryCursorSchema, discoveryPageSchema, getContentRequestSchema, getContentResultSchema, getReservationRequestSchema, getReservationResultSchema, listReservationsPageSchema, listReservationsQuerySchema, liveResolveRequestSchema, liveResolveResultSchema, provenanceSchema, pushAvailabilityRequestSchema, pushAvailabilityResultSchema, pushBookingRequestSchema, pushBookingResultSchema, pushContentRequestSchema, pushContentResultSchema, reservationStatusSchema, reserveRequestSchema, reserveResultSchema, sourceAdapterContextSchema, sourceAdapterRequestScopeSchema, sourceAdapterSchema, } from "./schemas.js";
3
+ const typeChecks = [
4
+ true,
5
+ true,
6
+ true,
7
+ true,
8
+ true,
9
+ true,
10
+ true,
11
+ true,
12
+ true,
13
+ true,
14
+ true,
15
+ true,
16
+ true,
17
+ true,
18
+ true,
19
+ true,
20
+ true,
21
+ true,
22
+ true,
23
+ true,
24
+ true,
25
+ true,
26
+ true,
27
+ true,
28
+ true,
29
+ true,
30
+ true,
31
+ true,
32
+ ];
33
+ void typeChecks;
34
+ const capabilities = {
35
+ verticals: ["products"],
36
+ supportsLiveResolution: true,
37
+ supportsDriftDetection: true,
38
+ supportsBookingForwarding: true,
39
+ supportsReservationRetrieval: true,
40
+ supportsSyncCancellation: false,
41
+ postBookOperations: ["cancel", "status"],
42
+ cacheTtlSeconds: 300,
43
+ supportsContentFetch: true,
44
+ supportedContentLocales: ["en-GB", "ro-RO"],
45
+ ownsContentCache: true,
46
+ ownsAvailabilityCache: false,
47
+ holdReleaseGraceMs: 1000,
48
+ supportsBookingPush: true,
49
+ supportsAvailabilityPush: true,
50
+ supportsContentPush: true,
51
+ providerCapabilities: [
52
+ {
53
+ capability: "category_availability_counts",
54
+ support: "supported",
55
+ applies_to: ["cruises"],
56
+ evidence: "category inventory feed",
57
+ },
58
+ {
59
+ capability: "physical_inventory_units",
60
+ support: "unsupported",
61
+ applies_to: ["cruises"],
62
+ reason: "provider feed exposes category counts but not cabin numbers",
63
+ },
64
+ ],
65
+ };
66
+ const context = {
67
+ connection_id: "conn_123",
68
+ credentials: { apiKey: "secret" },
69
+ tenant_id: "tenant_1",
70
+ correlation_id: "corr_1",
71
+ };
72
+ const provenance = {
73
+ source_kind: "direct:tui",
74
+ source_provider: "tui",
75
+ source_connection_id: "conn_123",
76
+ source_ref: "TUI-123",
77
+ source_freshness: "sync",
78
+ last_sourced_at: new Date("2026-01-01T00:00:00Z"),
79
+ };
80
+ const projection = {
81
+ entity_module: "products",
82
+ entity_id: "prod_123",
83
+ provenance,
84
+ fields: { title: "Sample tour", country: "RO" },
85
+ };
86
+ const discoveryPage = {
87
+ projections: [projection],
88
+ next_cursor: "cursor_2",
89
+ };
90
+ const liveResolveRequest = {
91
+ ids: ["prod_123"],
92
+ scope: {
93
+ locale: "en-GB",
94
+ audience: "customer",
95
+ market: "GB",
96
+ currency: "GBP",
97
+ },
98
+ parameters: { departureId: "dep_1" },
99
+ };
100
+ const sourceAdapterRequestScope = liveResolveRequest.scope;
101
+ const liveResolveResult = {
102
+ values: {
103
+ prod_123: {
104
+ price: { amount: "100.00", currency: "GBP" },
105
+ remainingPax: 4,
106
+ },
107
+ },
108
+ failed: { prod_missing: "not_found" },
109
+ };
110
+ const getContentRequest = {
111
+ entity_module: "products",
112
+ entity_id: "prod_123",
113
+ locale: "ro-RO",
114
+ market: "RO",
115
+ currency: "RON",
116
+ };
117
+ const getContentResult = {
118
+ entity_module: "products",
119
+ entity_id: "prod_123",
120
+ source_ref: "TUI-123",
121
+ returned_locale: "en-GB",
122
+ machine_translated: false,
123
+ content: { product: { title: "Sample tour" } },
124
+ content_schema_version: "products/v1",
125
+ source_updated_at: new Date("2026-01-02T00:00:00Z"),
126
+ fresh_until: new Date("2026-01-03T00:00:00Z"),
127
+ etag: "etag-1",
128
+ };
129
+ const reserveRequest = {
130
+ entity_module: "products",
131
+ entity_id: "prod_123",
132
+ parameters: { departureId: "dep_1" },
133
+ party: { travelers: 2 },
134
+ payment_intent: { type: "hold" },
135
+ scope: sourceAdapterRequestScope,
136
+ idempotency_key: "reserve_123",
137
+ };
138
+ const reserveResult = {
139
+ upstream_ref: "booking_123",
140
+ status: "held",
141
+ upstream_payload: { holdToken: "hold_1" },
142
+ };
143
+ const cancelRequest = {
144
+ upstream_ref: "booking_123",
145
+ reason: "customer_request",
146
+ scope: sourceAdapterRequestScope,
147
+ idempotency_key: "cancel_123",
148
+ };
149
+ const cancelResult = {
150
+ status: "pending",
151
+ pending_channel: "partner portal",
152
+ };
153
+ const getReservationRequest = {
154
+ upstream_ref: "booking_123",
155
+ scope: sourceAdapterRequestScope,
156
+ };
157
+ const getReservationResult = {
158
+ upstream_ref: "booking_123",
159
+ status: "confirmed",
160
+ source_updated_at: new Date("2026-01-04T00:00:00Z"),
161
+ upstream_payload: { travelers: 2, supplierStatus: "OK" },
162
+ };
163
+ const listReservationsQuery = {
164
+ cursor: "cursor_1",
165
+ limit: 50,
166
+ status: ["held", "confirmed", "cancelling"],
167
+ updated_after: new Date("2026-01-01T00:00:00Z"),
168
+ scope: sourceAdapterRequestScope,
169
+ };
170
+ const listReservationsPage = {
171
+ reservations: [getReservationResult],
172
+ next_cursor: "cursor_2",
173
+ };
174
+ const pushBookingRequest = {
175
+ idempotencyKey: "idem_1",
176
+ bookingId: "book_1",
177
+ bookingItemId: "item_1",
178
+ externalProductId: "EXT-1",
179
+ externalRateId: "RATE-1",
180
+ externalCategoryId: "CAT-1",
181
+ channelId: "channel_1",
182
+ contractPolicy: { commission: 0.1 },
183
+ payload: { travelers: 2 },
184
+ };
185
+ const pushBookingResult = {
186
+ upstreamRef: "upstream_1",
187
+ externalReference: "CONF-1",
188
+ externalStatus: "confirmed",
189
+ upstreamPayload: { ok: true },
190
+ };
191
+ const pushAvailabilityRequest = {
192
+ channelId: "channel_1",
193
+ externalProductId: "EXT-1",
194
+ externalRateId: "RATE-1",
195
+ externalCategoryId: "CAT-1",
196
+ slotId: "slot_1",
197
+ productId: "prod_123",
198
+ optionId: "opt_1",
199
+ startsAt: new Date("2026-02-01T10:00:00Z"),
200
+ remainingPax: 8,
201
+ source: "booking",
202
+ };
203
+ const pushAvailabilityResult = {
204
+ externalStatus: "updated",
205
+ upstreamPayload: { ok: true },
206
+ };
207
+ const pushContentRequest = {
208
+ channelId: "channel_1",
209
+ externalProductId: "EXT-1",
210
+ productId: "prod_123",
211
+ contentHash: "hash_123",
212
+ content: { product: { title: "Sample tour" } },
213
+ contentSchemaVersion: "products/v1",
214
+ locale: "en-GB",
215
+ };
216
+ const pushContentResult = {
217
+ externalStatus: "updated",
218
+ upstreamPayload: { ok: true },
219
+ acknowledgedHash: "hash_123",
220
+ };
221
+ const sourceAdapter = {
222
+ kind: "direct:tui",
223
+ capabilities,
224
+ async connect() { },
225
+ async getState() {
226
+ return "active";
227
+ },
228
+ async discover() {
229
+ return discoveryPage;
230
+ },
231
+ async getContent(_ctx, request) {
232
+ return { ...getContentResult, entity_id: request.entity_id };
233
+ },
234
+ async getReservation(_ctx, request) {
235
+ return request.upstream_ref === getReservationResult.upstream_ref ? getReservationResult : null;
236
+ },
237
+ async listReservations() {
238
+ return listReservationsPage;
239
+ },
240
+ };
241
+ const roundTripCases = [
242
+ ["adapterCapabilitiesSchema", adapterCapabilitiesSchema, capabilities],
243
+ ["connectionStateSchema", connectionStateSchema, "active"],
244
+ ["sourceAdapterContextSchema", sourceAdapterContextSchema, context],
245
+ ["provenanceSchema", provenanceSchema, provenance],
246
+ ["catalogProjectionSchema", catalogProjectionSchema, projection],
247
+ ["discoveryCursorSchema", discoveryCursorSchema, "cursor_2"],
248
+ ["discoveryPageSchema", discoveryPageSchema, discoveryPage],
249
+ ["sourceAdapterRequestScopeSchema", sourceAdapterRequestScopeSchema, sourceAdapterRequestScope],
250
+ ["liveResolveRequestSchema", liveResolveRequestSchema, liveResolveRequest],
251
+ ["liveResolveResultSchema", liveResolveResultSchema, liveResolveResult],
252
+ ["getContentRequestSchema", getContentRequestSchema, getContentRequest],
253
+ ["getContentResultSchema", getContentResultSchema, getContentResult],
254
+ ["reserveRequestSchema", reserveRequestSchema, reserveRequest],
255
+ ["reserveResultSchema", reserveResultSchema, reserveResult],
256
+ ["cancelRequestSchema", cancelRequestSchema, cancelRequest],
257
+ ["cancelResultSchema", cancelResultSchema, cancelResult],
258
+ ["reservationStatusSchema", reservationStatusSchema, "cancelling"],
259
+ ["getReservationRequestSchema", getReservationRequestSchema, getReservationRequest],
260
+ ["getReservationResultSchema", getReservationResultSchema, getReservationResult],
261
+ ["listReservationsQuerySchema", listReservationsQuerySchema, listReservationsQuery],
262
+ ["listReservationsPageSchema", listReservationsPageSchema, listReservationsPage],
263
+ ["pushBookingRequestSchema", pushBookingRequestSchema, pushBookingRequest],
264
+ ["pushBookingResultSchema", pushBookingResultSchema, pushBookingResult],
265
+ ["pushAvailabilityRequestSchema", pushAvailabilityRequestSchema, pushAvailabilityRequest],
266
+ ["pushAvailabilityResultSchema", pushAvailabilityResultSchema, pushAvailabilityResult],
267
+ ["pushContentRequestSchema", pushContentRequestSchema, pushContentRequest],
268
+ ["pushContentResultSchema", pushContentResultSchema, pushContentResult],
269
+ ["sourceAdapterSchema", sourceAdapterSchema, sourceAdapter],
270
+ ];
271
+ const invalidCases = [
272
+ ["adapterCapabilitiesSchema", adapterCapabilitiesSchema, { ...capabilities, verticals: [123] }],
273
+ ["connectionStateSchema", connectionStateSchema, "reconnecting"],
274
+ ["sourceAdapterContextSchema", sourceAdapterContextSchema, { credentials: { apiKey: "secret" } }],
275
+ ["provenanceSchema", provenanceSchema, { ...provenance, source_freshness: "live" }],
276
+ ["catalogProjectionSchema", catalogProjectionSchema, { ...projection, entity_module: 123 }],
277
+ ["discoveryCursorSchema", discoveryCursorSchema, 123],
278
+ ["discoveryPageSchema", discoveryPageSchema, { projections: [{ fields: {} }] }],
279
+ [
280
+ "sourceAdapterRequestScopeSchema",
281
+ sourceAdapterRequestScopeSchema,
282
+ { ...sourceAdapterRequestScope, currency: "GB" },
283
+ ],
284
+ [
285
+ "liveResolveRequestSchema",
286
+ liveResolveRequestSchema,
287
+ { ...liveResolveRequest, scope: { ...liveResolveRequest.scope, locale: "not_locale" } },
288
+ ],
289
+ ["liveResolveResultSchema", liveResolveResultSchema, { values: {}, failed: { prod_1: "gone" } }],
290
+ ["getContentRequestSchema", getContentRequestSchema, { ...getContentRequest, currency: "EU" }],
291
+ [
292
+ "getContentResultSchema",
293
+ getContentResultSchema,
294
+ { ...getContentResult, content_schema_version: undefined },
295
+ ],
296
+ ["reserveRequestSchema", reserveRequestSchema, { ...reserveRequest, parameters: undefined }],
297
+ ["reserveResultSchema", reserveResultSchema, { ...reserveResult, status: "pending" }],
298
+ ["cancelRequestSchema", cancelRequestSchema, { reason: "customer_request" }],
299
+ ["cancelResultSchema", cancelResultSchema, { ...cancelResult, refund_currency: "GB" }],
300
+ ["reservationStatusSchema", reservationStatusSchema, "expired"],
301
+ [
302
+ "getReservationRequestSchema",
303
+ getReservationRequestSchema,
304
+ { ...getReservationRequest, upstream_ref: undefined },
305
+ ],
306
+ [
307
+ "getReservationResultSchema",
308
+ getReservationResultSchema,
309
+ { ...getReservationResult, status: "expired" },
310
+ ],
311
+ ["listReservationsQuerySchema", listReservationsQuerySchema, { limit: 0 }],
312
+ [
313
+ "listReservationsPageSchema",
314
+ listReservationsPageSchema,
315
+ { reservations: [{ ...getReservationResult, upstream_payload: "raw" }] },
316
+ ],
317
+ [
318
+ "pushBookingRequestSchema",
319
+ pushBookingRequestSchema,
320
+ { ...pushBookingRequest, payload: undefined },
321
+ ],
322
+ ["pushBookingResultSchema", pushBookingResultSchema, { externalStatus: "confirmed" }],
323
+ [
324
+ "pushAvailabilityRequestSchema",
325
+ pushAvailabilityRequestSchema,
326
+ { ...pushAvailabilityRequest, remainingPax: -1 },
327
+ ],
328
+ ["pushAvailabilityResultSchema", pushAvailabilityResultSchema, { upstreamPayload: "ok" }],
329
+ [
330
+ "pushContentRequestSchema",
331
+ pushContentRequestSchema,
332
+ { ...pushContentRequest, locale: "not_locale" },
333
+ ],
334
+ ["pushContentResultSchema", pushContentResultSchema, { acknowledgedHash: 123 }],
335
+ ["sourceAdapterSchema", sourceAdapterSchema, { ...sourceAdapter, discover: "yes" }],
336
+ ];
337
+ describe("catalog source-adapter schemas", () => {
338
+ it.each(roundTripCases)("parses %s fixtures without changing shape", (_name, schema, value) => {
339
+ expect(schema.parse(value)).toEqual(value);
340
+ });
341
+ it.each(invalidCases)("rejects invalid %s fixtures", (_name, schema, value) => {
342
+ expect(schema.safeParse(value).success).toBe(false);
343
+ });
344
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `bookEntity` — the second step in the booking engine lifecycle.
3
+ *
4
+ * Validates the supplied quote, calls `adapter.reserve`, and writes a
5
+ * `booking_catalog_snapshot` row with the frozen view, source pointer,
6
+ * and pricing breakdown. The snapshot is the audit record; the source
7
+ * pointer is what subsequent `cancelEntity` / status calls dispatch
8
+ * against.
9
+ *
10
+ * `bookingId` is plain text (no FK), so the engine accepts an existing
11
+ * id from the caller (when a `bookings` row already exists) or generates
12
+ * one on demand. This keeps the engine decoupled from `packages/bookings`
13
+ * — the engine and the bookings module evolve independently and are
14
+ * joined at read time via the shared `booking_id` text column.
15
+ */
16
+ import type { AnyDrizzleDb } from "@voyant-travel/db";
17
+ import type { SourceAdapterContext, SourceAdapterRequestScope } from "../adapter/contract.js";
18
+ import { type PricingBasis, type SelectBookingCatalogSnapshot } from "../snapshot/schema.js";
19
+ /**
20
+ * `BookingPaymentIntent` now lives in `@voyant-travel/catalog-contracts`
21
+ * (the shared server/client seam, alongside `bookRequestV1` whose
22
+ * `paymentIntent` field mirrors it). Re-exported here to keep existing
23
+ * `@voyant-travel/catalog/booking-engine` import paths stable.
24
+ */
25
+ import type { BookingPaymentIntent } from "./contracts.js";
26
+ import type { OwnedBookingHandlerRegistry } from "./owned-handler.js";
27
+ import type { SourceAdapterRegistry } from "./registry.js";
28
+ import type { SnapshotContentCapturer } from "./snapshot-content.js";
29
+ export type { BookingPaymentIntent };
30
+ export interface BookEntityRequest {
31
+ /** Quote previously returned from `quoteEntity`. */
32
+ quoteId: string;
33
+ /**
34
+ * Existing or newly-created booking shell id. Generated when omitted,
35
+ * using the `bookings` typeid prefix so the value is shape-compatible
36
+ * with `packages/bookings`.
37
+ */
38
+ bookingId?: string;
39
+ /** Customer / passenger payload echoed to the adapter's reserve call. */
40
+ party?: Record<string, unknown>;
41
+ /** Defaults to `{ type: "hold" }` — see `BookingPaymentIntent`. */
42
+ paymentIntent?: BookingPaymentIntent;
43
+ /** Vertical-specific parameters passed to the adapter. */
44
+ parameters?: Record<string, unknown>;
45
+ /**
46
+ * Caller-supplied idempotency key. When set, a duplicate
47
+ * `bookEntity` call within the snapshot table's lifetime returns
48
+ * the prior booking instead of creating a new one. Length 8–128.
49
+ * Per booking-journey-architecture §12.6.
50
+ */
51
+ idempotencyKey?: string;
52
+ adapterContext: SourceAdapterContext;
53
+ /**
54
+ * Locale / market / currency scope for snapshot content capture per
55
+ * sourced-content §5.1. Required when `deps.captureSnapshotContent`
56
+ * is wired — the engine refreshes content from the adapter at commit
57
+ * time using this scope and embeds the result as `content_capture`
58
+ * in `frozen_payload`. When the deps callback isn't set, this field
59
+ * is ignored and snapshot behavior is unchanged.
60
+ */
61
+ contentScope?: {
62
+ locale: string;
63
+ market?: string;
64
+ currency?: string;
65
+ };
66
+ /**
67
+ * Per-request supplier scope forwarded to `SourceAdapter.reserve`.
68
+ * Kept separate from `contentScope`: this controls upstream write
69
+ * semantics, while `contentScope` controls snapshot-content capture.
70
+ */
71
+ adapterScope?: SourceAdapterRequestScope;
72
+ }
73
+ export interface BookEntityResult {
74
+ bookingId: string;
75
+ orderRef: string;
76
+ status: "held" | "confirmed" | "ticketed" | "failed";
77
+ snapshotId: string;
78
+ pricing?: PricingBasis;
79
+ upstreamPayload?: Record<string, unknown>;
80
+ }
81
+ export interface BookEntityDeps {
82
+ registry: SourceAdapterRegistry;
83
+ /**
84
+ * Owned-arm dispatch — when set and the quote's source kind is
85
+ * `"owned"`, the engine commits via a handler keyed by
86
+ * `entity_module` instead of the SourceAdapterRegistry. Per
87
+ * booking-journey-architecture §6.
88
+ */
89
+ ownedHandlers?: OwnedBookingHandlerRegistry;
90
+ /**
91
+ * Optional snapshot content capture orchestrator (sourced-content
92
+ * §5.1). When set, called after `adapter.reserve` succeeds. Returns a
93
+ * `SnapshotContentCapture` envelope embedded in `frozen_payload` so
94
+ * audit can later distinguish a fresh capture from a cache fallback.
95
+ *
96
+ * Throws `SnapshotContentUnavailableError` when neither a fresh
97
+ * adapter fetch nor a cache fallback can produce content; the engine
98
+ * propagates that error and aborts the commit. When the entity is
99
+ * owned (no sourced-entry row), the orchestrator should return null
100
+ * and the engine skips the capture.
101
+ *
102
+ * Implementations live in templates: each template composes per-
103
+ * vertical content services into one capturer and threads it through
104
+ * deps.
105
+ */
106
+ captureSnapshotContent?: SnapshotContentCapturer;
107
+ }
108
+ /**
109
+ * Book the row referenced by `quoteId`. End-to-end: validate quote,
110
+ * dispatch `adapter.reserve`, capture snapshot, mark quote consumed.
111
+ *
112
+ * Throws:
113
+ * - `BookingEngineError(QUOTE_NOT_FOUND)` if the quote doesn't exist.
114
+ * - `QuoteExpiredError` if the quote's `expires_at` has passed.
115
+ * - `QuoteMismatchError` if a future caller tries to book a different
116
+ * entity than the quote was issued for (defensive — the engine
117
+ * re-reads quote.entity_*).
118
+ * - `NoAdapterRegisteredError` if the registry has no adapter for
119
+ * the quote's `source_kind`.
120
+ * - `ReserveFailedError` when the adapter returns `status: "failed"`.
121
+ */
122
+ export declare function bookEntity(db: AnyDrizzleDb, deps: BookEntityDeps, request: BookEntityRequest): Promise<BookEntityResult>;
123
+ export type { SelectBookingCatalogSnapshot };
124
+ //# sourceMappingURL=book.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"book.d.ts","sourceRoot":"","sources":["../../src/booking-engine/book.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIrD,OAAO,KAAK,EAEV,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,4BAA4B,EAClC,MAAM,uBAAuB,CAAA;AAC9B;;;;;GAKG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAQ1D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAA;AAErE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,KAAK,EAA0B,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE5F,YAAY,EAAE,oBAAoB,EAAE,CAAA;AAMpC,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAE/B,mEAAmE;IACnE,aAAa,CAAC,EAAE,oBAAoB,CAAA;IAEpC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAEpC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB,cAAc,EAAE,oBAAoB,CAAA;IAEpC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE;QACb,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IAED;;;;OAIG;IACH,YAAY,CAAC,EAAE,yBAAyB,CAAA;CACzC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAA;IACpD,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,qBAAqB,CAAA;IAC/B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,2BAA2B,CAAA;IAC3C;;;;;;;;;;;;;;;OAeG;IACH,sBAAsB,CAAC,EAAE,uBAAuB,CAAA;CACjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAqL3B;AAoID,YAAY,EAAE,4BAA4B,EAAE,CAAA"}