@asteby/storefront 0.0.0-stage → 0.2.0
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 +164 -2
- package/dist/index.cjs +326 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +314 -0
- package/dist/index.js.map +1 -0
- package/dist/mock.cjs +998 -0
- package/dist/mock.cjs.map +1 -0
- package/dist/mock.d.cts +95 -0
- package/dist/mock.d.ts +95 -0
- package/dist/mock.js +993 -0
- package/dist/mock.js.map +1 -0
- package/dist/types-CTXB8Dnl.d.cts +465 -0
- package/dist/types-CTXB8Dnl.d.ts +465 -0
- package/package.json +59 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var StorefrontError = class extends Error {
|
|
5
|
+
name = "StorefrontError";
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
field;
|
|
9
|
+
url;
|
|
10
|
+
constructor(init) {
|
|
11
|
+
super(init.message, init.cause === void 0 ? void 0 : { cause: init.cause });
|
|
12
|
+
this.status = init.status;
|
|
13
|
+
this.code = init.code;
|
|
14
|
+
this.field = init.field;
|
|
15
|
+
this.url = init.url;
|
|
16
|
+
}
|
|
17
|
+
toJSON() {
|
|
18
|
+
return { name: this.name, status: this.status, code: this.code, message: this.message, field: this.field };
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function isStorefrontError(err) {
|
|
22
|
+
return err instanceof StorefrontError || typeof err === "object" && err !== null && err.name === "StorefrontError";
|
|
23
|
+
}
|
|
24
|
+
var STATUS_CODES = {
|
|
25
|
+
400: "validation",
|
|
26
|
+
401: "unauthorized",
|
|
27
|
+
403: "forbidden",
|
|
28
|
+
404: "not_found",
|
|
29
|
+
409: "conflict",
|
|
30
|
+
422: "invalid_state",
|
|
31
|
+
429: "rate_limited"
|
|
32
|
+
};
|
|
33
|
+
function defaultCodeForStatus(status) {
|
|
34
|
+
return STATUS_CODES[status] ?? (status >= 500 ? "server_error" : "invalid_response");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/money.ts
|
|
38
|
+
function parseMoney(money) {
|
|
39
|
+
if (money == null) return 0;
|
|
40
|
+
const raw = typeof money === "string" ? money : money.amount;
|
|
41
|
+
const n = Number.parseFloat(raw);
|
|
42
|
+
return Number.isFinite(n) ? n : 0;
|
|
43
|
+
}
|
|
44
|
+
function formatMoney(money, locale, options = {}) {
|
|
45
|
+
const value = parseMoney(money);
|
|
46
|
+
const whole = Number.isInteger(value);
|
|
47
|
+
const digits = options.withoutTrailingZeros && whole ? 0 : void 0;
|
|
48
|
+
try {
|
|
49
|
+
const fmt = new Intl.NumberFormat(locale, {
|
|
50
|
+
...options.withoutCurrency ? { style: "decimal", minimumFractionDigits: digits ?? 2, maximumFractionDigits: digits ?? 2 } : {
|
|
51
|
+
style: "currency",
|
|
52
|
+
currency: money.currency_code,
|
|
53
|
+
currencyDisplay: options.currencyDisplay ?? "symbol",
|
|
54
|
+
...digits === 0 ? { minimumFractionDigits: 0, maximumFractionDigits: 0 } : {}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return fmt.format(value);
|
|
58
|
+
} catch {
|
|
59
|
+
const amount = digits === 0 ? String(value) : value.toFixed(2);
|
|
60
|
+
return options.withoutCurrency ? amount : `${amount} ${money.currency_code}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function toMoney(value, currencyCode) {
|
|
64
|
+
return { amount: (Math.round(value * 100) / 100).toFixed(2), currency_code: currencyCode };
|
|
65
|
+
}
|
|
66
|
+
function isTokenPublishable(token) {
|
|
67
|
+
return typeof token === "string" && token.startsWith("pk_");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/query.ts
|
|
71
|
+
function appendParams(search, params) {
|
|
72
|
+
if (!params) return search;
|
|
73
|
+
for (const [key, raw] of Object.entries(params)) {
|
|
74
|
+
const values = Array.isArray(raw) ? raw : [raw];
|
|
75
|
+
for (const v of values) {
|
|
76
|
+
if (v === void 0 || v === null || v === "") continue;
|
|
77
|
+
search.append(key, String(v));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return search;
|
|
81
|
+
}
|
|
82
|
+
function productListQuery(params = {}) {
|
|
83
|
+
const { filters, handles, ...rest } = params;
|
|
84
|
+
const search = appendParams(new URLSearchParams(), rest);
|
|
85
|
+
if (handles && handles.length > 0) search.set("handles", handles.join(","));
|
|
86
|
+
if (filters) {
|
|
87
|
+
for (const [key, raw] of Object.entries(filters)) {
|
|
88
|
+
const name = key.startsWith("filter.") ? key : `filter.${key}`;
|
|
89
|
+
appendParams(search, { [name]: raw });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return search;
|
|
93
|
+
}
|
|
94
|
+
function parseProductFilters(search) {
|
|
95
|
+
const out = {};
|
|
96
|
+
for (const [key, value] of search) {
|
|
97
|
+
if (!key.startsWith("filter.")) continue;
|
|
98
|
+
const name = key.slice("filter.".length);
|
|
99
|
+
(out[name] ??= []).push(value);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/client.ts
|
|
105
|
+
var OWN_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "customerToken", "idempotencyKey", "preview", "signal"]);
|
|
106
|
+
function generateIdempotencyKey() {
|
|
107
|
+
const c = globalThis.crypto;
|
|
108
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
109
|
+
return "idem-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
110
|
+
}
|
|
111
|
+
function joinUrl(baseUrl, path) {
|
|
112
|
+
return baseUrl.replace(/\/+$/, "") + path;
|
|
113
|
+
}
|
|
114
|
+
function enc(segment) {
|
|
115
|
+
return encodeURIComponent(segment);
|
|
116
|
+
}
|
|
117
|
+
function linkSignals(signals) {
|
|
118
|
+
const active = signals.filter((s) => !!s);
|
|
119
|
+
if (active.length === 0) return { signal: void 0, cleanup: () => {
|
|
120
|
+
} };
|
|
121
|
+
if (active.length === 1) return { signal: active[0], cleanup: () => {
|
|
122
|
+
} };
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
const listeners = [];
|
|
125
|
+
for (const s of active) {
|
|
126
|
+
if (s.aborted) {
|
|
127
|
+
controller.abort(s.reason);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const on = () => controller.abort(s.reason);
|
|
131
|
+
s.addEventListener("abort", on, { once: true });
|
|
132
|
+
listeners.push([s, on]);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
cleanup: () => listeners.forEach(([s, on]) => s.removeEventListener("abort", on))
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function readError(res, url) {
|
|
140
|
+
let body;
|
|
141
|
+
try {
|
|
142
|
+
const text = await res.text();
|
|
143
|
+
body = text ? JSON.parse(text) : void 0;
|
|
144
|
+
} catch {
|
|
145
|
+
body = void 0;
|
|
146
|
+
}
|
|
147
|
+
const e = body?.error;
|
|
148
|
+
return new StorefrontError({
|
|
149
|
+
status: res.status,
|
|
150
|
+
code: e?.code ?? defaultCodeForStatus(res.status),
|
|
151
|
+
message: e?.message ?? `Storefront API responded ${res.status}${res.statusText ? " " + res.statusText : ""}`,
|
|
152
|
+
...e?.field ? { field: e.field } : {},
|
|
153
|
+
url
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function createStorefrontClient(config) {
|
|
157
|
+
if (!config.baseUrl) throw new TypeError("createStorefrontClient: baseUrl is required");
|
|
158
|
+
if (!config.token) throw new TypeError("createStorefrontClient: token is required");
|
|
159
|
+
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
160
|
+
if (isBrowser && !isTokenPublishable(config.token) && config.token.startsWith("sk_")) {
|
|
161
|
+
console.warn("[@asteby/storefront] A private sk_ token is being used in a browser. Use a public pk_ token instead.");
|
|
162
|
+
}
|
|
163
|
+
async function request(spec) {
|
|
164
|
+
const fetchImpl = config.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
165
|
+
if (!fetchImpl) throw new TypeError("createStorefrontClient: no fetch available; pass `fetch` in options");
|
|
166
|
+
const options = spec.options ?? {};
|
|
167
|
+
const search = spec.query instanceof URLSearchParams ? spec.query : appendParams(new URLSearchParams(), spec.query);
|
|
168
|
+
const qs = search.toString();
|
|
169
|
+
const url = joinUrl(config.baseUrl, spec.path) + (qs ? `?${qs}` : "");
|
|
170
|
+
const headers = new Headers(config.headers);
|
|
171
|
+
headers.set("Accept", "application/json");
|
|
172
|
+
headers.set("Authorization", `Bearer ${config.token}`);
|
|
173
|
+
if (config.locale) headers.set("Accept-Language", config.locale);
|
|
174
|
+
const customerToken = options.customerToken !== void 0 ? options.customerToken : config.customerToken;
|
|
175
|
+
if (customerToken) headers.set("X-Customer-Token", customerToken);
|
|
176
|
+
if (options.idempotencyKey) headers.set("Idempotency-Key", options.idempotencyKey);
|
|
177
|
+
if (spec.body !== void 0) headers.set("Content-Type", "application/json");
|
|
178
|
+
for (const [k, v] of Object.entries(options.headers ?? {})) headers.set(k, v);
|
|
179
|
+
const passthrough = {};
|
|
180
|
+
for (const [k, v] of Object.entries(options)) if (!OWN_OPTION_KEYS.has(k) && v !== void 0) passthrough[k] = v;
|
|
181
|
+
const timeoutMs = options.timeoutMs ?? config.timeoutMs;
|
|
182
|
+
const timeoutController = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
183
|
+
const timer = timeoutController ? setTimeout(() => timeoutController.abort(new StorefrontError({ status: 0, code: "timeout", message: `Request timed out after ${timeoutMs}ms`, url })), timeoutMs) : void 0;
|
|
184
|
+
const linked = linkSignals([options.signal, timeoutController?.signal]);
|
|
185
|
+
const init = {
|
|
186
|
+
...passthrough,
|
|
187
|
+
method: spec.method,
|
|
188
|
+
headers,
|
|
189
|
+
...spec.body !== void 0 ? { body: JSON.stringify(spec.body) } : {},
|
|
190
|
+
...linked.signal ? { signal: linked.signal } : {}
|
|
191
|
+
};
|
|
192
|
+
const ctx = { method: spec.method, url, path: spec.path, init, options };
|
|
193
|
+
const started = Date.now();
|
|
194
|
+
let res;
|
|
195
|
+
try {
|
|
196
|
+
const hooked = config.onRequest ? await config.onRequest(ctx) : void 0;
|
|
197
|
+
res = hooked instanceof Response ? hooked : await fetchImpl(url, init);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (timeoutController?.signal.aborted) {
|
|
200
|
+
throw new StorefrontError({ status: 0, code: "timeout", message: `Request timed out after ${timeoutMs}ms`, url, cause: err });
|
|
201
|
+
}
|
|
202
|
+
if (options.signal?.aborted) {
|
|
203
|
+
throw new StorefrontError({ status: 0, code: "aborted", message: "Request aborted", url, cause: err });
|
|
204
|
+
}
|
|
205
|
+
if (err instanceof StorefrontError) throw err;
|
|
206
|
+
throw new StorefrontError({ status: 0, code: "network", message: err instanceof Error ? err.message : "Network error", url, cause: err });
|
|
207
|
+
} finally {
|
|
208
|
+
if (timer) clearTimeout(timer);
|
|
209
|
+
linked.cleanup();
|
|
210
|
+
}
|
|
211
|
+
if (config.onResponse) await config.onResponse({ ...ctx, response: res, durationMs: Date.now() - started });
|
|
212
|
+
if (res.status === 404 && spec.nullOn404) {
|
|
213
|
+
await res.text().catch(() => void 0);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (res.status === 304) {
|
|
217
|
+
throw new StorefrontError({ status: 304, code: "not_modified", message: "Not modified", url });
|
|
218
|
+
}
|
|
219
|
+
if (!res.ok) throw await readError(res, url);
|
|
220
|
+
if (res.status === 204) return void 0;
|
|
221
|
+
const text = await res.text();
|
|
222
|
+
if (!text) return void 0;
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(text);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
throw new StorefrontError({ status: res.status, code: "invalid_response", message: "Response is not valid JSON", url, cause: err });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const get = (path, query, options) => request({ method: "GET", path, ...query ? { query } : {}, ...options ? { options } : {} });
|
|
230
|
+
const getOrNull = (path, query, options) => request({ method: "GET", path, nullOn404: true, ...query ? { query } : {}, ...options ? { options } : {} });
|
|
231
|
+
const write = (method, path, body, options) => request({ method, path, ...body !== void 0 ? { body } : {}, ...options ? { options } : {} });
|
|
232
|
+
const cartPath = (id, rest = "") => `/storefront/carts/${enc(id)}${rest}`;
|
|
233
|
+
const previewQuery = (options) => options?.preview ? { preview: options.preview } : void 0;
|
|
234
|
+
const client = {
|
|
235
|
+
/** Resolved configuration (read-only). */
|
|
236
|
+
config: Object.freeze({ ...config }),
|
|
237
|
+
/** Low-level escape hatch: any route under `baseUrl`. */
|
|
238
|
+
request,
|
|
239
|
+
/** Returns a new client that sends the given buyer session token. */
|
|
240
|
+
withCustomerToken(customerToken) {
|
|
241
|
+
return createStorefrontClient({ ...config, customerToken });
|
|
242
|
+
},
|
|
243
|
+
shop: {
|
|
244
|
+
get: (options) => get("/storefront/shop", void 0, options)
|
|
245
|
+
},
|
|
246
|
+
products: {
|
|
247
|
+
list: (params, options) => get("/storefront/products", productListQuery(params), options),
|
|
248
|
+
/** `null` when the product does not exist or is unpublished. */
|
|
249
|
+
get: (handle, options) => getOrNull(`/storefront/products/${enc(handle)}`, void 0, options),
|
|
250
|
+
recommendations: (handle, options) => get(`/storefront/products/${enc(handle)}/recommendations`, void 0, options)
|
|
251
|
+
},
|
|
252
|
+
collections: {
|
|
253
|
+
list: (params, options) => get("/storefront/collections", params, options),
|
|
254
|
+
/** `null` when the collection does not exist. */
|
|
255
|
+
get: (handle, options) => getOrNull(`/storefront/collections/${enc(handle)}`, void 0, options)
|
|
256
|
+
},
|
|
257
|
+
stock: {
|
|
258
|
+
/** Stock for up to 250 SKUs. */
|
|
259
|
+
get: (skus, options) => get("/storefront/stock", { sku: skus }, options)
|
|
260
|
+
},
|
|
261
|
+
carts: {
|
|
262
|
+
create: (input = {}, options) => write("POST", "/storefront/carts", input, options),
|
|
263
|
+
/** `null` when the cart does not exist (e.g. expired cookie). */
|
|
264
|
+
get: (id, options) => getOrNull(cartPath(id), void 0, options),
|
|
265
|
+
addLines: (id, lines, options) => write("POST", cartPath(id, "/lines"), { lines }, options),
|
|
266
|
+
/** Quantity `0` removes the line. */
|
|
267
|
+
updateLines: (id, lines, options) => write("PATCH", cartPath(id, "/lines"), { lines }, options),
|
|
268
|
+
removeLines: (id, lineIds, options) => write("PATCH", cartPath(id, "/lines"), { lines: lineIds.map((lid) => ({ id: lid, quantity: 0 })) }, options),
|
|
269
|
+
setDiscountCodes: (id, codes, options) => write("PUT", cartPath(id, "/discount-codes"), { codes }, options),
|
|
270
|
+
setBuyer: (id, buyer, options) => write("PUT", cartPath(id, "/buyer"), buyer, options),
|
|
271
|
+
setShippingAddress: (id, address, options) => write("PUT", cartPath(id, "/shipping-address"), address, options),
|
|
272
|
+
deliveryOptions: (id, options) => get(cartPath(id, "/delivery-options"), void 0, options),
|
|
273
|
+
/** Payment providers active for the cart's market; send the chosen `key` as `payment_method`. */
|
|
274
|
+
paymentMethods: (id, options) => get(cartPath(id, "/payment-methods"), void 0, options),
|
|
275
|
+
setDelivery: (id, delivery, options) => write("PUT", cartPath(id, "/delivery"), delivery, options),
|
|
276
|
+
/** Reserves stock and creates the order. Sends `Idempotency-Key` (auto-generated if omitted). */
|
|
277
|
+
checkout: (id, input, options = {}) => write("POST", cartPath(id, "/checkout"), input, {
|
|
278
|
+
...options,
|
|
279
|
+
idempotencyKey: options.idempotencyKey ?? generateIdempotencyKey()
|
|
280
|
+
})
|
|
281
|
+
},
|
|
282
|
+
customer: {
|
|
283
|
+
/** Sends a one-time code by email or SMS/WhatsApp. */
|
|
284
|
+
requestOtp: (identifier, options) => write("POST", "/storefront/customer/otp", identifier, options),
|
|
285
|
+
/** Exchanges the code for a buyer session (`token` goes in `customerToken`). */
|
|
286
|
+
createSession: (input, options) => write("POST", "/storefront/customer/session", input, options),
|
|
287
|
+
get: (options) => get("/storefront/customer", void 0, options),
|
|
288
|
+
orders: (params, options) => get("/storefront/customer/orders", params, options),
|
|
289
|
+
/** `null` when the order does not exist or belongs to someone else. */
|
|
290
|
+
order: (id, options) => getOrNull(`/storefront/customer/orders/${enc(id)}`, void 0, options),
|
|
291
|
+
requestReturn: (orderId, input, options) => write("POST", `/storefront/customer/orders/${enc(orderId)}/returns`, input, options),
|
|
292
|
+
requestInvoice: (orderId, input, options) => write("POST", `/storefront/customer/orders/${enc(orderId)}/invoice`, input, options),
|
|
293
|
+
addresses: {
|
|
294
|
+
list: (options) => get("/storefront/customer/addresses", void 0, options),
|
|
295
|
+
create: (address, options) => write("POST", "/storefront/customer/addresses", address, options),
|
|
296
|
+
update: (id, address, options) => write("PATCH", `/storefront/customer/addresses/${enc(id)}`, address, options),
|
|
297
|
+
delete: (id, options) => write("DELETE", `/storefront/customer/addresses/${enc(id)}`, void 0, options)
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
content: {
|
|
301
|
+
theme: (options) => get("/storefront/content/theme", void 0, options),
|
|
302
|
+
/** `null` when the page does not exist. */
|
|
303
|
+
page: (handle, options) => getOrNull(`/storefront/content/pages/${enc(handle)}`, previewQuery(options), options),
|
|
304
|
+
/** `null` when the menu does not exist. */
|
|
305
|
+
menu: (handle, options) => getOrNull(`/storefront/content/menus/${enc(handle)}`, void 0, options),
|
|
306
|
+
articles: (params, options) => get("/storefront/content/articles", params, options),
|
|
307
|
+
/** `null` when the article does not exist. */
|
|
308
|
+
article: (handle, options) => getOrNull(`/storefront/content/articles/${enc(handle)}`, previewQuery(options), options)
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
return client;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
exports.StorefrontError = StorefrontError;
|
|
315
|
+
exports.appendParams = appendParams;
|
|
316
|
+
exports.createStorefrontClient = createStorefrontClient;
|
|
317
|
+
exports.formatMoney = formatMoney;
|
|
318
|
+
exports.generateIdempotencyKey = generateIdempotencyKey;
|
|
319
|
+
exports.isStorefrontError = isStorefrontError;
|
|
320
|
+
exports.isTokenPublishable = isTokenPublishable;
|
|
321
|
+
exports.parseMoney = parseMoney;
|
|
322
|
+
exports.parseProductFilters = parseProductFilters;
|
|
323
|
+
exports.productListQuery = productListQuery;
|
|
324
|
+
exports.toMoney = toMoney;
|
|
325
|
+
//# sourceMappingURL=index.cjs.map
|
|
326
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/money.ts","../src/query.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { StorefrontErrorCode } from './types.js'\n\nexport type StorefrontErrorInit = {\n status: number\n code: StorefrontErrorCode\n message: string\n field?: string\n /** Request URL that failed (without credentials). */\n url?: string\n cause?: unknown\n}\n\n/**\n * Error thrown by the client for any non-2xx response, network failure,\n * timeout or abort. `status` is `0` when no HTTP response was received.\n */\nexport class StorefrontError extends Error {\n override readonly name = 'StorefrontError'\n readonly status: number\n readonly code: StorefrontErrorCode\n readonly field: string | undefined\n readonly url: string | undefined\n\n constructor(init: StorefrontErrorInit) {\n super(init.message, init.cause === undefined ? undefined : { cause: init.cause })\n this.status = init.status\n this.code = init.code\n this.field = init.field\n this.url = init.url\n }\n\n toJSON() {\n return { name: this.name, status: this.status, code: this.code, message: this.message, field: this.field }\n }\n}\n\nexport function isStorefrontError(err: unknown): err is StorefrontError {\n return err instanceof StorefrontError || (typeof err === 'object' && err !== null && (err as { name?: unknown }).name === 'StorefrontError')\n}\n\nconst STATUS_CODES: Record<number, StorefrontErrorCode> = {\n 400: 'validation',\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not_found',\n 409: 'conflict',\n 422: 'invalid_state',\n 429: 'rate_limited',\n}\n\nexport function defaultCodeForStatus(status: number): StorefrontErrorCode {\n return STATUS_CODES[status] ?? (status >= 500 ? 'server_error' : 'invalid_response')\n}\n","import type { Money } from './types.js'\n\nexport type FormatMoneyOptions = {\n /** Drop `.00` when the amount is a whole number. */\n withoutTrailingZeros?: boolean\n /** Format as a plain number, without the currency symbol. */\n withoutCurrency?: boolean\n /** `symbol` (default), `narrowSymbol`, `code` or `name`. */\n currencyDisplay?: 'symbol' | 'narrowSymbol' | 'code' | 'name'\n}\n\n/** Parses a contract `Money` amount into a JS number (`\"1899.00\"` → `1899`). */\nexport function parseMoney(money: Money | string | null | undefined): number {\n if (money == null) return 0\n const raw = typeof money === 'string' ? money : money.amount\n const n = Number.parseFloat(raw)\n return Number.isFinite(n) ? n : 0\n}\n\n/**\n * Formats `Money` with `Intl.NumberFormat`. Works in every runtime (no DOM).\n * Falls back to `\"<amount> <code>\"` when the runtime lacks the currency.\n */\nexport function formatMoney(money: Money, locale?: string, options: FormatMoneyOptions = {}): string {\n const value = parseMoney(money)\n const whole = Number.isInteger(value)\n const digits = options.withoutTrailingZeros && whole ? 0 : undefined\n try {\n const fmt = new Intl.NumberFormat(locale, {\n ...(options.withoutCurrency\n ? { style: 'decimal', minimumFractionDigits: digits ?? 2, maximumFractionDigits: digits ?? 2 }\n : {\n style: 'currency',\n currency: money.currency_code,\n currencyDisplay: options.currencyDisplay ?? 'symbol',\n ...(digits === 0 ? { minimumFractionDigits: 0, maximumFractionDigits: 0 } : {}),\n }),\n })\n return fmt.format(value)\n } catch {\n const amount = digits === 0 ? String(value) : value.toFixed(2)\n return options.withoutCurrency ? amount : `${amount} ${money.currency_code}`\n }\n}\n\n/** Builds a `Money` from a number, rounding to cents. */\nexport function toMoney(value: number, currencyCode: string): Money {\n return { amount: (Math.round(value * 100) / 100).toFixed(2), currency_code: currencyCode }\n}\n\n/**\n * True for public (`pk_`) tokens, the only ones that may reach a browser.\n * Private (`sk_`) tokens must stay on the server.\n */\nexport function isTokenPublishable(token: string | null | undefined): boolean {\n return typeof token === 'string' && token.startsWith('pk_')\n}\n","import type { ProductSort } from './types.js'\n\nexport type QueryValue = string | number | boolean | null | undefined\nexport type QueryParams = Record<string, QueryValue | QueryValue[]>\n\nexport type PaginationParams = {\n /** Page size (1–100; articles accept at most 50). */\n first?: number\n /** Cursor from `page_info.end_cursor`. */\n after?: string | null\n}\n\nexport type ProductListParams = PaginationParams & {\n /** Free-text search. */\n q?: string\n /** Collection handle. */\n collection?: string\n sort?: ProductSort\n /** Only products available for sale. */\n available?: boolean\n price_min?: number | string\n price_max?: number | string\n /**\n * Attribute filters, serialized as repeated `filter.<key>=<value>` params.\n * Values of one key are OR-ed; different keys are AND-ed.\n * e.g. `{ vendor: ['A', 'B'], rim: 16 }`.\n */\n filters?: Record<string, QueryValue | QueryValue[]>\n /** Restrict to these handles (max 100). Serialized comma-separated. */\n handles?: string[]\n}\n\nexport type CollectionListParams = PaginationParams\n\nexport type ArticleListParams = Omit<PaginationParams, 'first'> & {\n /** Page size (1–50). The API answers 400 `validation` above 50. */\n first?: number\n tag?: string\n q?: string\n}\n\n/** Appends params to a URLSearchParams, repeating keys for arrays and skipping nullish values. */\nexport function appendParams(search: URLSearchParams, params: QueryParams | undefined): URLSearchParams {\n if (!params) return search\n for (const [key, raw] of Object.entries(params)) {\n const values = Array.isArray(raw) ? raw : [raw]\n for (const v of values) {\n if (v === undefined || v === null || v === '') continue\n search.append(key, String(v))\n }\n }\n return search\n}\n\n/** Serializes product list params per the contract (`filter.<attr>` repeated, `handles` comma-separated). */\nexport function productListQuery(params: ProductListParams = {}): URLSearchParams {\n const { filters, handles, ...rest } = params\n const search = appendParams(new URLSearchParams(), rest as QueryParams)\n if (handles && handles.length > 0) search.set('handles', handles.join(','))\n if (filters) {\n for (const [key, raw] of Object.entries(filters)) {\n const name = key.startsWith('filter.') ? key : `filter.${key}`\n appendParams(search, { [name]: raw })\n }\n }\n return search\n}\n\n/** Reads `filter.<key>` params back into a filters record (useful in route loaders). */\nexport function parseProductFilters(search: URLSearchParams): Record<string, string[]> {\n const out: Record<string, string[]> = {}\n for (const [key, value] of search) {\n if (!key.startsWith('filter.')) continue\n const name = key.slice('filter.'.length)\n ;(out[name] ??= []).push(value)\n }\n return out\n}\n","import { StorefrontError, defaultCodeForStatus } from './errors.js'\nimport { isTokenPublishable } from './money.js'\nimport {\n appendParams,\n productListQuery,\n type ArticleListParams,\n type CollectionListParams,\n type PaginationParams,\n type ProductListParams,\n type QueryParams,\n} from './query.js'\nimport type {\n Address,\n AddressInput,\n Article,\n Cart,\n CartBuyerInput,\n CartLineInput,\n CartLineUpdateInput,\n CheckoutInput,\n CheckoutResult,\n Collection,\n Connection,\n Customer,\n CustomerIdentifier,\n CustomerSession,\n CustomerSessionInput,\n DeliveryInput,\n DeliveryOptions,\n PaymentMethods,\n InvoiceRequest,\n InvoiceResult,\n Menu,\n Order,\n Page,\n Product,\n ProductConnection,\n ReturnRequest,\n ReturnRequestInput,\n Shop,\n StockLevel,\n StorefrontErrorBody,\n Theme,\n} from './types.js'\n\nexport type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>\n\n/** Context passed to `onRequest` / `onResponse` hooks. */\nexport type StorefrontRequestContext = {\n method: string\n /** Absolute URL, including the query string. */\n url: string\n /** Route path relative to `baseUrl`, e.g. `/storefront/products/abc`. */\n path: string\n /** Final `RequestInit` (headers already merged). Hooks may mutate it. */\n init: RequestInit & { headers: Headers }\n /** Per-call options as given by the caller. */\n options: RequestOptions\n}\n\nexport type StorefrontResponseContext = StorefrontRequestContext & {\n response: Response\n /** Milliseconds spent waiting for the response. */\n durationMs: number\n}\n\nexport type StorefrontClientOptions = {\n /** Base URL of the gateway, e.g. `https://ops.example.com/api/gw`. Routes are appended as `/storefront/...`. */\n baseUrl: string\n /** `pk_...` (browser) or `sk_...` (server only). Sent as `Authorization: Bearer <token>`. */\n token: string\n /** Buyer session (`X-Customer-Token`) for `/storefront/customer/*` routes. */\n customerToken?: string | null\n /** Custom fetch (defaults to `globalThis.fetch`). */\n fetch?: FetchLike\n /** Sent as `Accept-Language`. */\n locale?: string\n /** Extra headers on every request. */\n headers?: Record<string, string>\n /** Abort requests that take longer than this (default: no timeout). */\n timeoutMs?: number\n /**\n * Runs before every request. Return a `Response` to short-circuit the\n * network (e.g. an SSR cache hit); return nothing to continue.\n */\n onRequest?: (ctx: StorefrontRequestContext) => void | Response | Promise<void | Response>\n /** Runs after every response (including short-circuited ones), before parsing. */\n onResponse?: (ctx: StorefrontResponseContext) => void | Promise<void>\n}\n\n/** Per-call options. Unknown keys (e.g. Next.js `next`) are forwarded to `fetch` as-is. */\nexport type RequestOptions = {\n cache?: RequestCache\n signal?: AbortSignal\n /** Framework fetch extensions, forwarded verbatim (e.g. `{ revalidate: 60, tags: ['products'] }`). */\n next?: Record<string, unknown>\n headers?: Record<string, string>\n /** Overrides the client-level timeout for this call. */\n timeoutMs?: number\n /** Overrides the client-level customer token for this call. */\n customerToken?: string | null\n [key: string]: unknown\n}\n\nexport type WriteOptions = RequestOptions & {\n /** `Idempotency-Key` header. Auto-generated for checkout when omitted. */\n idempotencyKey?: string\n}\n\nexport type PreviewOptions = RequestOptions & {\n /** Signed preview token: returns the draft (`private, no-store`). */\n preview?: string\n}\n\ntype HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'\n\ntype RequestSpec = {\n method: HttpMethod\n path: string\n query?: URLSearchParams | QueryParams\n body?: unknown\n options?: WriteOptions\n /** Resolve `null` instead of throwing on 404. */\n nullOn404?: boolean\n}\n\nconst OWN_OPTION_KEYS = new Set(['headers', 'timeoutMs', 'customerToken', 'idempotencyKey', 'preview', 'signal'])\n\n/** Generates an idempotency key (crypto.randomUUID when available). */\nexport function generateIdempotencyKey(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (c?.randomUUID) return c.randomUUID()\n // Non-cryptographic fallback for very old runtimes; the key only needs to be unique.\n return 'idem-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n return baseUrl.replace(/\\/+$/, '') + path\n}\n\nfunction enc(segment: string): string {\n return encodeURIComponent(segment)\n}\n\nfunction linkSignals(signals: (AbortSignal | undefined)[]): { signal: AbortSignal | undefined; cleanup: () => void } {\n const active = signals.filter((s): s is AbortSignal => !!s)\n if (active.length === 0) return { signal: undefined, cleanup: () => {} }\n if (active.length === 1) return { signal: active[0], cleanup: () => {} }\n const controller = new AbortController()\n const listeners: [AbortSignal, () => void][] = []\n for (const s of active) {\n if (s.aborted) {\n controller.abort(s.reason)\n break\n }\n const on = () => controller.abort(s.reason)\n s.addEventListener('abort', on, { once: true })\n listeners.push([s, on])\n }\n return {\n signal: controller.signal,\n cleanup: () => listeners.forEach(([s, on]) => s.removeEventListener('abort', on)),\n }\n}\n\nasync function readError(res: Response, url: string): Promise<StorefrontError> {\n let body: Partial<StorefrontErrorBody> | undefined\n try {\n const text = await res.text()\n body = text ? (JSON.parse(text) as Partial<StorefrontErrorBody>) : undefined\n } catch {\n body = undefined\n }\n const e = body?.error\n return new StorefrontError({\n status: res.status,\n code: e?.code ?? defaultCodeForStatus(res.status),\n message: e?.message ?? `Storefront API responded ${res.status}${res.statusText ? ' ' + res.statusText : ''}`,\n ...(e?.field ? { field: e.field } : {}),\n url,\n })\n}\n\nexport function createStorefrontClient(config: StorefrontClientOptions) {\n if (!config.baseUrl) throw new TypeError('createStorefrontClient: baseUrl is required')\n if (!config.token) throw new TypeError('createStorefrontClient: token is required')\n const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'\n if (isBrowser && !isTokenPublishable(config.token) && config.token.startsWith('sk_')) {\n console.warn('[@asteby/storefront] A private sk_ token is being used in a browser. Use a public pk_ token instead.')\n }\n\n async function request<T>(spec: RequestSpec & { nullOn404: true }): Promise<T | null>\n async function request<T>(spec: RequestSpec): Promise<T>\n async function request<T>(spec: RequestSpec): Promise<T | null> {\n const fetchImpl: FetchLike | undefined = config.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : undefined)\n if (!fetchImpl) throw new TypeError('createStorefrontClient: no fetch available; pass `fetch` in options')\n\n const options: WriteOptions = spec.options ?? {}\n const search = spec.query instanceof URLSearchParams ? spec.query : appendParams(new URLSearchParams(), spec.query)\n const qs = search.toString()\n const url = joinUrl(config.baseUrl, spec.path) + (qs ? `?${qs}` : '')\n\n const headers = new Headers(config.headers)\n headers.set('Accept', 'application/json')\n headers.set('Authorization', `Bearer ${config.token}`)\n if (config.locale) headers.set('Accept-Language', config.locale)\n const customerToken = options.customerToken !== undefined ? options.customerToken : config.customerToken\n if (customerToken) headers.set('X-Customer-Token', customerToken)\n if (options.idempotencyKey) headers.set('Idempotency-Key', options.idempotencyKey)\n if (spec.body !== undefined) headers.set('Content-Type', 'application/json')\n for (const [k, v] of Object.entries(options.headers ?? {})) headers.set(k, v)\n\n // Forward passthrough options (cache, next, ...) to fetch.\n const passthrough: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(options)) if (!OWN_OPTION_KEYS.has(k) && v !== undefined) passthrough[k] = v\n\n const timeoutMs = options.timeoutMs ?? config.timeoutMs\n const timeoutController = timeoutMs && timeoutMs > 0 ? new AbortController() : undefined\n const timer = timeoutController\n ? setTimeout(() => timeoutController.abort(new StorefrontError({ status: 0, code: 'timeout', message: `Request timed out after ${timeoutMs}ms`, url })), timeoutMs)\n : undefined\n const linked = linkSignals([options.signal, timeoutController?.signal])\n\n const init: RequestInit & { headers: Headers } = {\n ...passthrough,\n method: spec.method,\n headers,\n ...(spec.body !== undefined ? { body: JSON.stringify(spec.body) } : {}),\n ...(linked.signal ? { signal: linked.signal } : {}),\n }\n const ctx: StorefrontRequestContext = { method: spec.method, url, path: spec.path, init, options }\n\n const started = Date.now()\n let res: Response\n try {\n const hooked = config.onRequest ? await config.onRequest(ctx) : undefined\n res = hooked instanceof Response ? hooked : await fetchImpl(url, init)\n } catch (err) {\n if (timeoutController?.signal.aborted) {\n throw new StorefrontError({ status: 0, code: 'timeout', message: `Request timed out after ${timeoutMs}ms`, url, cause: err })\n }\n if (options.signal?.aborted) {\n throw new StorefrontError({ status: 0, code: 'aborted', message: 'Request aborted', url, cause: err })\n }\n if (err instanceof StorefrontError) throw err\n throw new StorefrontError({ status: 0, code: 'network', message: err instanceof Error ? err.message : 'Network error', url, cause: err })\n } finally {\n if (timer) clearTimeout(timer)\n linked.cleanup()\n }\n\n if (config.onResponse) await config.onResponse({ ...ctx, response: res, durationMs: Date.now() - started })\n\n if (res.status === 404 && spec.nullOn404) {\n // Drain the body so the connection can be reused.\n await res.text().catch(() => undefined)\n return null\n }\n if (res.status === 304) {\n throw new StorefrontError({ status: 304, code: 'not_modified', message: 'Not modified', url })\n }\n if (!res.ok) throw await readError(res, url)\n if (res.status === 204) return undefined as T\n\n const text = await res.text()\n if (!text) return undefined as T\n try {\n return JSON.parse(text) as T\n } catch (err) {\n throw new StorefrontError({ status: res.status, code: 'invalid_response', message: 'Response is not valid JSON', url, cause: err })\n }\n }\n\n const get = <T>(path: string, query?: URLSearchParams | QueryParams, options?: RequestOptions) =>\n request<T>({ method: 'GET', path, ...(query ? { query } : {}), ...(options ? { options } : {}) })\n const getOrNull = <T>(path: string, query?: URLSearchParams | QueryParams, options?: RequestOptions) =>\n request<T>({ method: 'GET', path, nullOn404: true, ...(query ? { query } : {}), ...(options ? { options } : {}) })\n const write = <T>(method: HttpMethod, path: string, body?: unknown, options?: WriteOptions) =>\n request<T>({ method, path, ...(body !== undefined ? { body } : {}), ...(options ? { options } : {}) })\n\n const cartPath = (id: string, rest = '') => `/storefront/carts/${enc(id)}${rest}`\n const previewQuery = (options?: PreviewOptions) => (options?.preview ? { preview: options.preview } : undefined)\n\n const client = {\n /** Resolved configuration (read-only). */\n config: Object.freeze({ ...config }) as Readonly<StorefrontClientOptions>,\n\n /** Low-level escape hatch: any route under `baseUrl`. */\n request,\n\n /** Returns a new client that sends the given buyer session token. */\n withCustomerToken(customerToken: string | null) {\n return createStorefrontClient({ ...config, customerToken })\n },\n\n shop: {\n get: (options?: RequestOptions) => get<Shop>('/storefront/shop', undefined, options),\n },\n\n products: {\n list: (params?: ProductListParams, options?: RequestOptions) =>\n get<ProductConnection>('/storefront/products', productListQuery(params), options),\n /** `null` when the product does not exist or is unpublished. */\n get: (handle: string, options?: RequestOptions) =>\n getOrNull<Product>(`/storefront/products/${enc(handle)}`, undefined, options),\n recommendations: (handle: string, options?: RequestOptions) =>\n get<{ nodes: Product[] }>(`/storefront/products/${enc(handle)}/recommendations`, undefined, options),\n },\n\n collections: {\n list: (params?: CollectionListParams, options?: RequestOptions) =>\n get<Connection<Collection>>('/storefront/collections', params as QueryParams, options),\n /** `null` when the collection does not exist. */\n get: (handle: string, options?: RequestOptions) =>\n getOrNull<Collection>(`/storefront/collections/${enc(handle)}`, undefined, options),\n },\n\n stock: {\n /** Stock for up to 250 SKUs. */\n get: (skus: string[], options?: RequestOptions) =>\n get<{ nodes: StockLevel[] }>('/storefront/stock', { sku: skus }, options),\n },\n\n carts: {\n create: (input: { lines?: CartLineInput[] } = {}, options?: WriteOptions) =>\n write<Cart>('POST', '/storefront/carts', input, options),\n /** `null` when the cart does not exist (e.g. expired cookie). */\n get: (id: string, options?: RequestOptions) => getOrNull<Cart>(cartPath(id), undefined, options),\n addLines: (id: string, lines: CartLineInput[], options?: WriteOptions) =>\n write<Cart>('POST', cartPath(id, '/lines'), { lines }, options),\n /** Quantity `0` removes the line. */\n updateLines: (id: string, lines: CartLineUpdateInput[], options?: WriteOptions) =>\n write<Cart>('PATCH', cartPath(id, '/lines'), { lines }, options),\n removeLines: (id: string, lineIds: string[], options?: WriteOptions) =>\n write<Cart>('PATCH', cartPath(id, '/lines'), { lines: lineIds.map((lid) => ({ id: lid, quantity: 0 })) }, options),\n setDiscountCodes: (id: string, codes: string[], options?: WriteOptions) =>\n write<Cart>('PUT', cartPath(id, '/discount-codes'), { codes }, options),\n setBuyer: (id: string, buyer: CartBuyerInput, options?: WriteOptions) =>\n write<Cart>('PUT', cartPath(id, '/buyer'), buyer, options),\n setShippingAddress: (id: string, address: AddressInput, options?: WriteOptions) =>\n write<Cart>('PUT', cartPath(id, '/shipping-address'), address, options),\n deliveryOptions: (id: string, options?: RequestOptions) =>\n get<DeliveryOptions>(cartPath(id, '/delivery-options'), undefined, options),\n /** Payment providers active for the cart's market; send the chosen `key` as `payment_method`. */\n paymentMethods: (id: string, options?: RequestOptions) =>\n get<PaymentMethods>(cartPath(id, '/payment-methods'), undefined, options),\n setDelivery: (id: string, delivery: DeliveryInput, options?: WriteOptions) =>\n write<Cart>('PUT', cartPath(id, '/delivery'), delivery, options),\n /** Reserves stock and creates the order. Sends `Idempotency-Key` (auto-generated if omitted). */\n checkout: (id: string, input: CheckoutInput, options: WriteOptions = {}) =>\n write<CheckoutResult>('POST', cartPath(id, '/checkout'), input, {\n ...options,\n idempotencyKey: options.idempotencyKey ?? generateIdempotencyKey(),\n }),\n },\n\n customer: {\n /** Sends a one-time code by email or SMS/WhatsApp. */\n requestOtp: (identifier: CustomerIdentifier, options?: WriteOptions) =>\n write<void>('POST', '/storefront/customer/otp', identifier, options),\n /** Exchanges the code for a buyer session (`token` goes in `customerToken`). */\n createSession: (input: CustomerSessionInput, options?: WriteOptions) =>\n write<CustomerSession>('POST', '/storefront/customer/session', input, options),\n get: (options?: RequestOptions) => get<Customer>('/storefront/customer', undefined, options),\n orders: (params?: PaginationParams, options?: RequestOptions) =>\n get<Connection<Order>>('/storefront/customer/orders', params as QueryParams, options),\n /** `null` when the order does not exist or belongs to someone else. */\n order: (id: string, options?: RequestOptions) =>\n getOrNull<Order>(`/storefront/customer/orders/${enc(id)}`, undefined, options),\n requestReturn: (orderId: string, input: ReturnRequestInput, options?: WriteOptions) =>\n write<ReturnRequest>('POST', `/storefront/customer/orders/${enc(orderId)}/returns`, input, options),\n requestInvoice: (orderId: string, input: InvoiceRequest, options?: WriteOptions) =>\n write<InvoiceResult>('POST', `/storefront/customer/orders/${enc(orderId)}/invoice`, input, options),\n addresses: {\n list: (options?: RequestOptions) => get<{ nodes: Address[] }>('/storefront/customer/addresses', undefined, options),\n create: (address: AddressInput, options?: WriteOptions) =>\n write<Address>('POST', '/storefront/customer/addresses', address, options),\n update: (id: string, address: Partial<AddressInput>, options?: WriteOptions) =>\n write<Address>('PATCH', `/storefront/customer/addresses/${enc(id)}`, address, options),\n delete: (id: string, options?: WriteOptions) =>\n write<void>('DELETE', `/storefront/customer/addresses/${enc(id)}`, undefined, options),\n },\n },\n\n content: {\n theme: (options?: RequestOptions) => get<Theme>('/storefront/content/theme', undefined, options),\n /** `null` when the page does not exist. */\n page: (handle: string, options?: PreviewOptions) =>\n getOrNull<Page>(`/storefront/content/pages/${enc(handle)}`, previewQuery(options), options),\n /** `null` when the menu does not exist. */\n menu: (handle: string, options?: RequestOptions) =>\n getOrNull<Menu>(`/storefront/content/menus/${enc(handle)}`, undefined, options),\n articles: (params?: ArticleListParams, options?: RequestOptions) =>\n get<Connection<Article>>('/storefront/content/articles', params as QueryParams, options),\n /** `null` when the article does not exist. */\n article: (handle: string, options?: PreviewOptions) =>\n getOrNull<Article>(`/storefront/content/articles/${enc(handle)}`, previewQuery(options), options),\n },\n }\n return client\n}\n\nexport type StorefrontClient = ReturnType<typeof createStorefrontClient>\n"]}
|