@voyantjs/storefront-sdk 0.31.3
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/README.md +33 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +92 -0
- package/dist/engine-state.d.ts +21 -0
- package/dist/engine-state.d.ts.map +1 -0
- package/dist/engine-state.js +124 -0
- package/dist/index.d.ts +1280 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/operations.d.ts +1263 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +80 -0
- package/dist/schemas.d.ts +1190 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +20 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# `@voyantjs/storefront-sdk`
|
|
2
|
+
|
|
3
|
+
Framework-agnostic TypeScript client for custom Voyant storefronts.
|
|
4
|
+
|
|
5
|
+
The SDK does not own HTTP routes. It wraps the existing public contracts from
|
|
6
|
+
`@voyantjs/storefront`, `@voyantjs/bookings`, and `@voyantjs/checkout` behind a
|
|
7
|
+
single typed client so custom booking UIs can consume Voyant booking logic
|
|
8
|
+
without stitching together package-local fetchers.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createVoyantStorefrontClient } from "@voyantjs/storefront-sdk"
|
|
12
|
+
|
|
13
|
+
const voyant = createVoyantStorefrontClient({
|
|
14
|
+
baseUrl: "https://operator.example.com",
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const session = await voyant.booking.createSession({
|
|
18
|
+
sellCurrency: "EUR",
|
|
19
|
+
items: [
|
|
20
|
+
{
|
|
21
|
+
title: "Danube tour",
|
|
22
|
+
availabilitySlotId: "slot_123",
|
|
23
|
+
quantity: 2,
|
|
24
|
+
totalSellAmountCents: 24000,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const state = voyant.booking.deriveState(session)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
React consumers should layer React Query hooks on top of this package rather
|
|
33
|
+
than reimplementing request paths directly.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export type VoyantStorefrontFetcher = (url: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export declare const defaultStorefrontFetcher: VoyantStorefrontFetcher;
|
|
4
|
+
export declare class VoyantStorefrontApiError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
constructor(message: string, status: number, body: unknown);
|
|
8
|
+
}
|
|
9
|
+
export interface VoyantStorefrontClientOptions {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
fetcher?: VoyantStorefrontFetcher;
|
|
12
|
+
headers?: HeadersInit;
|
|
13
|
+
}
|
|
14
|
+
export interface StorefrontRequestOptions {
|
|
15
|
+
headers?: HeadersInit;
|
|
16
|
+
idempotencyKey?: string;
|
|
17
|
+
}
|
|
18
|
+
export type StorefrontQueryParamValue = string | number | boolean | null | undefined | Array<string | number | boolean>;
|
|
19
|
+
export declare function withStorefrontQueryParams(path: string, query?: object): string;
|
|
20
|
+
export declare function storefrontFetchWithValidation<TOut>(path: string, schema: z.ZodType<TOut>, options: Required<Pick<VoyantStorefrontClientOptions, "baseUrl" | "fetcher">> & Pick<VoyantStorefrontClientOptions, "headers">, init?: RequestInit): Promise<TOut>;
|
|
21
|
+
export declare function requestHeaders(options?: StorefrontRequestOptions): HeadersInit | undefined;
|
|
22
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,MAAM,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAE5F,eAAO,MAAM,wBAAwB,EAAE,uBACU,CAAA;AAEjD,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,OAAO,CAAC,EAAE,WAAW,CAAA;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,yBAAyB,GACjC,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;AAEpC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAwB9E;AAED,wBAAsB,6BAA6B,CAAC,IAAI,EACtD,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,6BAA6B,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC,GAC3E,IAAI,CAAC,6BAA6B,EAAE,SAAS,CAAC,EAChD,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,WAAW,GAAG,SAAS,CAU1F"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const defaultStorefrontFetcher = (url, init) => fetch(url, { credentials: "include", ...init });
|
|
2
|
+
export class VoyantStorefrontApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(message, status, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "VoyantStorefrontApiError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function withStorefrontQueryParams(path, query) {
|
|
13
|
+
if (!query) {
|
|
14
|
+
return path;
|
|
15
|
+
}
|
|
16
|
+
const params = new URLSearchParams();
|
|
17
|
+
for (const [key, value] of Object.entries(query)) {
|
|
18
|
+
if (value === undefined || value === null) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (const item of value) {
|
|
23
|
+
params.append(key, String(item));
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
params.set(key, String(value));
|
|
28
|
+
}
|
|
29
|
+
const serialized = params.toString();
|
|
30
|
+
return serialized ? `${path}?${serialized}` : path;
|
|
31
|
+
}
|
|
32
|
+
export async function storefrontFetchWithValidation(path, schema, options, init) {
|
|
33
|
+
const url = joinUrl(options.baseUrl, path);
|
|
34
|
+
const headers = new Headers(options.headers);
|
|
35
|
+
for (const [key, value] of new Headers(init?.headers)) {
|
|
36
|
+
headers.set(key, value);
|
|
37
|
+
}
|
|
38
|
+
if (init?.body !== undefined && !headers.has("Content-Type")) {
|
|
39
|
+
headers.set("Content-Type", "application/json");
|
|
40
|
+
}
|
|
41
|
+
const response = await options.fetcher(url, { ...init, headers });
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const body = await safeJson(response);
|
|
44
|
+
throw new VoyantStorefrontApiError(extractErrorMessage(response.status, response.statusText, body), response.status, body);
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 204) {
|
|
47
|
+
return schema.parse(undefined);
|
|
48
|
+
}
|
|
49
|
+
const body = await safeJson(response);
|
|
50
|
+
const parsed = schema.safeParse(body);
|
|
51
|
+
if (!parsed.success) {
|
|
52
|
+
throw new VoyantStorefrontApiError(`Voyant storefront response failed validation: ${parsed.error.message}`, response.status, body);
|
|
53
|
+
}
|
|
54
|
+
return parsed.data;
|
|
55
|
+
}
|
|
56
|
+
export function requestHeaders(options) {
|
|
57
|
+
if (!options?.headers && !options?.idempotencyKey) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const headers = new Headers(options.headers);
|
|
61
|
+
if (options.idempotencyKey) {
|
|
62
|
+
headers.set("Idempotency-Key", options.idempotencyKey);
|
|
63
|
+
}
|
|
64
|
+
return headers;
|
|
65
|
+
}
|
|
66
|
+
function extractErrorMessage(status, statusText, body) {
|
|
67
|
+
if (typeof body === "object" && body !== null && "error" in body) {
|
|
68
|
+
const err = body.error;
|
|
69
|
+
if (typeof err === "string")
|
|
70
|
+
return err;
|
|
71
|
+
if (typeof err === "object" && err !== null && "message" in err) {
|
|
72
|
+
return String(err.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return `Voyant storefront API error: ${status} ${statusText}`;
|
|
76
|
+
}
|
|
77
|
+
async function safeJson(response) {
|
|
78
|
+
const text = await response.text();
|
|
79
|
+
if (!text)
|
|
80
|
+
return undefined;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(text);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function joinUrl(baseUrl, path) {
|
|
89
|
+
const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
90
|
+
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
|
|
91
|
+
return `${trimmedBase}${trimmedPath}`;
|
|
92
|
+
}
|
|
@@ -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
|
+
}
|