@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,132 @@
1
+ /**
2
+ * `checkoutFinalize` workflow — runs on `payment.completed` for
3
+ * bookings created through the storefront's checkout-start path.
4
+ *
5
+ * Steps:
6
+ * 1. transition_to_confirmed — flip the booking from
7
+ * `awaiting_payment` to `confirmed`, stamping `paidAt`. This
8
+ * emits `booking.confirmed` which fans out to:
9
+ * - legal's auto-generate-contract subscriber (if wired)
10
+ * - finance's auto-generate-invoice subscriber (Phase 5)
11
+ * 2. issue_invoice — explicit fallback when finance auto-generation
12
+ * isn't wired. Idempotent — checks if an invoice already exists.
13
+ *
14
+ * Compensation: if `issue_invoice` fails after the booking is
15
+ * already confirmed, we don't roll back to `awaiting_payment` — the
16
+ * payment was real and the booking is real. Instead the workflow
17
+ * leaves the booking in `confirmed` and rethrows so the workflow
18
+ * runs view surfaces the failed step for ops.
19
+ *
20
+ * This is a thin adapter on top of `createWorkflow` from
21
+ * `@voyant-travel/core/workflows` — no durability, no event-await; the
22
+ * caller subscribes to `payment.completed` and runs the workflow
23
+ * inline. Phase 6 of the storefront-checkout-flow plan elaborates
24
+ * with workflow-runs surfacing.
25
+ */
26
+ import { createWorkflow, step } from "@voyant-travel/core/workflows";
27
+ async function runStep(name, recorder, fn) {
28
+ await recorder?.startStep(name);
29
+ try {
30
+ const result = await fn();
31
+ await recorder?.completeStep(name, asRecord(result));
32
+ return result;
33
+ }
34
+ catch (err) {
35
+ await recorder?.failStep(name, err);
36
+ throw err;
37
+ }
38
+ }
39
+ function asRecord(value) {
40
+ if (value && typeof value === "object" && !Array.isArray(value)) {
41
+ return value;
42
+ }
43
+ return null;
44
+ }
45
+ export const checkoutFinalizeWorkflow = createWorkflow("checkout-finalize", [
46
+ step("transition_to_confirmed").run(async (input, ctx) => {
47
+ const deps = ctx.results.__deps;
48
+ if (!deps)
49
+ throw new Error("checkout-finalize: deps not seeded into context");
50
+ await runStep("transition_to_confirmed", deps.recorder, () => deps.confirmBooking(input.bookingId));
51
+ }),
52
+ step("issue_invoice").run(async (input, ctx) => {
53
+ const deps = ctx.results.__deps;
54
+ if (!deps)
55
+ throw new Error("checkout-finalize: deps not seeded into context");
56
+ return runStep("issue_invoice", deps.recorder, async () => {
57
+ const proforma = deps.findProformaForBooking
58
+ ? await deps.findProformaForBooking(input.bookingId)
59
+ : null;
60
+ return deps.issueInvoice({
61
+ bookingId: input.bookingId,
62
+ convertedFromInvoiceId: proforma?.invoiceId ?? null,
63
+ });
64
+ });
65
+ }),
66
+ step("link_payment_to_invoice").run(async (input, ctx) => {
67
+ const deps = ctx.results.__deps;
68
+ if (!deps)
69
+ throw new Error("checkout-finalize: deps not seeded into context");
70
+ if (!deps.linkPaymentToInvoice)
71
+ return null;
72
+ const issueOutput = ctx.results.issue_invoice;
73
+ if (!issueOutput?.invoiceId) {
74
+ // Invoice generation was skipped (returned null) — there's
75
+ // nothing to link a payment to. Skip silently rather than
76
+ // throwing so the workflow continues for "hold"-only checkouts.
77
+ return null;
78
+ }
79
+ return runStep("link_payment_to_invoice", deps.recorder, () => deps.linkPaymentToInvoice({
80
+ bookingId: input.bookingId,
81
+ invoiceId: issueOutput.invoiceId,
82
+ paymentSessionId: input.paymentSessionId,
83
+ }));
84
+ }),
85
+ step("generate_contract_pdf").run(async (input, ctx) => {
86
+ const deps = ctx.results.__deps;
87
+ if (!deps)
88
+ throw new Error("checkout-finalize: deps not seeded into context");
89
+ // Optional step — when no generator is wired, the workflow
90
+ // proceeds without a contract document (some operators don't
91
+ // attach a customer-facing contract). Returning null also keeps
92
+ // the dashboard's step row, which is the point of having this
93
+ // as an explicit step rather than a fire-and-forget subscriber.
94
+ if (!deps.generateContractPdf)
95
+ return null;
96
+ return runStep("generate_contract_pdf", deps.recorder, () => deps.generateContractPdf({ bookingId: input.bookingId }));
97
+ }),
98
+ ]);
99
+ /**
100
+ * Run the workflow with deps seeded. Wraps `checkoutFinalizeWorkflow.run`
101
+ * with the dependency-injection plumbing — the workflow primitive
102
+ * doesn't carry a "deps" concept on its own, so we pass them through
103
+ * `ctx.results` keyed under `__deps`.
104
+ *
105
+ * Resume support: when `skipUntil` is set, the seeded `__deps` step
106
+ * is added to `seedResults` automatically so the resumed step still
107
+ * sees `ctx.results.__deps`. The caller doesn't need to know about
108
+ * the deps-injection mechanism.
109
+ */
110
+ export async function runCheckoutFinalize(input, deps, options = {}) {
111
+ // Seed deps into ctx.results before the first step runs by passing
112
+ // them as a synthetic step output. The step name MUST match the
113
+ // key the downstream steps read (`__deps`) — a previous spelling
114
+ // (`__seed_deps`) silently broke this because step outputs are
115
+ // keyed by name.
116
+ const seeded = createWorkflow("checkout-finalize", [
117
+ step("__deps").run(() => deps),
118
+ ...checkoutFinalizeWorkflow.steps,
119
+ ]);
120
+ // For resume: the synthetic "__deps" step would otherwise be
121
+ // skipped (and produce no value), starving downstream steps of
122
+ // their dependencies. Inject deps into seedResults so the skipped
123
+ // path still hydrates them.
124
+ const seedResults = options.skipUntil !== undefined
125
+ ? { ...(options.seedResults ?? {}), __deps: deps }
126
+ : options.seedResults;
127
+ await seeded.run({
128
+ input,
129
+ skipUntil: options.skipUntil,
130
+ seedResults,
131
+ });
132
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `BookingDraft` + V1 engine contract schemas now live in
3
+ * `@voyant-travel/catalog-contracts` so external consumers (Voyant Connect, adapter
4
+ * authors, the Admin SDK) can validate booking-engine payloads without the
5
+ * catalog runtime. Re-exported here to keep existing `@voyant-travel/catalog`
6
+ * import paths stable. See `docs/adr/0002-contract-packages.md`.
7
+ */
8
+ export * from "@voyant-travel/catalog-contracts/booking-engine/contracts";
9
+ //# sourceMappingURL=contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/booking-engine/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,2DAA2D,CAAA"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `BookingDraft` + V1 engine contract schemas now live in
3
+ * `@voyant-travel/catalog-contracts` so external consumers (Voyant Connect, adapter
4
+ * authors, the Admin SDK) can validate booking-engine payloads without the
5
+ * catalog runtime. Re-exported here to keep existing `@voyant-travel/catalog`
6
+ * import paths stable. See `docs/adr/0002-contract-packages.md`.
7
+ */
8
+ export * from "@voyant-travel/catalog-contracts/booking-engine/contracts";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contracts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.test.d.ts","sourceRoot":"","sources":["../../src/booking-engine/contracts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { bookingDraftV1, bookRequestV1, pricingBreakdownV1, quoteRequestV1, quoteResponseV1, } from "./contracts.js";
3
+ describe("V1 contracts", () => {
4
+ describe("bookingDraftV1", () => {
5
+ it("accepts a minimal draft and applies defaults", () => {
6
+ const result = bookingDraftV1.parse({
7
+ entity: { module: "products", id: "prod_1", sourceKind: "owned" },
8
+ });
9
+ expect(result.configure.pax).toEqual({});
10
+ expect(result.billing.buyerType).toBe("B2C");
11
+ expect(result.payment.intent).toBe("hold");
12
+ expect(result.travelers).toEqual([]);
13
+ expect(result.addons).toEqual([]);
14
+ });
15
+ it("preserves explicit field values", () => {
16
+ const result = bookingDraftV1.parse({
17
+ entity: { module: "products", id: "prod_1", sourceKind: "owned" },
18
+ configure: {
19
+ pax: { adult: 2 },
20
+ roomTypeId: "HOTEL:DZL1",
21
+ ratePlanId: "HOTEL:DZL1:BB",
22
+ board: "BB",
23
+ },
24
+ billing: {
25
+ buyerType: "B2B",
26
+ contact: { firstName: "Mihai", lastName: "U", email: "a@b.com" },
27
+ address: { country: "RO" },
28
+ company: { name: "Voyant" },
29
+ },
30
+ });
31
+ expect(result.configure.pax.adult).toBe(2);
32
+ expect(result.configure.roomTypeId).toBe("HOTEL:DZL1");
33
+ expect(result.configure.ratePlanId).toBe("HOTEL:DZL1:BB");
34
+ expect(result.configure.board).toBe("BB");
35
+ expect(result.billing.buyerType).toBe("B2B");
36
+ expect(result.billing.address.country).toBe("RO");
37
+ expect(result.billing.company?.name).toBe("Voyant");
38
+ });
39
+ it("rejects malformed entity reference", () => {
40
+ const result = bookingDraftV1.safeParse({});
41
+ expect(result.success).toBe(false);
42
+ });
43
+ });
44
+ describe("quoteRequestV1", () => {
45
+ it("validates a request without a draft", () => {
46
+ const result = quoteRequestV1.safeParse({
47
+ entityModule: "products",
48
+ entityId: "prod_1",
49
+ sourceKind: "owned",
50
+ scope: { locale: "en-GB", audience: "staff", market: "default" },
51
+ });
52
+ expect(result.success).toBe(true);
53
+ });
54
+ it("requires audience to be one of the recognized actors", () => {
55
+ const result = quoteRequestV1.safeParse({
56
+ entityModule: "products",
57
+ entityId: "prod_1",
58
+ sourceKind: "owned",
59
+ scope: { locale: "en-GB", audience: "robot", market: "default" },
60
+ });
61
+ expect(result.success).toBe(false);
62
+ });
63
+ });
64
+ describe("bookRequestV1", () => {
65
+ it("requires either quoteId or draftId", () => {
66
+ expect(bookRequestV1.safeParse({}).success).toBe(false);
67
+ expect(bookRequestV1.safeParse({ quoteId: "q1" }).success).toBe(true);
68
+ expect(bookRequestV1.safeParse({ draftId: "d1" }).success).toBe(true);
69
+ });
70
+ it("rejects too-short idempotency keys", () => {
71
+ const result = bookRequestV1.safeParse({ quoteId: "q1", idempotencyKey: "abc" });
72
+ expect(result.success).toBe(false);
73
+ });
74
+ it("accepts idempotency keys in the 8–128 range", () => {
75
+ expect(bookRequestV1.safeParse({ quoteId: "q1", idempotencyKey: "12345678" }).success).toBe(true);
76
+ expect(bookRequestV1.safeParse({ quoteId: "q1", idempotencyKey: "x".repeat(128) }).success).toBe(true);
77
+ expect(bookRequestV1.safeParse({ quoteId: "q1", idempotencyKey: "x".repeat(129) }).success).toBe(false);
78
+ });
79
+ });
80
+ describe("pricingBreakdownV1", () => {
81
+ it("validates a populated breakdown", () => {
82
+ const result = pricingBreakdownV1.safeParse({
83
+ currency: "EUR",
84
+ lines: [{ kind: "base", label: "Base", quantity: 2, unitAmount: 5000, totalAmount: 10000 }],
85
+ taxes: [{ code: "vat", label: "VAT", rate: 0.19, amount: 1900, base: 10000 }],
86
+ subtotal: 10000,
87
+ taxTotal: 1900,
88
+ total: 11900,
89
+ });
90
+ expect(result.success).toBe(true);
91
+ });
92
+ it("rejects bad currency length", () => {
93
+ const result = pricingBreakdownV1.safeParse({
94
+ currency: "EURO",
95
+ lines: [],
96
+ taxes: [],
97
+ subtotal: 0,
98
+ taxTotal: 0,
99
+ total: 0,
100
+ });
101
+ expect(result.success).toBe(false);
102
+ });
103
+ });
104
+ describe("quoteResponseV1", () => {
105
+ it("validates a minimal availability=false response", () => {
106
+ const result = quoteResponseV1.safeParse({
107
+ quoteId: "q1",
108
+ quotedAt: new Date().toISOString(),
109
+ expiresAt: new Date(Date.now() + 60_000).toISOString(),
110
+ available: false,
111
+ invalidReason: "out_of_stock",
112
+ });
113
+ expect(result.success).toBe(true);
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `BookingDraftShape` + the shared draft-shape defaults now live in
3
+ * `@voyant-travel/catalog-contracts` so client packages (bookings-react,
4
+ * catalog-react) and adapter authors can consume the journey descriptor
5
+ * without the catalog runtime. Re-exported here to keep existing
6
+ * `@voyant-travel/catalog` import paths stable. See
7
+ * `docs/adr/0002-contract-packages.md`.
8
+ */
9
+ export * from "@voyant-travel/catalog-contracts/booking-engine/draft-shape";
10
+ //# sourceMappingURL=draft-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-shape.d.ts","sourceRoot":"","sources":["../../src/booking-engine/draft-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,6DAA6D,CAAA"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `BookingDraftShape` + the shared draft-shape defaults now live in
3
+ * `@voyant-travel/catalog-contracts` so client packages (bookings-react,
4
+ * catalog-react) and adapter authors can consume the journey descriptor
5
+ * without the catalog runtime. Re-exported here to keep existing
6
+ * `@voyant-travel/catalog` import paths stable. See
7
+ * `docs/adr/0002-contract-packages.md`.
8
+ */
9
+ export * from "@voyant-travel/catalog-contracts/booking-engine/draft-shape";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=draft-shape.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-shape.test.d.ts","sourceRoot":"","sources":["../../src/booking-engine/draft-shape.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { DEFAULT_PAX_BANDS, DEFAULT_PAX_TOTAL, defaultBookingFields, defaultDraftShapeFlags, defaultTravelerFields, paxBandsAllowedTotalFrom, } from "./draft-shape.js";
3
+ describe("defaultDraftShapeFlags", () => {
4
+ it("returns the canonical flag set: configure + billing + travelers + payment + review on, accommodation + addons off", () => {
5
+ const flags = defaultDraftShapeFlags();
6
+ expect(flags.showsConfigure).toBe(true);
7
+ expect(flags.showsBilling).toBe(true);
8
+ expect(flags.showsTravelers).toBe(true);
9
+ expect(flags.showsAccommodation).toBe(false);
10
+ expect(flags.showsAddons).toBe(false);
11
+ expect(flags.showsPayment).toBe(true);
12
+ expect(flags.showsReview).toBe(true);
13
+ });
14
+ it("returns a fresh object on each call (no shared mutation)", () => {
15
+ const a = defaultDraftShapeFlags();
16
+ const b = defaultDraftShapeFlags();
17
+ expect(a).not.toBe(b);
18
+ });
19
+ });
20
+ describe("paxBandsAllowedTotalFrom", () => {
21
+ it("sums minCount and maxCount across bands", () => {
22
+ expect(paxBandsAllowedTotalFrom([
23
+ { code: "adult", label: "Adult", minCount: 1, maxCount: 8 },
24
+ { code: "child", label: "Child", minCount: 0, maxCount: 4 },
25
+ ])).toEqual({ min: 1, max: 12 });
26
+ });
27
+ it("returns 0/0 for an empty list", () => {
28
+ expect(paxBandsAllowedTotalFrom([])).toEqual({ min: 0, max: 0 });
29
+ });
30
+ it("handles the default multi-band list", () => {
31
+ expect(paxBandsAllowedTotalFrom(DEFAULT_PAX_BANDS)).toEqual({ min: 1, max: 18 });
32
+ });
33
+ });
34
+ describe("DEFAULT_PAX_BANDS / DEFAULT_PAX_TOTAL constants", () => {
35
+ it("DEFAULT_PAX_BANDS covers adults, children, and infants", () => {
36
+ expect(DEFAULT_PAX_BANDS).toEqual([
37
+ { code: "adult", label: "Adult", minAge: 12, minCount: 1, maxCount: 8 },
38
+ { code: "child", label: "Child", minAge: 2, maxAge: 11, minCount: 0, maxCount: 6 },
39
+ { code: "infant", label: "Infant", maxAge: 1, minCount: 0, maxCount: 4 },
40
+ ]);
41
+ });
42
+ it("DEFAULT_PAX_TOTAL is 1-8", () => {
43
+ expect(DEFAULT_PAX_TOTAL).toEqual({ min: 1, max: 8 });
44
+ });
45
+ });
46
+ describe("default field requirement sets", () => {
47
+ it("defaultTravelerFields includes identity, contact, and document fields", () => {
48
+ const fields = defaultTravelerFields();
49
+ expect(fields.map((f) => f.key)).toEqual([
50
+ "firstName",
51
+ "lastName",
52
+ "email",
53
+ "phone",
54
+ "dateOfBirth",
55
+ "documentType",
56
+ "documentNumber",
57
+ "documentExpiry",
58
+ ]);
59
+ expect(fields.find((f) => f.key === "firstName")?.required).toBe(true);
60
+ expect(fields.find((f) => f.key === "email")?.required).toBe(false);
61
+ });
62
+ it("defaultBookingFields covers buyerType + address basics", () => {
63
+ const fields = defaultBookingFields();
64
+ const keys = fields.map((f) => f.key);
65
+ expect(keys).toContain("buyerType");
66
+ expect(keys).toContain("address.line1");
67
+ expect(keys).toContain("address.city");
68
+ expect(keys).toContain("address.country");
69
+ });
70
+ it("defaultBookingFields buckets fields into the billing group", () => {
71
+ const fields = defaultBookingFields();
72
+ expect(fields.every((f) => f.group === "billing")).toBe(true);
73
+ });
74
+ });