@voyant-travel/storefront-sdk 0.120.1

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/dist/client.js ADDED
@@ -0,0 +1,98 @@
1
+ import { parseStorefrontApiErrorEnvelope } from "./errors.js";
2
+ export const defaultStorefrontFetcher = (url, init) => fetch(url, { credentials: "include", ...init });
3
+ export class VoyantStorefrontApiError extends Error {
4
+ status;
5
+ body;
6
+ normalizedError;
7
+ constructor(message, status, body, normalizedError = parseStorefrontApiErrorEnvelope(body)) {
8
+ super(message);
9
+ this.name = "VoyantStorefrontApiError";
10
+ this.status = status;
11
+ this.body = body;
12
+ this.normalizedError = normalizedError;
13
+ }
14
+ }
15
+ export function withStorefrontQueryParams(path, query) {
16
+ if (!query) {
17
+ return path;
18
+ }
19
+ const params = new URLSearchParams();
20
+ for (const [key, value] of Object.entries(query)) {
21
+ if (value === undefined || value === null) {
22
+ continue;
23
+ }
24
+ if (Array.isArray(value)) {
25
+ for (const item of value) {
26
+ params.append(key, String(item));
27
+ }
28
+ continue;
29
+ }
30
+ params.set(key, String(value));
31
+ }
32
+ const serialized = params.toString();
33
+ return serialized ? `${path}?${serialized}` : path;
34
+ }
35
+ export async function storefrontFetchWithValidation(path, schema, options, init) {
36
+ const url = joinUrl(options.baseUrl, path);
37
+ const headers = new Headers(options.headers);
38
+ for (const [key, value] of new Headers(init?.headers)) {
39
+ headers.set(key, value);
40
+ }
41
+ if (init?.body !== undefined && !headers.has("Content-Type")) {
42
+ headers.set("Content-Type", "application/json");
43
+ }
44
+ const response = await options.fetcher(url, { ...init, headers });
45
+ if (!response.ok) {
46
+ const body = await safeJson(response);
47
+ throw new VoyantStorefrontApiError(extractErrorMessage(response.status, response.statusText, body), response.status, body);
48
+ }
49
+ if (response.status === 204) {
50
+ return schema.parse(undefined);
51
+ }
52
+ const body = await safeJson(response);
53
+ const parsed = schema.safeParse(body);
54
+ if (!parsed.success) {
55
+ throw new VoyantStorefrontApiError(`Voyant storefront response failed validation: ${parsed.error.message}`, response.status, body);
56
+ }
57
+ return parsed.data;
58
+ }
59
+ export function requestHeaders(options) {
60
+ if (!options?.headers && !options?.idempotencyKey) {
61
+ return undefined;
62
+ }
63
+ const headers = new Headers(options.headers);
64
+ if (options.idempotencyKey) {
65
+ headers.set("Idempotency-Key", options.idempotencyKey);
66
+ }
67
+ return headers;
68
+ }
69
+ function extractErrorMessage(status, statusText, body) {
70
+ const normalizedError = parseStorefrontApiErrorEnvelope(body);
71
+ if (normalizedError)
72
+ return normalizedError.message;
73
+ if (typeof body === "object" && body !== null && "error" in body) {
74
+ const err = body.error;
75
+ if (typeof err === "string")
76
+ return err;
77
+ if (typeof err === "object" && err !== null && "message" in err) {
78
+ return String(err.message);
79
+ }
80
+ }
81
+ return `Voyant storefront API error: ${status} ${statusText}`;
82
+ }
83
+ async function safeJson(response) {
84
+ const text = await response.text();
85
+ if (!text)
86
+ return undefined;
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch {
91
+ return text;
92
+ }
93
+ }
94
+ function joinUrl(baseUrl, path) {
95
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
96
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
97
+ return `${trimmedBase}${trimmedPath}`;
98
+ }
@@ -0,0 +1,21 @@
1
+ import type { PublicBookingSessionRecord } from "./schemas.js";
2
+ export declare const bookingEngineStates: readonly ["draft", "reserved", "billing_completed", "travelers_completed", "terms_accepted", "ready_for_payment", "payment_started", "payment_pending", "confirmed", "completed", "expired", "cancelled"];
3
+ export type BookingEngineState = (typeof bookingEngineStates)[number];
4
+ export declare const bookingEngineActions: readonly ["create_session", "reserve", "update_billing", "update_travelers", "reprice", "accept_terms", "start_payment", "poll_payment", "confirm", "expire", "clear_session"];
5
+ export type BookingEngineAction = (typeof bookingEngineActions)[number];
6
+ export interface BookingEngineSnapshot {
7
+ sessionId: string;
8
+ bookingNumber: string;
9
+ state: BookingEngineState;
10
+ bookingStatus: PublicBookingSessionRecord["status"];
11
+ currentStep: string | null;
12
+ completedSteps: string[];
13
+ holdExpiresAt: string | null;
14
+ readyForConfirmation: boolean;
15
+ allowedActions: BookingEngineAction[];
16
+ }
17
+ export declare function deriveBookingEngineState(session: PublicBookingSessionRecord): BookingEngineState;
18
+ export declare function getAllowedBookingEngineActions(state: BookingEngineState): BookingEngineAction[];
19
+ export declare function canRunBookingEngineAction(state: BookingEngineState, action: BookingEngineAction): boolean;
20
+ export declare function createBookingEngineSnapshot(session: PublicBookingSessionRecord): BookingEngineSnapshot;
21
+ //# sourceMappingURL=engine-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-state.d.ts","sourceRoot":"","sources":["../src/engine-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAE9D,eAAO,MAAM,mBAAmB,2MAatB,CAAA;AAEV,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAErE,eAAO,MAAM,oBAAoB,gLAYvB,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,kBAAkB,CAAA;IACzB,aAAa,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACnD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,oBAAoB,EAAE,OAAO,CAAA;IAC7B,cAAc,EAAE,mBAAmB,EAAE,CAAA;CACtC;AAWD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,0BAA0B,GAAG,kBAAkB,CAmChG;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,EAAE,CAoC/F;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAET;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,0BAA0B,GAClC,qBAAqB,CAavB"}
@@ -0,0 +1,124 @@
1
+ export const bookingEngineStates = [
2
+ "draft",
3
+ "reserved",
4
+ "billing_completed",
5
+ "travelers_completed",
6
+ "terms_accepted",
7
+ "ready_for_payment",
8
+ "payment_started",
9
+ "payment_pending",
10
+ "confirmed",
11
+ "completed",
12
+ "expired",
13
+ "cancelled",
14
+ ];
15
+ export const bookingEngineActions = [
16
+ "create_session",
17
+ "reserve",
18
+ "update_billing",
19
+ "update_travelers",
20
+ "reprice",
21
+ "accept_terms",
22
+ "start_payment",
23
+ "poll_payment",
24
+ "confirm",
25
+ "expire",
26
+ "clear_session",
27
+ ];
28
+ const completedStateByStep = [
29
+ ["payment", "payment_started"],
30
+ ["terms", "terms_accepted"],
31
+ ["contract", "terms_accepted"],
32
+ ["travelers", "travelers_completed"],
33
+ ["passengers", "travelers_completed"],
34
+ ["billing", "billing_completed"],
35
+ ];
36
+ export function deriveBookingEngineState(session) {
37
+ switch (session.status) {
38
+ case "cancelled":
39
+ return "cancelled";
40
+ case "completed":
41
+ return "completed";
42
+ case "expired":
43
+ return "expired";
44
+ case "confirmed":
45
+ case "in_progress":
46
+ return "confirmed";
47
+ case "awaiting_payment":
48
+ return "payment_pending";
49
+ case "on_hold":
50
+ break;
51
+ case "draft":
52
+ return "draft";
53
+ }
54
+ const completedSteps = new Set(session.state?.completedSteps ?? []);
55
+ if (session.checklist.readyForConfirmation && completedSteps.has("payment")) {
56
+ return "payment_started";
57
+ }
58
+ if (session.checklist.readyForConfirmation && hasTermsAccepted(completedSteps)) {
59
+ return "ready_for_payment";
60
+ }
61
+ for (const [step, state] of completedStateByStep) {
62
+ if (completedSteps.has(step)) {
63
+ return state;
64
+ }
65
+ }
66
+ return "reserved";
67
+ }
68
+ export function getAllowedBookingEngineActions(state) {
69
+ switch (state) {
70
+ case "draft":
71
+ return ["create_session", "reserve", "clear_session"];
72
+ case "reserved":
73
+ case "billing_completed":
74
+ case "travelers_completed":
75
+ return [
76
+ "update_billing",
77
+ "update_travelers",
78
+ "reprice",
79
+ "accept_terms",
80
+ "expire",
81
+ "clear_session",
82
+ ];
83
+ case "terms_accepted":
84
+ case "ready_for_payment":
85
+ return [
86
+ "update_billing",
87
+ "update_travelers",
88
+ "reprice",
89
+ "start_payment",
90
+ "confirm",
91
+ "expire",
92
+ "clear_session",
93
+ ];
94
+ case "payment_started":
95
+ case "payment_pending":
96
+ return ["poll_payment", "confirm", "clear_session"];
97
+ case "confirmed":
98
+ case "completed":
99
+ return ["clear_session"];
100
+ case "expired":
101
+ case "cancelled":
102
+ return ["clear_session"];
103
+ }
104
+ }
105
+ export function canRunBookingEngineAction(state, action) {
106
+ return getAllowedBookingEngineActions(state).includes(action);
107
+ }
108
+ export function createBookingEngineSnapshot(session) {
109
+ const state = deriveBookingEngineState(session);
110
+ return {
111
+ sessionId: session.sessionId,
112
+ bookingNumber: session.bookingNumber,
113
+ state,
114
+ bookingStatus: session.status,
115
+ currentStep: session.state?.currentStep ?? null,
116
+ completedSteps: session.state?.completedSteps ?? [],
117
+ holdExpiresAt: session.holdExpiresAt,
118
+ readyForConfirmation: session.checklist.readyForConfirmation,
119
+ allowedActions: getAllowedBookingEngineActions(state),
120
+ };
121
+ }
122
+ function hasTermsAccepted(completedSteps) {
123
+ return completedSteps.has("terms") || completedSteps.has("contract");
124
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ export declare const bookingEngineErrorCodes: readonly ["contract_template_missing", "reservation_expired", "departure_unavailable", "invalid_traveler_payload", "payment_provider_unavailable", "payment_url_missing", "payment_webhook_pending", "checkout_finalization_failed"];
3
+ export type BookingEngineErrorCode = (typeof bookingEngineErrorCodes)[number];
4
+ export declare const bookingEngineNextActions: readonly ["configure_contract_template", "restart_reservation", "choose_another_departure", "correct_traveler_payload", "choose_another_payment_method", "retry_payment_start", "poll_payment_status", "contact_operator"];
5
+ export type BookingEngineNextAction = (typeof bookingEngineNextActions)[number];
6
+ export declare const storefrontApiErrorEnvelopeSchema: z.ZodObject<{
7
+ code: z.ZodOptional<z.ZodString>;
8
+ message: z.ZodString;
9
+ recoverable: z.ZodOptional<z.ZodBoolean>;
10
+ nextAction: z.ZodOptional<z.ZodString>;
11
+ details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ }, z.core.$strip>;
13
+ export declare const bookingEngineErrorEnvelopeSchema: z.ZodObject<{
14
+ message: z.ZodString;
15
+ recoverable: z.ZodOptional<z.ZodBoolean>;
16
+ details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
17
+ code: z.ZodEnum<{
18
+ contract_template_missing: "contract_template_missing";
19
+ reservation_expired: "reservation_expired";
20
+ departure_unavailable: "departure_unavailable";
21
+ invalid_traveler_payload: "invalid_traveler_payload";
22
+ payment_provider_unavailable: "payment_provider_unavailable";
23
+ payment_url_missing: "payment_url_missing";
24
+ payment_webhook_pending: "payment_webhook_pending";
25
+ checkout_finalization_failed: "checkout_finalization_failed";
26
+ }>;
27
+ nextAction: z.ZodOptional<z.ZodEnum<{
28
+ configure_contract_template: "configure_contract_template";
29
+ restart_reservation: "restart_reservation";
30
+ choose_another_departure: "choose_another_departure";
31
+ correct_traveler_payload: "correct_traveler_payload";
32
+ choose_another_payment_method: "choose_another_payment_method";
33
+ retry_payment_start: "retry_payment_start";
34
+ poll_payment_status: "poll_payment_status";
35
+ contact_operator: "contact_operator";
36
+ }>>;
37
+ }, z.core.$strip>;
38
+ export type StorefrontApiErrorEnvelope = z.infer<typeof storefrontApiErrorEnvelopeSchema>;
39
+ export type BookingEngineErrorEnvelope = z.infer<typeof bookingEngineErrorEnvelopeSchema>;
40
+ export declare function parseStorefrontApiErrorEnvelope(body: unknown): StorefrontApiErrorEnvelope | null;
41
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,uBAAuB,sOAS1B,CAAA;AAEV,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7E,eAAO,MAAM,wBAAwB,4NAS3B,CAAA;AAEV,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/E,eAAO,MAAM,gCAAgC;;;;;;iBAM3C,CAAA;AAEF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;iBAG3C,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AACzF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAEzF,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,OAAO,GAAG,0BAA0B,GAAG,IAAI,CAqBhG"}
package/dist/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ export const bookingEngineErrorCodes = [
3
+ "contract_template_missing",
4
+ "reservation_expired",
5
+ "departure_unavailable",
6
+ "invalid_traveler_payload",
7
+ "payment_provider_unavailable",
8
+ "payment_url_missing",
9
+ "payment_webhook_pending",
10
+ "checkout_finalization_failed",
11
+ ];
12
+ export const bookingEngineNextActions = [
13
+ "configure_contract_template",
14
+ "restart_reservation",
15
+ "choose_another_departure",
16
+ "correct_traveler_payload",
17
+ "choose_another_payment_method",
18
+ "retry_payment_start",
19
+ "poll_payment_status",
20
+ "contact_operator",
21
+ ];
22
+ export const storefrontApiErrorEnvelopeSchema = z.object({
23
+ code: z.string().optional(),
24
+ message: z.string(),
25
+ recoverable: z.boolean().optional(),
26
+ nextAction: z.string().optional(),
27
+ details: z.record(z.string(), z.unknown()).optional(),
28
+ });
29
+ export const bookingEngineErrorEnvelopeSchema = storefrontApiErrorEnvelopeSchema.extend({
30
+ code: z.enum(bookingEngineErrorCodes),
31
+ nextAction: z.enum(bookingEngineNextActions).optional(),
32
+ });
33
+ export function parseStorefrontApiErrorEnvelope(body) {
34
+ const direct = storefrontApiErrorEnvelopeSchema.safeParse(body);
35
+ if (direct.success) {
36
+ return direct.data;
37
+ }
38
+ if (typeof body !== "object" || body === null || !("error" in body)) {
39
+ return null;
40
+ }
41
+ const error = body.error;
42
+ if (typeof error === "string") {
43
+ const code = typeof body.code === "string"
44
+ ? body.code
45
+ : undefined;
46
+ return { code, message: error };
47
+ }
48
+ const nested = storefrontApiErrorEnvelopeSchema.safeParse(error);
49
+ return nested.success ? nested.data : null;
50
+ }