@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.
- package/LICENSE +201 -0
- package/README.md +190 -0
- package/dist/adapter/booking-forwarding.d.ts +2 -0
- package/dist/adapter/booking-forwarding.d.ts.map +1 -0
- package/dist/adapter/booking-forwarding.js +1 -0
- package/dist/adapter/channel-push-contracts.d.ts +2 -0
- package/dist/adapter/channel-push-contracts.d.ts.map +1 -0
- package/dist/adapter/channel-push-contracts.js +1 -0
- package/dist/adapter/contract.d.ts +2 -0
- package/dist/adapter/contract.d.ts.map +1 -0
- package/dist/adapter/contract.js +1 -0
- package/dist/adapter/contract.test.d.ts +2 -0
- package/dist/adapter/contract.test.d.ts.map +1 -0
- package/dist/adapter/contract.test.js +390 -0
- package/dist/adapter/provider-contracts.d.ts +2 -0
- package/dist/adapter/provider-contracts.d.ts.map +1 -0
- package/dist/adapter/provider-contracts.js +1 -0
- package/dist/adapter/provider-contracts.test.d.ts +2 -0
- package/dist/adapter/provider-contracts.test.d.ts.map +1 -0
- package/dist/adapter/provider-contracts.test.js +206 -0
- package/dist/adapter/schemas.d.ts +2 -0
- package/dist/adapter/schemas.d.ts.map +1 -0
- package/dist/adapter/schemas.js +1 -0
- package/dist/adapter/schemas.test.d.ts +2 -0
- package/dist/adapter/schemas.test.d.ts.map +1 -0
- package/dist/adapter/schemas.test.js +344 -0
- package/dist/booking-engine/book.d.ts +124 -0
- package/dist/booking-engine/book.d.ts.map +1 -0
- package/dist/booking-engine/book.js +311 -0
- package/dist/booking-engine/cancel.d.ts +40 -0
- package/dist/booking-engine/cancel.d.ts.map +1 -0
- package/dist/booking-engine/cancel.js +56 -0
- package/dist/booking-engine/checkout-finalize.d.ts +146 -0
- package/dist/booking-engine/checkout-finalize.d.ts.map +1 -0
- package/dist/booking-engine/checkout-finalize.js +132 -0
- package/dist/booking-engine/contracts.d.ts +9 -0
- package/dist/booking-engine/contracts.d.ts.map +1 -0
- package/dist/booking-engine/contracts.js +8 -0
- package/dist/booking-engine/contracts.test.d.ts +2 -0
- package/dist/booking-engine/contracts.test.d.ts.map +1 -0
- package/dist/booking-engine/contracts.test.js +116 -0
- package/dist/booking-engine/draft-shape.d.ts +10 -0
- package/dist/booking-engine/draft-shape.d.ts.map +1 -0
- package/dist/booking-engine/draft-shape.js +9 -0
- package/dist/booking-engine/draft-shape.test.d.ts +2 -0
- package/dist/booking-engine/draft-shape.test.d.ts.map +1 -0
- package/dist/booking-engine/draft-shape.test.js +74 -0
- package/dist/booking-engine/drafts-schema.d.ts +302 -0
- package/dist/booking-engine/drafts-schema.d.ts.map +1 -0
- package/dist/booking-engine/drafts-schema.js +53 -0
- package/dist/booking-engine/drafts-service.d.ts +41 -0
- package/dist/booking-engine/drafts-service.d.ts.map +1 -0
- package/dist/booking-engine/drafts-service.js +108 -0
- package/dist/booking-engine/errors.d.ts +81 -0
- package/dist/booking-engine/errors.d.ts.map +1 -0
- package/dist/booking-engine/errors.js +113 -0
- package/dist/booking-engine/index.d.ts +36 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +34 -0
- package/dist/booking-engine/orders.d.ts +41 -0
- package/dist/booking-engine/orders.d.ts.map +1 -0
- package/dist/booking-engine/orders.js +49 -0
- package/dist/booking-engine/owned-handler.d.ts +166 -0
- package/dist/booking-engine/owned-handler.d.ts.map +1 -0
- package/dist/booking-engine/owned-handler.js +50 -0
- package/dist/booking-engine/owned-handler.test.d.ts +2 -0
- package/dist/booking-engine/owned-handler.test.d.ts.map +1 -0
- package/dist/booking-engine/owned-handler.test.js +63 -0
- package/dist/booking-engine/promotions-contract.d.ts +8 -0
- package/dist/booking-engine/promotions-contract.d.ts.map +1 -0
- package/dist/booking-engine/promotions-contract.js +7 -0
- package/dist/booking-engine/quote-enricher.test.d.ts +12 -0
- package/dist/booking-engine/quote-enricher.test.d.ts.map +1 -0
- package/dist/booking-engine/quote-enricher.test.js +138 -0
- package/dist/booking-engine/quote.d.ts +163 -0
- package/dist/booking-engine/quote.d.ts.map +1 -0
- package/dist/booking-engine/quote.js +259 -0
- package/dist/booking-engine/registry.d.ts +85 -0
- package/dist/booking-engine/registry.d.ts.map +1 -0
- package/dist/booking-engine/registry.js +118 -0
- package/dist/booking-engine/registry.test.d.ts +2 -0
- package/dist/booking-engine/registry.test.d.ts.map +1 -0
- package/dist/booking-engine/registry.test.js +132 -0
- package/dist/booking-engine/routes-contracts.d.ts +169 -0
- package/dist/booking-engine/routes-contracts.d.ts.map +1 -0
- package/dist/booking-engine/routes-contracts.js +63 -0
- package/dist/booking-engine/routes.d.ts +7 -0
- package/dist/booking-engine/routes.d.ts.map +1 -0
- package/dist/booking-engine/routes.js +443 -0
- package/dist/booking-engine/routes.test.d.ts +2 -0
- package/dist/booking-engine/routes.test.d.ts.map +1 -0
- package/dist/booking-engine/routes.test.js +304 -0
- package/dist/booking-engine/schema.d.ts +455 -0
- package/dist/booking-engine/schema.d.ts.map +1 -0
- package/dist/booking-engine/schema.js +75 -0
- package/dist/booking-engine/snapshot-content.d.ts +120 -0
- package/dist/booking-engine/snapshot-content.d.ts.map +1 -0
- package/dist/booking-engine/snapshot-content.js +110 -0
- package/dist/booking-engine/snapshot-content.test.d.ts +2 -0
- package/dist/booking-engine/snapshot-content.test.d.ts.map +1 -0
- package/dist/booking-engine/snapshot-content.test.js +213 -0
- package/dist/booking-engine/sync.d.ts +136 -0
- package/dist/booking-engine/sync.d.ts.map +1 -0
- package/dist/booking-engine/sync.js +177 -0
- package/dist/booking-engine/sync.test.d.ts +2 -0
- package/dist/booking-engine/sync.test.d.ts.map +1 -0
- package/dist/booking-engine/sync.test.js +377 -0
- package/dist/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +1 -0
- package/dist/contract.test.d.ts +2 -0
- package/dist/contract.test.d.ts.map +1 -0
- package/dist/contract.test.js +107 -0
- package/dist/drift/events.d.ts +2 -0
- package/dist/drift/events.d.ts.map +1 -0
- package/dist/drift/events.js +1 -0
- package/dist/drift/events.test.d.ts +2 -0
- package/dist/drift/events.test.d.ts.map +1 -0
- package/dist/drift/events.test.js +100 -0
- package/dist/embeddings/contract.d.ts +85 -0
- package/dist/embeddings/contract.d.ts.map +1 -0
- package/dist/embeddings/contract.js +42 -0
- package/dist/embeddings/contract.test.d.ts +2 -0
- package/dist/embeddings/contract.test.d.ts.map +1 -0
- package/dist/embeddings/contract.test.js +30 -0
- package/dist/embeddings/gemini.d.ts +110 -0
- package/dist/embeddings/gemini.d.ts.map +1 -0
- package/dist/embeddings/gemini.js +118 -0
- package/dist/embeddings/gemini.test.d.ts +2 -0
- package/dist/embeddings/gemini.test.d.ts.map +1 -0
- package/dist/embeddings/gemini.test.js +132 -0
- package/dist/embeddings/model-registry.d.ts +62 -0
- package/dist/embeddings/model-registry.d.ts.map +1 -0
- package/dist/embeddings/model-registry.js +78 -0
- package/dist/embeddings/model-registry.test.d.ts +2 -0
- package/dist/embeddings/model-registry.test.d.ts.map +1 -0
- package/dist/embeddings/model-registry.test.js +81 -0
- package/dist/embeddings/openai.d.ts +81 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +123 -0
- package/dist/embeddings/openai.test.d.ts +2 -0
- package/dist/embeddings/openai.test.d.ts.map +1 -0
- package/dist/embeddings/openai.test.js +164 -0
- package/dist/events/taxonomy.d.ts +158 -0
- package/dist/events/taxonomy.d.ts.map +1 -0
- package/dist/events/taxonomy.js +99 -0
- package/dist/events/taxonomy.test.d.ts +2 -0
- package/dist/events/taxonomy.test.d.ts.map +1 -0
- package/dist/events/taxonomy.test.js +48 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/indexer/contract.d.ts +203 -0
- package/dist/indexer/contract.d.ts.map +1 -0
- package/dist/indexer/contract.js +16 -0
- package/dist/indexer/typesense-search-query.d.ts +31 -0
- package/dist/indexer/typesense-search-query.d.ts.map +1 -0
- package/dist/indexer/typesense-search-query.js +185 -0
- package/dist/indexer/typesense.d.ts +105 -0
- package/dist/indexer/typesense.d.ts.map +1 -0
- package/dist/indexer/typesense.js +394 -0
- package/dist/indexer/typesense.test.d.ts +2 -0
- package/dist/indexer/typesense.test.d.ts.map +1 -0
- package/dist/indexer/typesense.test.js +253 -0
- package/dist/overlay/resolver.d.ts +101 -0
- package/dist/overlay/resolver.d.ts.map +1 -0
- package/dist/overlay/resolver.js +167 -0
- package/dist/overlay/resolver.test.d.ts +2 -0
- package/dist/overlay/resolver.test.d.ts.map +1 -0
- package/dist/overlay/resolver.test.js +179 -0
- package/dist/overlay/schema.d.ts +266 -0
- package/dist/overlay/schema.d.ts.map +1 -0
- package/dist/overlay/schema.js +71 -0
- package/dist/provenance.d.ts +2 -0
- package/dist/provenance.d.ts.map +1 -0
- package/dist/provenance.js +1 -0
- package/dist/schema-sourced-entries.d.ts +344 -0
- package/dist/schema-sourced-entries.d.ts.map +1 -0
- package/dist/schema-sourced-entries.js +75 -0
- package/dist/schema.d.ts +21 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +20 -0
- package/dist/search/federate.d.ts +58 -0
- package/dist/search/federate.d.ts.map +1 -0
- package/dist/search/federate.js +103 -0
- package/dist/search/federate.test.d.ts +2 -0
- package/dist/search/federate.test.d.ts.map +1 -0
- package/dist/search/federate.test.js +146 -0
- package/dist/search/rerank.d.ts +77 -0
- package/dist/search/rerank.d.ts.map +1 -0
- package/dist/search/rerank.js +68 -0
- package/dist/search/rerank.test.d.ts +2 -0
- package/dist/search/rerank.test.d.ts.map +1 -0
- package/dist/search/rerank.test.js +60 -0
- package/dist/search/routes.d.ts +144 -0
- package/dist/search/routes.d.ts.map +1 -0
- package/dist/search/routes.js +288 -0
- package/dist/search/routes.test.d.ts +2 -0
- package/dist/search/routes.test.d.ts.map +1 -0
- package/dist/search/routes.test.js +322 -0
- package/dist/search/semantic.d.ts +63 -0
- package/dist/search/semantic.d.ts.map +1 -0
- package/dist/search/semantic.js +75 -0
- package/dist/search/semantic.test.d.ts +2 -0
- package/dist/search/semantic.test.d.ts.map +1 -0
- package/dist/search/semantic.test.js +143 -0
- package/dist/services/build-indexer-document.test.d.ts +2 -0
- package/dist/services/build-indexer-document.test.d.ts.map +1 -0
- package/dist/services/build-indexer-document.test.js +102 -0
- package/dist/services/content-service.d.ts +125 -0
- package/dist/services/content-service.d.ts.map +1 -0
- package/dist/services/content-service.js +139 -0
- package/dist/services/content-service.test.d.ts +2 -0
- package/dist/services/content-service.test.d.ts.map +1 -0
- package/dist/services/content-service.test.js +322 -0
- package/dist/services/indexer-service.d.ts +109 -0
- package/dist/services/indexer-service.d.ts.map +1 -0
- package/dist/services/indexer-service.js +123 -0
- package/dist/services/indexer-service.test.d.ts +2 -0
- package/dist/services/indexer-service.test.d.ts.map +1 -0
- package/dist/services/indexer-service.test.js +176 -0
- package/dist/services/overlay-service.d.ts +108 -0
- package/dist/services/overlay-service.d.ts.map +1 -0
- package/dist/services/overlay-service.js +211 -0
- package/dist/services/overlay-service.test.d.ts +2 -0
- package/dist/services/overlay-service.test.d.ts.map +1 -0
- package/dist/services/overlay-service.test.js +79 -0
- package/dist/services/snapshot-builder.test.d.ts +2 -0
- package/dist/services/snapshot-builder.test.d.ts.map +1 -0
- package/dist/services/snapshot-builder.test.js +93 -0
- package/dist/services/snapshot-service.d.ts +78 -0
- package/dist/services/snapshot-service.d.ts.map +1 -0
- package/dist/services/snapshot-service.js +165 -0
- package/dist/services/sourced-entry-service.d.ts +142 -0
- package/dist/services/sourced-entry-service.d.ts.map +1 -0
- package/dist/services/sourced-entry-service.js +203 -0
- package/dist/services/sourced-entry-service.test.d.ts +10 -0
- package/dist/services/sourced-entry-service.test.d.ts.map +1 -0
- package/dist/services/sourced-entry-service.test.js +66 -0
- package/dist/snapshot/schema.d.ts +362 -0
- package/dist/snapshot/schema.d.ts.map +1 -0
- package/dist/snapshot/schema.js +102 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|
+
});
|