@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,390 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { availabilityProjectionSchema, cancelRequestSchema, getReservationRequestSchema, getReservationResultSchema, listReservationsPageSchema, providerPromotionSchema, reservationStatusSchema, reserveRequestSchema, } from "./schemas.js";
3
+ describe("AdapterCapabilities — content fetch declaration", () => {
4
+ it("accepts adapters that omit supportsContentFetch (backward compatible)", () => {
5
+ const cap = {
6
+ verticals: ["products"],
7
+ supportsLiveResolution: true,
8
+ supportsDriftDetection: false,
9
+ supportsBookingForwarding: false,
10
+ postBookOperations: [],
11
+ };
12
+ expect(cap.supportsContentFetch).toBeUndefined();
13
+ expect(cap.supportedContentLocales).toBeUndefined();
14
+ expect(cap.ownsContentCache).toBeUndefined();
15
+ expect(cap.ownsAvailabilityCache).toBeUndefined();
16
+ });
17
+ it("records content and availability cache ownership when declared", () => {
18
+ const cap = {
19
+ verticals: ["products"],
20
+ supportsLiveResolution: true,
21
+ supportsDriftDetection: false,
22
+ supportsBookingForwarding: true,
23
+ supportsReservationRetrieval: true,
24
+ postBookOperations: ["cancel"],
25
+ supportsContentFetch: true,
26
+ supportedContentLocales: ["en-GB", "ro-RO", "de-DE"],
27
+ ownsContentCache: true,
28
+ ownsAvailabilityCache: true,
29
+ };
30
+ expect(cap.supportsContentFetch).toBe(true);
31
+ expect(cap.supportedContentLocales).toEqual(["en-GB", "ro-RO", "de-DE"]);
32
+ expect(cap.ownsContentCache).toBe(true);
33
+ expect(cap.ownsAvailabilityCache).toBe(true);
34
+ expect(cap.supportsReservationRetrieval).toBe(true);
35
+ });
36
+ });
37
+ describe("AdapterCapabilities — provider capability declarations", () => {
38
+ it("can state supported and explicitly unsupported provider features", () => {
39
+ const cap = {
40
+ verticals: ["cruises"],
41
+ supportsLiveResolution: true,
42
+ supportsDriftDetection: true,
43
+ supportsBookingForwarding: true,
44
+ postBookOperations: ["cancel", "status"],
45
+ providerCapabilities: [
46
+ {
47
+ capability: "category_availability_counts",
48
+ support: "supported",
49
+ applies_to: ["sailings", "cabin_categories"],
50
+ },
51
+ {
52
+ capability: "physical_inventory_units",
53
+ support: "unsupported",
54
+ applies_to: ["cabins"],
55
+ reason: "current feed exposes category inventory counts but not cabin numbers",
56
+ },
57
+ {
58
+ capability: "offer_applicability_evaluation",
59
+ support: "unknown",
60
+ reason: "customer/session eligibility is provider-side",
61
+ },
62
+ ],
63
+ };
64
+ expect(cap.providerCapabilities?.map((item) => [item.capability, item.support])).toEqual([
65
+ ["category_availability_counts", "supported"],
66
+ ["physical_inventory_units", "unsupported"],
67
+ ["offer_applicability_evaluation", "unknown"],
68
+ ]);
69
+ });
70
+ });
71
+ describe("ProviderPromotion / AvailabilityProjection contract shape", () => {
72
+ it("represents non-evaluable loyalty offers with normalized display media", () => {
73
+ const promotion = {
74
+ source_offer_id: "UNI-PAST-GUEST",
75
+ provider: "uniworld",
76
+ display: {
77
+ display_name: "Past Guest Savings",
78
+ subtitle: "Exclusive loyalty pricing",
79
+ media: [
80
+ {
81
+ kind: "primary",
82
+ url: "https://example.com/offers/past-guest.jpg",
83
+ alt_text: "River ship suite",
84
+ },
85
+ ],
86
+ featured: true,
87
+ },
88
+ applicability: {
89
+ evaluation: "not_evaluable_locally",
90
+ price_effect: "price_affecting",
91
+ constraints: [
92
+ {
93
+ kind: "loyalty",
94
+ resolution: "customer_context_required",
95
+ values: ["past_guest"],
96
+ requires_customer_context: true,
97
+ },
98
+ {
99
+ kind: "fare_code",
100
+ resolution: "not_evaluable_locally",
101
+ values: ["PG"],
102
+ },
103
+ ],
104
+ },
105
+ stacking: "unknown",
106
+ raw_payload: { offerCode: "UNI-PAST-GUEST" },
107
+ };
108
+ expect(providerPromotionSchema.parse(promotion)).toEqual(promotion);
109
+ expect(promotion.applicability.evaluation).toBe("not_evaluable_locally");
110
+ expect(promotion.display?.media?.[0]?.kind).toBe("primary");
111
+ });
112
+ it("distinguishes category-count availability from exact physical units", () => {
113
+ const projection = {
114
+ row_kind: "category",
115
+ row_id: "suite-deluxe",
116
+ available_units: 3,
117
+ precision: "category_count",
118
+ status: "low",
119
+ low_availability_threshold: 4,
120
+ badge: { kind: "low_availability", label: "Only 3 left" },
121
+ sort_priority: 20,
122
+ };
123
+ expect(availabilityProjectionSchema.parse(projection)).toEqual(projection);
124
+ expect(projection.precision).toBe("category_count");
125
+ expect(projection.status).toBe("low");
126
+ });
127
+ });
128
+ describe("GetContentRequest / GetContentResult shape", () => {
129
+ it("requires entity_module, entity_id, and locale", () => {
130
+ const req = {
131
+ entity_module: "products",
132
+ entity_id: "prod_abc",
133
+ locale: "ro-RO",
134
+ };
135
+ expect(req.locale).toBe("ro-RO");
136
+ expect(req.market).toBeUndefined();
137
+ expect(req.currency).toBeUndefined();
138
+ });
139
+ it("carries optional market + currency scope axes", () => {
140
+ const req = {
141
+ entity_module: "cruises",
142
+ entity_id: "crus_xyz",
143
+ locale: "de-DE",
144
+ market: "DE",
145
+ currency: "EUR",
146
+ };
147
+ expect(req.market).toBe("DE");
148
+ expect(req.currency).toBe("EUR");
149
+ });
150
+ it("returned_locale may differ from request.locale (upstream fallback)", () => {
151
+ const result = {
152
+ entity_module: "products",
153
+ entity_id: "prod_abc",
154
+ source_ref: "TUI-123",
155
+ // Asked for ro-RO, upstream had no Romanian → served en-GB.
156
+ returned_locale: "en-GB",
157
+ content: { product: { name: "Sample" } },
158
+ content_schema_version: "products/v1",
159
+ };
160
+ expect(result.returned_locale).toBe("en-GB");
161
+ expect(result.machine_translated).toBeUndefined();
162
+ });
163
+ it("flags machine_translated when upstream marks the payload", () => {
164
+ const result = {
165
+ entity_module: "products",
166
+ entity_id: "prod_abc",
167
+ source_ref: "TUI-123",
168
+ returned_locale: "ro-RO",
169
+ machine_translated: true,
170
+ content: { product: { name: "Mostră" } },
171
+ content_schema_version: "products/v1",
172
+ };
173
+ expect(result.machine_translated).toBe(true);
174
+ });
175
+ });
176
+ describe("SourceAdapter — getContent capability gating", () => {
177
+ it("typechecks without getContent (capability not declared)", () => {
178
+ const adapter = {
179
+ kind: "thin",
180
+ capabilities: {
181
+ verticals: ["products"],
182
+ supportsLiveResolution: false,
183
+ supportsDriftDetection: false,
184
+ supportsBookingForwarding: false,
185
+ postBookOperations: [],
186
+ supportsContentFetch: false,
187
+ },
188
+ async connect() { },
189
+ async pause() { },
190
+ async disconnect() { },
191
+ async getState() {
192
+ return "active";
193
+ },
194
+ async discover() {
195
+ return { projections: [], next_cursor: undefined };
196
+ },
197
+ };
198
+ expect(adapter.getContent).toBeUndefined();
199
+ expect(adapter.capabilities.supportsContentFetch).toBe(false);
200
+ });
201
+ it("typechecks with getContent implemented", async () => {
202
+ const adapter = {
203
+ kind: "rich",
204
+ capabilities: {
205
+ verticals: ["products"],
206
+ supportsLiveResolution: false,
207
+ supportsDriftDetection: false,
208
+ supportsBookingForwarding: false,
209
+ postBookOperations: [],
210
+ supportsContentFetch: true,
211
+ supportedContentLocales: ["en-GB"],
212
+ },
213
+ async connect() { },
214
+ async pause() { },
215
+ async disconnect() { },
216
+ async getState() {
217
+ return "active";
218
+ },
219
+ async discover() {
220
+ return { projections: [], next_cursor: undefined };
221
+ },
222
+ async getContent(_ctx, request) {
223
+ return {
224
+ entity_module: request.entity_module,
225
+ entity_id: request.entity_id,
226
+ source_ref: "ref-1",
227
+ returned_locale: "en-GB",
228
+ content: { product: { name: "Test" } },
229
+ content_schema_version: "products/v1",
230
+ };
231
+ },
232
+ };
233
+ expect(adapter.getContent).toBeDefined();
234
+ const result = await adapter.getContent({ connection_id: "conn_1" }, { entity_module: "products", entity_id: "prod_abc", locale: "ro-RO" });
235
+ expect(result.returned_locale).toBe("en-GB");
236
+ expect(result.content_schema_version).toBe("products/v1");
237
+ });
238
+ });
239
+ describe("SourceAdapter — reservation retrieval capability gating", () => {
240
+ it("typechecks without reservation read methods when capability is false", () => {
241
+ const adapter = {
242
+ kind: "booking-write-only",
243
+ capabilities: {
244
+ verticals: ["products"],
245
+ supportsLiveResolution: false,
246
+ supportsDriftDetection: false,
247
+ supportsBookingForwarding: true,
248
+ supportsReservationRetrieval: false,
249
+ postBookOperations: ["cancel"],
250
+ },
251
+ async reserve() {
252
+ return { upstream_ref: "booking_abc", status: "held" };
253
+ },
254
+ async cancel() {
255
+ return { status: "cancelled" };
256
+ },
257
+ };
258
+ expect(adapter.getReservation).toBeUndefined();
259
+ expect(adapter.listReservations).toBeUndefined();
260
+ expect(adapter.capabilities.supportsReservationRetrieval).toBe(false);
261
+ });
262
+ it("typechecks with getReservation and listReservations implemented", async () => {
263
+ const reservation = {
264
+ upstream_ref: "booking_abc",
265
+ status: "confirmed",
266
+ source_updated_at: new Date("2026-01-01T00:00:00Z"),
267
+ upstream_payload: { travelers: 2 },
268
+ };
269
+ const page = {
270
+ reservations: [reservation],
271
+ next_cursor: "cursor_2",
272
+ };
273
+ const adapter = {
274
+ kind: "booking-readable",
275
+ capabilities: {
276
+ verticals: ["products"],
277
+ supportsLiveResolution: false,
278
+ supportsDriftDetection: true,
279
+ supportsBookingForwarding: true,
280
+ supportsReservationRetrieval: true,
281
+ postBookOperations: ["cancel", "status"],
282
+ },
283
+ async getReservation(_ctx, request) {
284
+ return request.upstream_ref === reservation.upstream_ref ? reservation : null;
285
+ },
286
+ async listReservations() {
287
+ return page;
288
+ },
289
+ };
290
+ const request = {
291
+ upstream_ref: "missing",
292
+ scope: { locale: "en-GB", audience: "operator", market: "GB", currency: "GBP" },
293
+ };
294
+ expect(getReservationRequestSchema.parse(request)).toEqual(request);
295
+ await expect(adapter.getReservation({ connection_id: "conn_1" }, request)).resolves.toBeNull();
296
+ await expect(adapter.getReservation({ connection_id: "conn_1" }, { upstream_ref: "booking_abc" })).resolves.toEqual(reservation);
297
+ await expect(adapter.listReservations({ connection_id: "conn_1" }, { status: ["confirmed"] })).resolves.toEqual(page);
298
+ expect(getReservationResultSchema.parse(reservation)).toEqual(reservation);
299
+ expect(listReservationsPageSchema.parse(page)).toEqual(page);
300
+ });
301
+ it("accepts every reservation status and rejects unknown statuses", () => {
302
+ const statuses = [
303
+ "held",
304
+ "confirmed",
305
+ "ticketed",
306
+ "failed",
307
+ "cancelled",
308
+ "pending",
309
+ "refused",
310
+ "cancelling",
311
+ ];
312
+ expect(statuses.map((status) => reservationStatusSchema.parse(status))).toEqual(statuses);
313
+ expect(reservationStatusSchema.safeParse("expired").success).toBe(false);
314
+ });
315
+ });
316
+ describe("ReserveRequest / CancelRequest scoped write shape", () => {
317
+ it("round-trips reserve requests with request scope and idempotency key", () => {
318
+ const request = {
319
+ entity_module: "products",
320
+ entity_id: "prod_abc",
321
+ parameters: { departureId: "dep_1" },
322
+ party: { travelers: 2 },
323
+ scope: {
324
+ locale: "en-GB",
325
+ audience: "customer",
326
+ market: "GB",
327
+ currency: "GBP",
328
+ },
329
+ idempotency_key: "reserve_abc",
330
+ };
331
+ expect(reserveRequestSchema.parse(request)).toEqual(request);
332
+ });
333
+ it("round-trips cancel requests with request scope and idempotency key", () => {
334
+ const request = {
335
+ upstream_ref: "booking_abc",
336
+ reason: "customer_request",
337
+ scope: {
338
+ locale: "en-GB",
339
+ audience: "customer",
340
+ market: "GB",
341
+ currency: "GBP",
342
+ },
343
+ idempotency_key: "cancel_abc",
344
+ };
345
+ expect(cancelRequestSchema.parse(request)).toEqual(request);
346
+ });
347
+ });
348
+ describe("SourceAdapter — cancellation capability declaration", () => {
349
+ it("typechecks sync cancellation adapters that return terminal statuses", async () => {
350
+ const adapter = {
351
+ kind: "sync-cancel",
352
+ capabilities: {
353
+ verticals: ["products"],
354
+ supportsLiveResolution: false,
355
+ supportsDriftDetection: false,
356
+ supportsBookingForwarding: true,
357
+ supportsSyncCancellation: true,
358
+ postBookOperations: ["cancel"],
359
+ },
360
+ async cancel() {
361
+ return { status: "cancelled", refund_amount: 100, refund_currency: "GBP" };
362
+ },
363
+ };
364
+ await expect(adapter.cancel({ connection_id: "conn_1" }, { upstream_ref: "up_1" })).resolves.toEqual({
365
+ status: "cancelled",
366
+ refund_amount: 100,
367
+ refund_currency: "GBP",
368
+ });
369
+ });
370
+ it("typechecks async cancellation adapters that return pending", async () => {
371
+ const adapter = {
372
+ kind: "async-cancel",
373
+ capabilities: {
374
+ verticals: ["products"],
375
+ supportsLiveResolution: false,
376
+ supportsDriftDetection: true,
377
+ supportsBookingForwarding: true,
378
+ supportsSyncCancellation: false,
379
+ postBookOperations: ["cancel"],
380
+ },
381
+ async cancel() {
382
+ return { status: "pending", pending_channel: "partner portal" };
383
+ },
384
+ };
385
+ await expect(adapter.cancel({ connection_id: "conn_1" }, { upstream_ref: "up_1" })).resolves.toEqual({
386
+ status: "pending",
387
+ pending_channel: "partner portal",
388
+ });
389
+ });
390
+ });
@@ -0,0 +1,2 @@
1
+ export * from "@voyant-travel/catalog-contracts/adapter/provider-contracts";
2
+ //# sourceMappingURL=provider-contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-contracts.d.ts","sourceRoot":"","sources":["../../src/adapter/provider-contracts.ts"],"names":[],"mappings":"AAAA,cAAc,6DAA6D,CAAA"}
@@ -0,0 +1 @@
1
+ export * from "@voyant-travel/catalog-contracts/adapter/provider-contracts";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provider-contracts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-contracts.test.d.ts","sourceRoot":"","sources":["../../src/adapter/provider-contracts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,206 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { availabilityBadgeKindSchema, availabilityBadgeSchema, availabilityProjectionSchema, availabilityRowKindSchema, availabilityStatusSchema, availabilityUnitPrecisionSchema, capabilitySupportSchema, promotionApplicabilityConstraintKindSchema, promotionApplicabilityConstraintSchema, promotionApplicabilityEvaluationSchema, promotionApplicabilityResolutionSchema, promotionApplicabilitySchema, promotionDisplayFieldsSchema, promotionMediaAssetSchema, promotionMediaKindSchema, promotionPriceEffectSchema, promotionStackingSemanticsSchema, providerCapabilityDeclarationSchema, providerCapabilityKeySchema, providerPromotionSchema, } 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
+ ];
25
+ void typeChecks;
26
+ const providerCapabilityDeclaration = {
27
+ capability: "offer_applicability_evaluation",
28
+ support: "unknown",
29
+ reason: "loyalty eligibility depends on shopper session context",
30
+ };
31
+ const promotionApplicabilityConstraint = {
32
+ kind: "fare_code",
33
+ resolution: "not_evaluable_locally",
34
+ values: ["PAST-GUEST"],
35
+ requires_customer_context: true,
36
+ reason: "provider does not link every sale row to a specific offer row",
37
+ };
38
+ const promotionApplicability = {
39
+ evaluation: "not_evaluable_locally",
40
+ price_effect: "informational_only",
41
+ constraints: [promotionApplicabilityConstraint],
42
+ };
43
+ const promotionMediaAsset = {
44
+ kind: "hero",
45
+ url: "https://example.com/offers/hero.jpg",
46
+ alt_text: "River cruise promotion",
47
+ width: 1600,
48
+ height: 900,
49
+ };
50
+ const promotionDisplayFields = {
51
+ display_name: "Past Guest Savings",
52
+ subtitle: "Exclusive loyalty pricing",
53
+ rich_description: "<p>Save on selected departures.</p>",
54
+ terms_and_conditions: "Eligibility required.",
55
+ media: [promotionMediaAsset],
56
+ featured: true,
57
+ display_priority: 10,
58
+ };
59
+ const providerPromotion = {
60
+ source_offer_id: "offer_123",
61
+ source_offer_ref: "PG-2026",
62
+ provider: "uniworld",
63
+ display: promotionDisplayFields,
64
+ applicability: promotionApplicability,
65
+ stacking: "unknown",
66
+ raw_payload: { offerCode: "PG-2026" },
67
+ };
68
+ const availabilityBadge = {
69
+ kind: "low_availability",
70
+ label: "Only 3 left",
71
+ };
72
+ const availabilityProjection = {
73
+ row_kind: "category",
74
+ row_id: "suite-deluxe",
75
+ available_units: 3,
76
+ precision: "category_count",
77
+ status: "low",
78
+ low_availability_threshold: 4,
79
+ badge: availabilityBadge,
80
+ sort_priority: 20,
81
+ source_updated_at: "2026-05-30T00:00:00Z",
82
+ };
83
+ const roundTripCases = [
84
+ ["capabilitySupportSchema", capabilitySupportSchema, "unsupported"],
85
+ [
86
+ "providerCapabilityKeySchema",
87
+ providerCapabilityKeySchema,
88
+ "physical_inventory_units",
89
+ ],
90
+ [
91
+ "providerCapabilityDeclarationSchema",
92
+ providerCapabilityDeclarationSchema,
93
+ providerCapabilityDeclaration,
94
+ ],
95
+ [
96
+ "promotionApplicabilityEvaluationSchema",
97
+ promotionApplicabilityEvaluationSchema,
98
+ "not_evaluable_locally",
99
+ ],
100
+ [
101
+ "promotionPriceEffectSchema",
102
+ promotionPriceEffectSchema,
103
+ "informational_only",
104
+ ],
105
+ [
106
+ "promotionApplicabilityConstraintKindSchema",
107
+ promotionApplicabilityConstraintKindSchema,
108
+ "customer_session",
109
+ ],
110
+ [
111
+ "promotionApplicabilityResolutionSchema",
112
+ promotionApplicabilityResolutionSchema,
113
+ "customer_context_required",
114
+ ],
115
+ [
116
+ "promotionApplicabilityConstraintSchema",
117
+ promotionApplicabilityConstraintSchema,
118
+ promotionApplicabilityConstraint,
119
+ ],
120
+ ["promotionApplicabilitySchema", promotionApplicabilitySchema, promotionApplicability],
121
+ ["promotionMediaKindSchema", promotionMediaKindSchema, "primary"],
122
+ ["promotionMediaAssetSchema", promotionMediaAssetSchema, promotionMediaAsset],
123
+ ["promotionDisplayFieldsSchema", promotionDisplayFieldsSchema, promotionDisplayFields],
124
+ [
125
+ "promotionStackingSemanticsSchema",
126
+ promotionStackingSemanticsSchema,
127
+ "provider_defined",
128
+ ],
129
+ ["providerPromotionSchema", providerPromotionSchema, providerPromotion],
130
+ ["availabilityRowKindSchema", availabilityRowKindSchema, "sailing"],
131
+ [
132
+ "availabilityUnitPrecisionSchema",
133
+ availabilityUnitPrecisionSchema,
134
+ "category_count",
135
+ ],
136
+ ["availabilityStatusSchema", availabilityStatusSchema, "sold_out"],
137
+ [
138
+ "availabilityBadgeKindSchema",
139
+ availabilityBadgeKindSchema,
140
+ "low_availability",
141
+ ],
142
+ ["availabilityBadgeSchema", availabilityBadgeSchema, availabilityBadge],
143
+ ["availabilityProjectionSchema", availabilityProjectionSchema, availabilityProjection],
144
+ ];
145
+ const invalidCases = [
146
+ ["capabilitySupportSchema", capabilitySupportSchema, "partial"],
147
+ ["providerCapabilityKeySchema", providerCapabilityKeySchema, "deck_plans"],
148
+ [
149
+ "providerCapabilityDeclarationSchema",
150
+ providerCapabilityDeclarationSchema,
151
+ { ...providerCapabilityDeclaration, support: "partial" },
152
+ ],
153
+ ["promotionApplicabilityEvaluationSchema", promotionApplicabilityEvaluationSchema, "local"],
154
+ ["promotionPriceEffectSchema", promotionPriceEffectSchema, "discount"],
155
+ [
156
+ "promotionApplicabilityConstraintKindSchema",
157
+ promotionApplicabilityConstraintKindSchema,
158
+ "tier",
159
+ ],
160
+ ["promotionApplicabilityResolutionSchema", promotionApplicabilityResolutionSchema, "maybe"],
161
+ [
162
+ "promotionApplicabilityConstraintSchema",
163
+ promotionApplicabilityConstraintSchema,
164
+ { ...promotionApplicabilityConstraint, values: [123] },
165
+ ],
166
+ [
167
+ "promotionApplicabilitySchema",
168
+ promotionApplicabilitySchema,
169
+ { ...promotionApplicability, constraints: undefined },
170
+ ],
171
+ ["promotionMediaKindSchema", promotionMediaKindSchema, "banner"],
172
+ [
173
+ "promotionMediaAssetSchema",
174
+ promotionMediaAssetSchema,
175
+ { ...promotionMediaAsset, url: "image" },
176
+ ],
177
+ [
178
+ "promotionDisplayFieldsSchema",
179
+ promotionDisplayFieldsSchema,
180
+ { ...promotionDisplayFields, media: [{ ...promotionMediaAsset, width: 0 }] },
181
+ ],
182
+ ["promotionStackingSemanticsSchema", promotionStackingSemanticsSchema, "combinable"],
183
+ [
184
+ "providerPromotionSchema",
185
+ providerPromotionSchema,
186
+ { ...providerPromotion, source_offer_id: 123 },
187
+ ],
188
+ ["availabilityRowKindSchema", availabilityRowKindSchema, "cabin"],
189
+ ["availabilityUnitPrecisionSchema", availabilityUnitPrecisionSchema, "estimated"],
190
+ ["availabilityStatusSchema", availabilityStatusSchema, "limited"],
191
+ ["availabilityBadgeKindSchema", availabilityBadgeKindSchema, "few_left"],
192
+ ["availabilityBadgeSchema", availabilityBadgeSchema, { kind: "few_left" }],
193
+ [
194
+ "availabilityProjectionSchema",
195
+ availabilityProjectionSchema,
196
+ { ...availabilityProjection, available_units: -1 },
197
+ ],
198
+ ];
199
+ describe("provider catalog contract schemas", () => {
200
+ it.each(roundTripCases)("parses %s fixtures without changing shape", (_name, schema, value) => {
201
+ expect(schema.parse(value)).toEqual(value);
202
+ });
203
+ it.each(invalidCases)("rejects invalid %s fixtures", (_name, schema, value) => {
204
+ expect(schema.safeParse(value).success).toBe(false);
205
+ });
206
+ });
@@ -0,0 +1,2 @@
1
+ export * from "@voyant-travel/catalog-contracts/adapter/schemas";
2
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/adapter/schemas.ts"],"names":[],"mappings":"AAAA,cAAc,kDAAkD,CAAA"}
@@ -0,0 +1 @@
1
+ export * from "@voyant-travel/catalog-contracts/adapter/schemas";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schemas.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.test.d.ts","sourceRoot":"","sources":["../../src/adapter/schemas.test.ts"],"names":[],"mappings":""}