@asteby/storefront 0.4.0 → 0.5.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/README.md +31 -1
- package/dist/index.cjs +27 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/mockup.cjs +218 -0
- package/dist/mockup.cjs.map +1 -0
- package/dist/mockup.d.cts +142 -0
- package/dist/mockup.d.ts +142 -0
- package/dist/mockup.js +205 -0
- package/dist/mockup.js.map +1 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -177,6 +177,26 @@ Guadalajara) ordered by haversine; it knows the centroid of a few postal codes
|
|
|
177
177
|
(`MOCK_POSTAL_CENTROIDS`), pickup is offered only where `pickup.enabled`, and
|
|
178
178
|
the pickup `fee` is charged on the cart.
|
|
179
179
|
|
|
180
|
+
## Mockup data (`@asteby/storefront/mockup`)
|
|
181
|
+
|
|
182
|
+
Pure helpers behind the fallback mockups of `@asteby/storefront-react/mockups`
|
|
183
|
+
(products without an image): which illustration to draw and what it may show.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { resolveMockupKind, mockupDataFromProduct, cleanMockupText } from '@asteby/storefront/mockup'
|
|
187
|
+
|
|
188
|
+
resolveMockupKind(product) // 'tire' | 'part' | 'supplement' | 'generic' | a custom kind
|
|
189
|
+
resolveMockupKind(product, { rules, kinds, fallback })
|
|
190
|
+
mockupDataFromProduct('tire', product) // { size, width, aspect, rim, service, model, brand, lightTruck, runFlat, title }
|
|
191
|
+
mockupDataFromProduct('supplement', product, variant) // flavor/presentation from the selected variant
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Resolution: `mockup_kind` attribute (set in OPS) → attribute rules (tire specs,
|
|
195
|
+
part numbers) → words in `product_type`, category and tags (never the title) →
|
|
196
|
+
`generic`. Extractors only read the product (attributes, `vendor`, title,
|
|
197
|
+
options, variant), never prices, and never fill a missing value; every string
|
|
198
|
+
is cleaned and capped (`MOCKUP_TEXT_LIMITS`).
|
|
199
|
+
|
|
180
200
|
## API
|
|
181
201
|
|
|
182
202
|
`createStorefrontClient(options)`:
|
|
@@ -186,6 +206,7 @@ the pickup `fee` is charged on the cart.
|
|
|
186
206
|
| `baseUrl` | Gateway base, e.g. `https://ops.example.com/api/gw` |
|
|
187
207
|
| `token` | `pk_...` or `sk_...` → `Authorization: Bearer` |
|
|
188
208
|
| `customerToken?` | Buyer session → `X-Customer-Token` |
|
|
209
|
+
| `clientIp?` | Shopper IP for server-side calls → `X-Storefront-Client-IP` (see below) |
|
|
189
210
|
| `fetch?` | Custom fetch (default `globalThis.fetch`) |
|
|
190
211
|
| `locale?` | `Accept-Language` |
|
|
191
212
|
| `headers?` | Extra headers on every request |
|
|
@@ -198,10 +219,19 @@ Resources: `shop.get`, `products.list/get/recommendations`,
|
|
|
198
219
|
`carts.create/get/addLines/updateLines/removeLines/setDiscountCodes/setBuyer/setShippingAddress/deliveryOptions/setDelivery/checkout`,
|
|
199
220
|
`customer.requestOtp/createSession/get/orders/order/requestReturn/requestInvoice/addresses.{list,create,update,delete}`,
|
|
200
221
|
`content.theme/page/menu/articles/article`. Every method takes a last
|
|
201
|
-
`options` argument: `{ cache, signal, next, headers, timeoutMs, customerToken }`
|
|
222
|
+
`options` argument: `{ cache, signal, next, headers, timeoutMs, customerToken, clientIp }`
|
|
202
223
|
(plus `idempotencyKey` on writes and `preview` on pages/articles); unknown keys
|
|
203
224
|
are forwarded to `fetch`.
|
|
204
225
|
|
|
226
|
+
**Shopper IP (SSR).** A server that renders for many shoppers with one `sk_`/`pt_`
|
|
227
|
+
token shares a single rate-limit bucket, and the OTP limiter would see only the
|
|
228
|
+
server's address. Issue the token with the `trust_client_ip` option (OPS
|
|
229
|
+
`opts:trust_client_ip` scope) and pass the shopper's IP taken from your own
|
|
230
|
+
connection or trusted proxy (never from a browser-forgeable header):
|
|
231
|
+
`createStorefrontClient({ ..., clientIp })`, `client.withClientIp(ip)` or
|
|
232
|
+
`{ clientIp }` per call. It is sent as `X-Storefront-Client-IP`, never with a
|
|
233
|
+
`pk_` token, and invalid values (lists, ports, text) are dropped.
|
|
234
|
+
|
|
205
235
|
`get`-by-handle methods (`products.get`, `collections.get`, `carts.get`,
|
|
206
236
|
`customer.order`, `content.page/menu/article`) resolve `null` on 404. Every
|
|
207
237
|
other failure throws `StorefrontError` with `status`, `code`
|
package/dist/index.cjs
CHANGED
|
@@ -106,7 +106,24 @@ function parseProductFilters(search) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// src/client.ts
|
|
109
|
-
var OWN_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "customerToken", "idempotencyKey", "preview", "signal"]);
|
|
109
|
+
var OWN_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "customerToken", "clientIp", "idempotencyKey", "preview", "signal"]);
|
|
110
|
+
var CLIENT_IP_HEADER = "X-Storefront-Client-IP";
|
|
111
|
+
function normalizeClientIp(raw) {
|
|
112
|
+
if (!raw) return void 0;
|
|
113
|
+
let v = raw.trim();
|
|
114
|
+
if (v.startsWith("[") && v.endsWith("]")) v = v.slice(1, -1);
|
|
115
|
+
if (!v || v.length > 45 || /[\s,%/]/.test(v)) return void 0;
|
|
116
|
+
if (/^[0-9]{1,3}(\.[0-9]{1,3}){3}$/.test(v)) {
|
|
117
|
+
return v.split(".").every((o) => Number(o) <= 255) ? v : void 0;
|
|
118
|
+
}
|
|
119
|
+
if (!v.includes(":") || !/^[0-9a-fA-F:.]+$/.test(v) || v.split("::").length > 2) return void 0;
|
|
120
|
+
try {
|
|
121
|
+
new URL(`http://[${v}]/`);
|
|
122
|
+
} catch {
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
return v;
|
|
126
|
+
}
|
|
110
127
|
function generateIdempotencyKey() {
|
|
111
128
|
const c = globalThis.crypto;
|
|
112
129
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -177,6 +194,9 @@ function createStorefrontClient(config) {
|
|
|
177
194
|
if (config.locale) headers.set("Accept-Language", config.locale);
|
|
178
195
|
const customerToken = options.customerToken !== void 0 ? options.customerToken : config.customerToken;
|
|
179
196
|
if (customerToken) headers.set("X-Customer-Token", customerToken);
|
|
197
|
+
const clientIp = options.clientIp !== void 0 ? options.clientIp : config.clientIp;
|
|
198
|
+
const ip = isTokenPublishable(config.token) ? void 0 : normalizeClientIp(clientIp);
|
|
199
|
+
if (ip) headers.set(CLIENT_IP_HEADER, ip);
|
|
180
200
|
if (options.idempotencyKey) headers.set("Idempotency-Key", options.idempotencyKey);
|
|
181
201
|
if (spec.body !== void 0) headers.set("Content-Type", "application/json");
|
|
182
202
|
for (const [k, v] of Object.entries(options.headers ?? {})) headers.set(k, v);
|
|
@@ -244,6 +264,10 @@ function createStorefrontClient(config) {
|
|
|
244
264
|
withCustomerToken(customerToken) {
|
|
245
265
|
return createStorefrontClient({ ...config, customerToken });
|
|
246
266
|
},
|
|
267
|
+
/** Returns a new client that reports the shopper's IP (`X-Storefront-Client-IP`) on every call. */
|
|
268
|
+
withClientIp(clientIp) {
|
|
269
|
+
return createStorefrontClient({ ...config, clientIp: clientIp ?? null });
|
|
270
|
+
},
|
|
247
271
|
shop: {
|
|
248
272
|
get: (options) => get("/storefront/shop", void 0, options)
|
|
249
273
|
},
|
|
@@ -340,6 +364,7 @@ function createStorefrontClient(config) {
|
|
|
340
364
|
return client;
|
|
341
365
|
}
|
|
342
366
|
|
|
367
|
+
exports.CLIENT_IP_HEADER = CLIENT_IP_HEADER;
|
|
343
368
|
exports.StorefrontError = StorefrontError;
|
|
344
369
|
exports.appendParams = appendParams;
|
|
345
370
|
exports.createStorefrontClient = createStorefrontClient;
|
|
@@ -347,6 +372,7 @@ exports.formatMoney = formatMoney;
|
|
|
347
372
|
exports.generateIdempotencyKey = generateIdempotencyKey;
|
|
348
373
|
exports.isStorefrontError = isStorefrontError;
|
|
349
374
|
exports.isTokenPublishable = isTokenPublishable;
|
|
375
|
+
exports.normalizeClientIp = normalizeClientIp;
|
|
350
376
|
exports.parseMoney = parseMoney;
|
|
351
377
|
exports.parseProductFilters = parseProductFilters;
|
|
352
378
|
exports.productListQuery = productListQuery;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/geo.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","/**\n * Geo helpers: pure TypeScript, no network and no DOM. Works in Node 20, edge\n * runtimes and browsers. Behaviour is pinned by `geo-vectors.json`, which the\n * Go side copies verbatim, so keep both implementations in sync.\n */\n\nexport type GeoPoint = { lat: number; lng: number }\n\n/** `(0, 0)` means \"unset\" on this platform, so it is never a valid point. */\ntype MaybePoint = { lat?: number | null; lng?: number | null } | null | undefined\n\nexport type OpeningHours = {\n /** `day`: 1 = Monday ... 7 = Sunday. Several entries per day are allowed. A `close` at or before `open` crosses midnight. */\n weekly: { day: number; open: string; close: string }[]\n /** Dated overrides of the weekly rule. `closed`, or both `open` and `close`. */\n exceptions?: { date: string; closed?: true; open?: string; close?: string; note?: string }[]\n}\n\nexport type OpenStatus = {\n open: boolean\n /** ISO instant (UTC) when the current interval ends; `null` when closed. */\n closesAt: string | null\n /** ISO instant (UTC) of the next opening; `null` when open or none within 8 days. */\n nextOpenAt: string | null\n}\n\nconst RAD = Math.PI / 180\n\n/** Great-circle distance in km (haversine, earth radius 6371.0088 km). */\nexport function distanceKm(a: GeoPoint, b: GeoPoint): number {\n const dLat = (b.lat - a.lat) * RAD\n const dLng = (b.lng - a.lng) * RAD\n const h = Math.sin(dLat / 2) ** 2 + Math.cos(a.lat * RAD) * Math.cos(b.lat * RAD) * Math.sin(dLng / 2) ** 2\n return 2 * 6371.0088 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h))\n}\n\n/** Finite, in range and not the `(0, 0)` \"unset\" pair. */\nexport function isValidPoint(p: MaybePoint): p is GeoPoint {\n const { lat, lng } = p ?? {}\n // Number.isFinite is false for null, undefined and non-numbers.\n return Number.isFinite(lat) && Number.isFinite(lng) && Math.abs(lat!) <= 90 && Math.abs(lng!) <= 180 && !(lat === 0 && lng === 0)\n}\n\n/** `'19.43,-99.13'` (spaces allowed) to a point; `null` when malformed or invalid. */\nexport function parseLatLng(s: string | null | undefined): GeoPoint | null {\n const [a, b, ...rest] = (s ?? '').split(',')\n const p = !rest.length && a?.trim() && b?.trim() ? { lat: Number(a), lng: Number(b) } : null\n return isValidPoint(p) ? p : null\n}\n\nexport type RankOptions<T> = {\n /** Keep items at most this far away (inclusive). */\n radiusKm?: number\n /** Keep the first N after sorting. */\n limit?: number\n /** Defaults to reading `lat` / `lng` from the item. */\n getPoint?: (item: T) => MaybePoint\n}\n\n/**\n * Sorts by distance from `origin` (stable: ties keep input order). Items without\n * a valid point are EXCLUDED, not placed last, because they have no distance.\n */\nexport function rankByDistance<T>(\n items: readonly T[],\n origin: GeoPoint,\n { radiusKm, limit, getPoint = (i) => i as MaybePoint }: RankOptions<T> = {},\n): { item: T; distanceKm: number }[] {\n const out: { item: T; distanceKm: number }[] = []\n for (const item of items) {\n const p = getPoint(item)\n if (!isValidPoint(p)) continue\n const d = distanceKm(origin, p)\n if (radiusKm === undefined || d <= radiusKm) out.push({ item, distanceKm: d })\n }\n // Array.prototype.sort is stable (ES2019), so ties keep input order.\n out.sort((x, y) => x.distanceKm - y.distanceKm)\n return limit === undefined ? out : out.slice(0, Math.max(0, limit))\n}\n\n/** Under 1 km: meters rounded to 50 m. Otherwise km with one decimal. */\nexport function formatDistance(km: number, locale = 'es-MX'): string {\n const m = Math.round((km * 1000) / 50) * 50\n const [value, unit, digits] = m < 1000 ? [m, 'meter', 0] : [km, 'kilometer', 1]\n return new Intl.NumberFormat(locale, {\n style: 'unit', unit, minimumFractionDigits: digits, maximumFractionDigits: digits,\n }).format(value)\n}\n\n/** Destination for directions: coordinates when valid, else the address text. */\nexport function directionsUrl(\n location: { lat?: number | null; lng?: number | null; address?: string | null; city?: string | null },\n { provider = 'google' }: { provider?: 'google' | 'apple' | 'geo' } = {},\n): string | null {\n const ll = isValidPoint(location) ? `${location.lat},${location.lng}` : null\n const text = [location.address, location.city].filter(Boolean).join(', ')\n const dest = ll ?? text\n if (!dest) return null\n const q = encodeURIComponent(dest)\n if (provider === 'apple') return `https://maps.apple.com/?daddr=${q}`\n if (provider === 'geo') return ll ? `geo:${ll}?q=${ll}` : `geo:0,0?q=${q}`\n return `https://www.google.com/maps/dir/?api=1&destination=${q}`\n}\n\n// --- opening hours -------------------------------------------------------\n\nconst toMin = (t: string | undefined): number => {\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(t ?? '')\n return m ? Number(m[1]) * 60 + Number(m[2]) : NaN\n}\n\ntype Iv = [number, number]\n\n/** Intervals in minutes; an end at or before the start crosses midnight. */\nconst interval = (open: string | undefined, close: string | undefined): Iv[] => {\n const s = toMin(open)\n let e = toMin(close)\n if (Number.isNaN(s) || Number.isNaN(e)) return []\n if (e <= s) e += 1440\n return [[s, e]]\n}\n\nconst dayIntervals = (h: OpeningHours, day: number, date: string): Iv[] => {\n const x = h.exceptions?.find((e) => e.date === date)\n if (x?.closed) return []\n if (x && x.open && x.close) return interval(x.open, x.close)\n return h.weekly.filter((w) => w.day === day).flatMap((w) => interval(w.open, w.close))\n}\n\n/**\n * Whether the place is open at `now` in the IANA `timeZone` (throws RangeError\n * for an unknown zone). Looks 8 days ahead for the next opening. Handles\n * intervals crossing midnight, several per day, dated exceptions and DST.\n */\nexport function openStatus(hours: OpeningHours, timeZone: string, now: Date = new Date()): OpenStatus {\n const fmt = new Intl.DateTimeFormat('en-US', {\n timeZone, hourCycle: 'h23', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric',\n })\n // Wall-clock minutes since the epoch as seen in `timeZone`.\n const wall = (ms: number): number => {\n const p: Record<string, number> = {}\n for (const { type, value } of fmt.formatToParts(ms)) p[type] = Number(value)\n return Date.UTC(p.year!, p.month! - 1, p.day!, (p.hour ?? 0) % 24, p.minute!) / 60000\n }\n const nowMs = now.getTime()\n const w = wall(nowMs)\n const day0 = Math.floor(w / 1440) * 1440\n // Offset from UTC, in minutes, at an instant.\n const offset = (ms: number): number => wall(ms) - Math.floor(ms / 60000)\n const toInstant = (t: number): string => {\n const guess = (t - offset(nowMs)) * 60000\n return new Date((t - offset(guess)) * 60000).toISOString()\n }\n\n const all: Iv[] = []\n for (let d = -1; d <= 8; d++) {\n const at = new Date((day0 + d * 1440) * 60000)\n const day = at.getUTCDay() || 7\n for (const [s, e] of dayIntervals(hours, day, at.toISOString().slice(0, 10))) all.push([day0 + d * 1440 + s, day0 + d * 1440 + e])\n }\n all.sort((a, b) => a[0] - b[0])\n const merged: Iv[] = []\n for (const iv of all) {\n const last = merged[merged.length - 1]\n if (last && iv[0] <= last[1]) last[1] = Math.max(last[1], iv[1])\n else merged.push([iv[0], iv[1]])\n }\n const current = merged.find(([s, e]) => s <= w && w < e)\n if (current) return { open: true, closesAt: toInstant(current[1]), nextOpenAt: null }\n const next = merged.find(([s]) => s > w)\n return { open: false, closesAt: null, nextOpenAt: next ? toInstant(next[0]) : null }\n}\n\ntype Run = { from: number; to: number; iv: Iv[] }\n\n/** Consecutive days (1..7) with identical intervals, in week order. */\nconst runs = (h: OpeningHours): Run[] => {\n const out: Run[] = []\n for (let day = 1; day <= 7; day++) {\n const iv = h.weekly.filter((x) => x.day === day).flatMap((x) => interval(x.open, x.close)).sort((a, b) => a[0] - b[0])\n if (!iv.length) continue\n const last = out[out.length - 1]\n if (last && last.to === day - 1 && JSON.stringify(last.iv) === JSON.stringify(iv)) last.to = day\n else out.push({ from: day, to: day, iv })\n }\n return out\n}\n\n/** Hour (24 for an end at midnight) and padded minutes of a minute-of-day value. */\nconst hm = (min: number): [number, string] => [min && min % 1440 === 0 ? 24 : Math.floor(min / 60) % 24, String(min % 60).padStart(2, '0')]\n\n/** Compact text, e.g. `Lun–Vie 9:00–19:00 · Sáb 9:00–14:00` (24 h) or `Mon–Fri 9:00 AM–7:00 PM` (en). */\nexport function formatOpeningHours(hours: OpeningHours, locale = 'es-MX'): string {\n const en = /^en/i.test(locale)\n const name = (d: number): string => {\n const s = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }).format(Date.UTC(2024, 0, d)).replace(/\\.$/, '')\n return s.charAt(0).toUpperCase() + s.slice(1)\n }\n const time = (min: number): string => {\n const [h, m] = hm(min)\n return en ? `${h % 12 || 12}:${m} ${h % 24 < 12 ? 'AM' : 'PM'}` : `${h}:${m}`\n }\n return runs(hours)\n .map((r) => `${name(r.from)}${r.from === r.to ? '' : '–' + name(r.to)} ${r.iv.map(([s, e]) => `${time(s)}–${time(e)}`).join(', ')}`)\n .join(' · ')\n}\n\n/** schema.org `openingHours` strings, one per interval, e.g. `Mo-Fr 09:00-19:00`. */\nexport function toSchemaOpeningHours(hours: OpeningHours): string[] {\n const code = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']\n const t = (min: number): string => { const [h, m] = hm(min); return `${String(h).padStart(2, '0')}:${m}` }\n return runs(hours).flatMap((r) =>\n r.iv.map(([s, e]) => `${code[r.from - 1]}${r.from === r.to ? '' : '-' + code[r.to - 1]} ${t(s)}-${t(e)}`),\n )\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 { isValidPoint, type GeoPoint } from './geo.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 Location,\n LocationListParams,\n NearestLocation,\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 locations: {\n /** Branches of the shop. With `postal_code` they come ordered by distance (`distance_km`). */\n list: (params?: LocationListParams, options?: RequestOptions) =>\n get<{ nodes: Location[] }>('/storefront/locations', params as QueryParams, options),\n /** `null` when the branch does not exist or is not visible. */\n get: (id: string, options?: RequestOptions) =>\n getOrNull<Location>(`/storefront/locations/${enc(id)}`, undefined, options),\n /**\n * Branches ordered by distance to a point, closest first. The server rounds the coordinates (0.01°, ~1 km)\n * and never stores them; this client does not log or keep them either. The answer is `private, no-store`\n * (it depends on the buyer's position), so do not put it in a shared cache. Note `onRequest` hooks and\n * `fetch` wrappers do see the URL: do not log it. Throws `TypeError` for an invalid point.\n */\n nearest: (point: GeoPoint, options?: RequestOptions) => {\n if (!isValidPoint(point)) throw new TypeError('locations.nearest: lat/lng must be a valid point')\n return get<{ nodes: NearestLocation[] }>('/storefront/locations/nearest', { lat: point.lat, lng: point.lng }, { cache: 'no-store', ...options })\n },\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 checkouts: {\n /**\n * Reads a placed checkout (same shape as the `carts.checkout` result). `order.number` is `null` until OPS\n * has created the order (a few hundred ms after checkout); poll once or twice and, if it is still `null`,\n * keep telling the buyer the number will arrive by email. Needs a server token (`checkout:write`).\n * `null` when the checkout does not exist. Never cache.\n */\n get: (id: string, options?: RequestOptions) =>\n getOrNull<CheckoutResult>(`/storefront/checkouts/${enc(id)}`, undefined, options),\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/geo.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","/**\n * Geo helpers: pure TypeScript, no network and no DOM. Works in Node 20, edge\n * runtimes and browsers. Behaviour is pinned by `geo-vectors.json`, which the\n * Go side copies verbatim, so keep both implementations in sync.\n */\n\nexport type GeoPoint = { lat: number; lng: number }\n\n/** `(0, 0)` means \"unset\" on this platform, so it is never a valid point. */\ntype MaybePoint = { lat?: number | null; lng?: number | null } | null | undefined\n\nexport type OpeningHours = {\n /** `day`: 1 = Monday ... 7 = Sunday. Several entries per day are allowed. A `close` at or before `open` crosses midnight. */\n weekly: { day: number; open: string; close: string }[]\n /** Dated overrides of the weekly rule. `closed`, or both `open` and `close`. */\n exceptions?: { date: string; closed?: true; open?: string; close?: string; note?: string }[]\n}\n\nexport type OpenStatus = {\n open: boolean\n /** ISO instant (UTC) when the current interval ends; `null` when closed. */\n closesAt: string | null\n /** ISO instant (UTC) of the next opening; `null` when open or none within 8 days. */\n nextOpenAt: string | null\n}\n\nconst RAD = Math.PI / 180\n\n/** Great-circle distance in km (haversine, earth radius 6371.0088 km). */\nexport function distanceKm(a: GeoPoint, b: GeoPoint): number {\n const dLat = (b.lat - a.lat) * RAD\n const dLng = (b.lng - a.lng) * RAD\n const h = Math.sin(dLat / 2) ** 2 + Math.cos(a.lat * RAD) * Math.cos(b.lat * RAD) * Math.sin(dLng / 2) ** 2\n return 2 * 6371.0088 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h))\n}\n\n/** Finite, in range and not the `(0, 0)` \"unset\" pair. */\nexport function isValidPoint(p: MaybePoint): p is GeoPoint {\n const { lat, lng } = p ?? {}\n // Number.isFinite is false for null, undefined and non-numbers.\n return Number.isFinite(lat) && Number.isFinite(lng) && Math.abs(lat!) <= 90 && Math.abs(lng!) <= 180 && !(lat === 0 && lng === 0)\n}\n\n/** `'19.43,-99.13'` (spaces allowed) to a point; `null` when malformed or invalid. */\nexport function parseLatLng(s: string | null | undefined): GeoPoint | null {\n const [a, b, ...rest] = (s ?? '').split(',')\n const p = !rest.length && a?.trim() && b?.trim() ? { lat: Number(a), lng: Number(b) } : null\n return isValidPoint(p) ? p : null\n}\n\nexport type RankOptions<T> = {\n /** Keep items at most this far away (inclusive). */\n radiusKm?: number\n /** Keep the first N after sorting. */\n limit?: number\n /** Defaults to reading `lat` / `lng` from the item. */\n getPoint?: (item: T) => MaybePoint\n}\n\n/**\n * Sorts by distance from `origin` (stable: ties keep input order). Items without\n * a valid point are EXCLUDED, not placed last, because they have no distance.\n */\nexport function rankByDistance<T>(\n items: readonly T[],\n origin: GeoPoint,\n { radiusKm, limit, getPoint = (i) => i as MaybePoint }: RankOptions<T> = {},\n): { item: T; distanceKm: number }[] {\n const out: { item: T; distanceKm: number }[] = []\n for (const item of items) {\n const p = getPoint(item)\n if (!isValidPoint(p)) continue\n const d = distanceKm(origin, p)\n if (radiusKm === undefined || d <= radiusKm) out.push({ item, distanceKm: d })\n }\n // Array.prototype.sort is stable (ES2019), so ties keep input order.\n out.sort((x, y) => x.distanceKm - y.distanceKm)\n return limit === undefined ? out : out.slice(0, Math.max(0, limit))\n}\n\n/** Under 1 km: meters rounded to 50 m. Otherwise km with one decimal. */\nexport function formatDistance(km: number, locale = 'es-MX'): string {\n const m = Math.round((km * 1000) / 50) * 50\n const [value, unit, digits] = m < 1000 ? [m, 'meter', 0] : [km, 'kilometer', 1]\n return new Intl.NumberFormat(locale, {\n style: 'unit', unit, minimumFractionDigits: digits, maximumFractionDigits: digits,\n }).format(value)\n}\n\n/** Destination for directions: coordinates when valid, else the address text. */\nexport function directionsUrl(\n location: { lat?: number | null; lng?: number | null; address?: string | null; city?: string | null },\n { provider = 'google' }: { provider?: 'google' | 'apple' | 'geo' } = {},\n): string | null {\n const ll = isValidPoint(location) ? `${location.lat},${location.lng}` : null\n const text = [location.address, location.city].filter(Boolean).join(', ')\n const dest = ll ?? text\n if (!dest) return null\n const q = encodeURIComponent(dest)\n if (provider === 'apple') return `https://maps.apple.com/?daddr=${q}`\n if (provider === 'geo') return ll ? `geo:${ll}?q=${ll}` : `geo:0,0?q=${q}`\n return `https://www.google.com/maps/dir/?api=1&destination=${q}`\n}\n\n// --- opening hours -------------------------------------------------------\n\nconst toMin = (t: string | undefined): number => {\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(t ?? '')\n return m ? Number(m[1]) * 60 + Number(m[2]) : NaN\n}\n\ntype Iv = [number, number]\n\n/** Intervals in minutes; an end at or before the start crosses midnight. */\nconst interval = (open: string | undefined, close: string | undefined): Iv[] => {\n const s = toMin(open)\n let e = toMin(close)\n if (Number.isNaN(s) || Number.isNaN(e)) return []\n if (e <= s) e += 1440\n return [[s, e]]\n}\n\nconst dayIntervals = (h: OpeningHours, day: number, date: string): Iv[] => {\n const x = h.exceptions?.find((e) => e.date === date)\n if (x?.closed) return []\n if (x && x.open && x.close) return interval(x.open, x.close)\n return h.weekly.filter((w) => w.day === day).flatMap((w) => interval(w.open, w.close))\n}\n\n/**\n * Whether the place is open at `now` in the IANA `timeZone` (throws RangeError\n * for an unknown zone). Looks 8 days ahead for the next opening. Handles\n * intervals crossing midnight, several per day, dated exceptions and DST.\n */\nexport function openStatus(hours: OpeningHours, timeZone: string, now: Date = new Date()): OpenStatus {\n const fmt = new Intl.DateTimeFormat('en-US', {\n timeZone, hourCycle: 'h23', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric',\n })\n // Wall-clock minutes since the epoch as seen in `timeZone`.\n const wall = (ms: number): number => {\n const p: Record<string, number> = {}\n for (const { type, value } of fmt.formatToParts(ms)) p[type] = Number(value)\n return Date.UTC(p.year!, p.month! - 1, p.day!, (p.hour ?? 0) % 24, p.minute!) / 60000\n }\n const nowMs = now.getTime()\n const w = wall(nowMs)\n const day0 = Math.floor(w / 1440) * 1440\n // Offset from UTC, in minutes, at an instant.\n const offset = (ms: number): number => wall(ms) - Math.floor(ms / 60000)\n const toInstant = (t: number): string => {\n const guess = (t - offset(nowMs)) * 60000\n return new Date((t - offset(guess)) * 60000).toISOString()\n }\n\n const all: Iv[] = []\n for (let d = -1; d <= 8; d++) {\n const at = new Date((day0 + d * 1440) * 60000)\n const day = at.getUTCDay() || 7\n for (const [s, e] of dayIntervals(hours, day, at.toISOString().slice(0, 10))) all.push([day0 + d * 1440 + s, day0 + d * 1440 + e])\n }\n all.sort((a, b) => a[0] - b[0])\n const merged: Iv[] = []\n for (const iv of all) {\n const last = merged[merged.length - 1]\n if (last && iv[0] <= last[1]) last[1] = Math.max(last[1], iv[1])\n else merged.push([iv[0], iv[1]])\n }\n const current = merged.find(([s, e]) => s <= w && w < e)\n if (current) return { open: true, closesAt: toInstant(current[1]), nextOpenAt: null }\n const next = merged.find(([s]) => s > w)\n return { open: false, closesAt: null, nextOpenAt: next ? toInstant(next[0]) : null }\n}\n\ntype Run = { from: number; to: number; iv: Iv[] }\n\n/** Consecutive days (1..7) with identical intervals, in week order. */\nconst runs = (h: OpeningHours): Run[] => {\n const out: Run[] = []\n for (let day = 1; day <= 7; day++) {\n const iv = h.weekly.filter((x) => x.day === day).flatMap((x) => interval(x.open, x.close)).sort((a, b) => a[0] - b[0])\n if (!iv.length) continue\n const last = out[out.length - 1]\n if (last && last.to === day - 1 && JSON.stringify(last.iv) === JSON.stringify(iv)) last.to = day\n else out.push({ from: day, to: day, iv })\n }\n return out\n}\n\n/** Hour (24 for an end at midnight) and padded minutes of a minute-of-day value. */\nconst hm = (min: number): [number, string] => [min && min % 1440 === 0 ? 24 : Math.floor(min / 60) % 24, String(min % 60).padStart(2, '0')]\n\n/** Compact text, e.g. `Lun–Vie 9:00–19:00 · Sáb 9:00–14:00` (24 h) or `Mon–Fri 9:00 AM–7:00 PM` (en). */\nexport function formatOpeningHours(hours: OpeningHours, locale = 'es-MX'): string {\n const en = /^en/i.test(locale)\n const name = (d: number): string => {\n const s = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }).format(Date.UTC(2024, 0, d)).replace(/\\.$/, '')\n return s.charAt(0).toUpperCase() + s.slice(1)\n }\n const time = (min: number): string => {\n const [h, m] = hm(min)\n return en ? `${h % 12 || 12}:${m} ${h % 24 < 12 ? 'AM' : 'PM'}` : `${h}:${m}`\n }\n return runs(hours)\n .map((r) => `${name(r.from)}${r.from === r.to ? '' : '–' + name(r.to)} ${r.iv.map(([s, e]) => `${time(s)}–${time(e)}`).join(', ')}`)\n .join(' · ')\n}\n\n/** schema.org `openingHours` strings, one per interval, e.g. `Mo-Fr 09:00-19:00`. */\nexport function toSchemaOpeningHours(hours: OpeningHours): string[] {\n const code = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']\n const t = (min: number): string => { const [h, m] = hm(min); return `${String(h).padStart(2, '0')}:${m}` }\n return runs(hours).flatMap((r) =>\n r.iv.map(([s, e]) => `${code[r.from - 1]}${r.from === r.to ? '' : '-' + code[r.to - 1]} ${t(s)}-${t(e)}`),\n )\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 { isValidPoint, type GeoPoint } from './geo.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 Location,\n LocationListParams,\n NearestLocation,\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 /**\n * IP address of the shopper, for SERVER-side calls (SSR, route actions) made with an\n * `sk_`/`pt_` token. Sent as `X-Storefront-Client-IP`, which the gateway honors only for\n * tokens issued with the `trust_client_ip` option: the rate limit is then charged per\n * shopper instead of to the store's single token bucket, and the OTP flow limits the\n * shopper's IP instead of the server's. Pass the IP from your own connection or trusted\n * proxy, never from a header the browser can forge. Never sent with a `pk_` token (the\n * gateway already separates those by caller IP). Invalid values are dropped.\n */\n clientIp?: string | null\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 /** Overrides the client-level shopper IP for this call (see `StorefrontClientOptions.clientIp`). */\n clientIp?: 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', 'clientIp', 'idempotencyKey', 'preview', 'signal'])\n\n/** Header the gateway reads (only for tokens with `trust_client_ip`). */\nexport const CLIENT_IP_HEADER = 'X-Storefront-Client-IP'\n\n/**\n * Returns the single IP literal in `raw`, or undefined. Takes the form a server hands over\n * (`::ffff:1.2.3.4`, `[2001:db8::1]`) and rejects lists, ports, zones and text so the\n * header is never a forgery vector.\n */\nexport function normalizeClientIp(raw: string | null | undefined): string | undefined {\n if (!raw) return undefined\n let v = raw.trim()\n if (v.startsWith('[') && v.endsWith(']')) v = v.slice(1, -1)\n if (!v || v.length > 45 || /[\\s,%/]/.test(v)) return undefined\n if (/^[0-9]{1,3}(\\.[0-9]{1,3}){3}$/.test(v)) {\n return v.split('.').every((o) => Number(o) <= 255) ? v : undefined\n }\n // IPv6 (incl. IPv4-mapped): hex groups and colons, at most one \"::\".\n if (!v.includes(':') || !/^[0-9a-fA-F:.]+$/.test(v) || v.split('::').length > 2) return undefined\n try {\n new URL(`http://[${v}]/`)\n } catch {\n return undefined\n }\n return v\n}\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 const clientIp = options.clientIp !== undefined ? options.clientIp : config.clientIp\n // Server-side tokens only: a pk_ token already gets a per-caller bucket from the gateway.\n const ip = isTokenPublishable(config.token) ? undefined : normalizeClientIp(clientIp)\n if (ip) headers.set(CLIENT_IP_HEADER, ip)\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 /** Returns a new client that reports the shopper's IP (`X-Storefront-Client-IP`) on every call. */\n withClientIp(clientIp: string | null | undefined) {\n return createStorefrontClient({ ...config, clientIp: clientIp ?? null })\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 locations: {\n /** Branches of the shop. With `postal_code` they come ordered by distance (`distance_km`). */\n list: (params?: LocationListParams, options?: RequestOptions) =>\n get<{ nodes: Location[] }>('/storefront/locations', params as QueryParams, options),\n /** `null` when the branch does not exist or is not visible. */\n get: (id: string, options?: RequestOptions) =>\n getOrNull<Location>(`/storefront/locations/${enc(id)}`, undefined, options),\n /**\n * Branches ordered by distance to a point, closest first. The server rounds the coordinates (0.01°, ~1 km)\n * and never stores them; this client does not log or keep them either. The answer is `private, no-store`\n * (it depends on the buyer's position), so do not put it in a shared cache. Note `onRequest` hooks and\n * `fetch` wrappers do see the URL: do not log it. Throws `TypeError` for an invalid point.\n */\n nearest: (point: GeoPoint, options?: RequestOptions) => {\n if (!isValidPoint(point)) throw new TypeError('locations.nearest: lat/lng must be a valid point')\n return get<{ nodes: NearestLocation[] }>('/storefront/locations/nearest', { lat: point.lat, lng: point.lng }, { cache: 'no-store', ...options })\n },\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 checkouts: {\n /**\n * Reads a placed checkout (same shape as the `carts.checkout` result). `order.number` is `null` until OPS\n * has created the order (a few hundred ms after checkout); poll once or twice and, if it is still `null`,\n * keep telling the buyer the number will arrive by email. Needs a server token (`checkout:write`).\n * `null` when the checkout does not exist. Never cache.\n */\n get: (id: string, options?: RequestOptions) =>\n getOrNull<CheckoutResult>(`/storefront/checkouts/${enc(id)}`, undefined, options),\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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -77,6 +77,16 @@ type StorefrontClientOptions = {
|
|
|
77
77
|
locale?: string;
|
|
78
78
|
/** Extra headers on every request. */
|
|
79
79
|
headers?: Record<string, string>;
|
|
80
|
+
/**
|
|
81
|
+
* IP address of the shopper, for SERVER-side calls (SSR, route actions) made with an
|
|
82
|
+
* `sk_`/`pt_` token. Sent as `X-Storefront-Client-IP`, which the gateway honors only for
|
|
83
|
+
* tokens issued with the `trust_client_ip` option: the rate limit is then charged per
|
|
84
|
+
* shopper instead of to the store's single token bucket, and the OTP flow limits the
|
|
85
|
+
* shopper's IP instead of the server's. Pass the IP from your own connection or trusted
|
|
86
|
+
* proxy, never from a header the browser can forge. Never sent with a `pk_` token (the
|
|
87
|
+
* gateway already separates those by caller IP). Invalid values are dropped.
|
|
88
|
+
*/
|
|
89
|
+
clientIp?: string | null;
|
|
80
90
|
/** Abort requests that take longer than this (default: no timeout). */
|
|
81
91
|
timeoutMs?: number;
|
|
82
92
|
/**
|
|
@@ -98,6 +108,8 @@ type RequestOptions = {
|
|
|
98
108
|
timeoutMs?: number;
|
|
99
109
|
/** Overrides the client-level customer token for this call. */
|
|
100
110
|
customerToken?: string | null;
|
|
111
|
+
/** Overrides the client-level shopper IP for this call (see `StorefrontClientOptions.clientIp`). */
|
|
112
|
+
clientIp?: string | null;
|
|
101
113
|
[key: string]: unknown;
|
|
102
114
|
};
|
|
103
115
|
type WriteOptions = RequestOptions & {
|
|
@@ -118,6 +130,14 @@ type RequestSpec = {
|
|
|
118
130
|
/** Resolve `null` instead of throwing on 404. */
|
|
119
131
|
nullOn404?: boolean;
|
|
120
132
|
};
|
|
133
|
+
/** Header the gateway reads (only for tokens with `trust_client_ip`). */
|
|
134
|
+
declare const CLIENT_IP_HEADER = "X-Storefront-Client-IP";
|
|
135
|
+
/**
|
|
136
|
+
* Returns the single IP literal in `raw`, or undefined. Takes the form a server hands over
|
|
137
|
+
* (`::ffff:1.2.3.4`, `[2001:db8::1]`) and rejects lists, ports, zones and text so the
|
|
138
|
+
* header is never a forgery vector.
|
|
139
|
+
*/
|
|
140
|
+
declare function normalizeClientIp(raw: string | null | undefined): string | undefined;
|
|
121
141
|
/** Generates an idempotency key (crypto.randomUUID when available). */
|
|
122
142
|
declare function generateIdempotencyKey(): string;
|
|
123
143
|
declare function createStorefrontClient(config: StorefrontClientOptions): {
|
|
@@ -132,6 +152,8 @@ declare function createStorefrontClient(config: StorefrontClientOptions): {
|
|
|
132
152
|
};
|
|
133
153
|
/** Returns a new client that sends the given buyer session token. */
|
|
134
154
|
withCustomerToken(customerToken: string | null): /*elided*/ any;
|
|
155
|
+
/** Returns a new client that reports the shopper's IP (`X-Storefront-Client-IP`) on every call. */
|
|
156
|
+
withClientIp(clientIp: string | null | undefined): /*elided*/ any;
|
|
135
157
|
shop: {
|
|
136
158
|
get: (options?: RequestOptions) => Promise<Shop>;
|
|
137
159
|
};
|
|
@@ -286,4 +308,4 @@ declare function toMoney(value: number, currencyCode: string): Money;
|
|
|
286
308
|
*/
|
|
287
309
|
declare function isTokenPublishable(token: string | null | undefined): boolean;
|
|
288
310
|
|
|
289
|
-
export { Address, AddressInput, Article, type ArticleListParams, Cart, CartBuyerInput, CartLineInput, CartLineUpdateInput, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeliveryInput, DeliveryOptions, type FetchLike, type FormatMoneyOptions, GeoPoint, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NearestLocation, Order, Page, type PaginationParams, PaymentMethods, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, type QueryParams, type QueryValue, type RequestOptions, ReturnRequest, ReturnRequestInput, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, parseMoney, parseProductFilters, productListQuery, toMoney };
|
|
311
|
+
export { Address, AddressInput, Article, type ArticleListParams, CLIENT_IP_HEADER, Cart, CartBuyerInput, CartLineInput, CartLineUpdateInput, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeliveryInput, DeliveryOptions, type FetchLike, type FormatMoneyOptions, GeoPoint, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NearestLocation, Order, Page, type PaginationParams, PaymentMethods, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, type QueryParams, type QueryValue, type RequestOptions, ReturnRequest, ReturnRequestInput, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, parseMoney, parseProductFilters, productListQuery, toMoney };
|
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,16 @@ type StorefrontClientOptions = {
|
|
|
77
77
|
locale?: string;
|
|
78
78
|
/** Extra headers on every request. */
|
|
79
79
|
headers?: Record<string, string>;
|
|
80
|
+
/**
|
|
81
|
+
* IP address of the shopper, for SERVER-side calls (SSR, route actions) made with an
|
|
82
|
+
* `sk_`/`pt_` token. Sent as `X-Storefront-Client-IP`, which the gateway honors only for
|
|
83
|
+
* tokens issued with the `trust_client_ip` option: the rate limit is then charged per
|
|
84
|
+
* shopper instead of to the store's single token bucket, and the OTP flow limits the
|
|
85
|
+
* shopper's IP instead of the server's. Pass the IP from your own connection or trusted
|
|
86
|
+
* proxy, never from a header the browser can forge. Never sent with a `pk_` token (the
|
|
87
|
+
* gateway already separates those by caller IP). Invalid values are dropped.
|
|
88
|
+
*/
|
|
89
|
+
clientIp?: string | null;
|
|
80
90
|
/** Abort requests that take longer than this (default: no timeout). */
|
|
81
91
|
timeoutMs?: number;
|
|
82
92
|
/**
|
|
@@ -98,6 +108,8 @@ type RequestOptions = {
|
|
|
98
108
|
timeoutMs?: number;
|
|
99
109
|
/** Overrides the client-level customer token for this call. */
|
|
100
110
|
customerToken?: string | null;
|
|
111
|
+
/** Overrides the client-level shopper IP for this call (see `StorefrontClientOptions.clientIp`). */
|
|
112
|
+
clientIp?: string | null;
|
|
101
113
|
[key: string]: unknown;
|
|
102
114
|
};
|
|
103
115
|
type WriteOptions = RequestOptions & {
|
|
@@ -118,6 +130,14 @@ type RequestSpec = {
|
|
|
118
130
|
/** Resolve `null` instead of throwing on 404. */
|
|
119
131
|
nullOn404?: boolean;
|
|
120
132
|
};
|
|
133
|
+
/** Header the gateway reads (only for tokens with `trust_client_ip`). */
|
|
134
|
+
declare const CLIENT_IP_HEADER = "X-Storefront-Client-IP";
|
|
135
|
+
/**
|
|
136
|
+
* Returns the single IP literal in `raw`, or undefined. Takes the form a server hands over
|
|
137
|
+
* (`::ffff:1.2.3.4`, `[2001:db8::1]`) and rejects lists, ports, zones and text so the
|
|
138
|
+
* header is never a forgery vector.
|
|
139
|
+
*/
|
|
140
|
+
declare function normalizeClientIp(raw: string | null | undefined): string | undefined;
|
|
121
141
|
/** Generates an idempotency key (crypto.randomUUID when available). */
|
|
122
142
|
declare function generateIdempotencyKey(): string;
|
|
123
143
|
declare function createStorefrontClient(config: StorefrontClientOptions): {
|
|
@@ -132,6 +152,8 @@ declare function createStorefrontClient(config: StorefrontClientOptions): {
|
|
|
132
152
|
};
|
|
133
153
|
/** Returns a new client that sends the given buyer session token. */
|
|
134
154
|
withCustomerToken(customerToken: string | null): /*elided*/ any;
|
|
155
|
+
/** Returns a new client that reports the shopper's IP (`X-Storefront-Client-IP`) on every call. */
|
|
156
|
+
withClientIp(clientIp: string | null | undefined): /*elided*/ any;
|
|
135
157
|
shop: {
|
|
136
158
|
get: (options?: RequestOptions) => Promise<Shop>;
|
|
137
159
|
};
|
|
@@ -286,4 +308,4 @@ declare function toMoney(value: number, currencyCode: string): Money;
|
|
|
286
308
|
*/
|
|
287
309
|
declare function isTokenPublishable(token: string | null | undefined): boolean;
|
|
288
310
|
|
|
289
|
-
export { Address, AddressInput, Article, type ArticleListParams, Cart, CartBuyerInput, CartLineInput, CartLineUpdateInput, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeliveryInput, DeliveryOptions, type FetchLike, type FormatMoneyOptions, GeoPoint, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NearestLocation, Order, Page, type PaginationParams, PaymentMethods, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, type QueryParams, type QueryValue, type RequestOptions, ReturnRequest, ReturnRequestInput, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, parseMoney, parseProductFilters, productListQuery, toMoney };
|
|
311
|
+
export { Address, AddressInput, Article, type ArticleListParams, CLIENT_IP_HEADER, Cart, CartBuyerInput, CartLineInput, CartLineUpdateInput, CheckoutInput, CheckoutResult, Collection, type CollectionListParams, Connection, Customer, CustomerIdentifier, CustomerSession, CustomerSessionInput, DeliveryInput, DeliveryOptions, type FetchLike, type FormatMoneyOptions, GeoPoint, InvoiceRequest, InvoiceResult, Location, LocationListParams, Menu, Money, NearestLocation, Order, Page, type PaginationParams, PaymentMethods, type PreviewOptions, Product, ProductConnection, type ProductListParams, ProductSort, type QueryParams, type QueryValue, type RequestOptions, ReturnRequest, ReturnRequestInput, Shop, StockLevel, type StorefrontClient, type StorefrontClientOptions, StorefrontError, StorefrontErrorCode, type StorefrontErrorInit, type StorefrontRequestContext, type StorefrontResponseContext, Theme, type WriteOptions, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, parseMoney, parseProductFilters, productListQuery, toMoney };
|
package/dist/index.js
CHANGED
|
@@ -102,7 +102,24 @@ function parseProductFilters(search) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// src/client.ts
|
|
105
|
-
var OWN_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "customerToken", "idempotencyKey", "preview", "signal"]);
|
|
105
|
+
var OWN_OPTION_KEYS = /* @__PURE__ */ new Set(["headers", "timeoutMs", "customerToken", "clientIp", "idempotencyKey", "preview", "signal"]);
|
|
106
|
+
var CLIENT_IP_HEADER = "X-Storefront-Client-IP";
|
|
107
|
+
function normalizeClientIp(raw) {
|
|
108
|
+
if (!raw) return void 0;
|
|
109
|
+
let v = raw.trim();
|
|
110
|
+
if (v.startsWith("[") && v.endsWith("]")) v = v.slice(1, -1);
|
|
111
|
+
if (!v || v.length > 45 || /[\s,%/]/.test(v)) return void 0;
|
|
112
|
+
if (/^[0-9]{1,3}(\.[0-9]{1,3}){3}$/.test(v)) {
|
|
113
|
+
return v.split(".").every((o) => Number(o) <= 255) ? v : void 0;
|
|
114
|
+
}
|
|
115
|
+
if (!v.includes(":") || !/^[0-9a-fA-F:.]+$/.test(v) || v.split("::").length > 2) return void 0;
|
|
116
|
+
try {
|
|
117
|
+
new URL(`http://[${v}]/`);
|
|
118
|
+
} catch {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
return v;
|
|
122
|
+
}
|
|
106
123
|
function generateIdempotencyKey() {
|
|
107
124
|
const c = globalThis.crypto;
|
|
108
125
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -173,6 +190,9 @@ function createStorefrontClient(config) {
|
|
|
173
190
|
if (config.locale) headers.set("Accept-Language", config.locale);
|
|
174
191
|
const customerToken = options.customerToken !== void 0 ? options.customerToken : config.customerToken;
|
|
175
192
|
if (customerToken) headers.set("X-Customer-Token", customerToken);
|
|
193
|
+
const clientIp = options.clientIp !== void 0 ? options.clientIp : config.clientIp;
|
|
194
|
+
const ip = isTokenPublishable(config.token) ? void 0 : normalizeClientIp(clientIp);
|
|
195
|
+
if (ip) headers.set(CLIENT_IP_HEADER, ip);
|
|
176
196
|
if (options.idempotencyKey) headers.set("Idempotency-Key", options.idempotencyKey);
|
|
177
197
|
if (spec.body !== void 0) headers.set("Content-Type", "application/json");
|
|
178
198
|
for (const [k, v] of Object.entries(options.headers ?? {})) headers.set(k, v);
|
|
@@ -240,6 +260,10 @@ function createStorefrontClient(config) {
|
|
|
240
260
|
withCustomerToken(customerToken) {
|
|
241
261
|
return createStorefrontClient({ ...config, customerToken });
|
|
242
262
|
},
|
|
263
|
+
/** Returns a new client that reports the shopper's IP (`X-Storefront-Client-IP`) on every call. */
|
|
264
|
+
withClientIp(clientIp) {
|
|
265
|
+
return createStorefrontClient({ ...config, clientIp: clientIp ?? null });
|
|
266
|
+
},
|
|
243
267
|
shop: {
|
|
244
268
|
get: (options) => get("/storefront/shop", void 0, options)
|
|
245
269
|
},
|
|
@@ -336,6 +360,6 @@ function createStorefrontClient(config) {
|
|
|
336
360
|
return client;
|
|
337
361
|
}
|
|
338
362
|
|
|
339
|
-
export { StorefrontError, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, parseMoney, parseProductFilters, productListQuery, toMoney };
|
|
363
|
+
export { CLIENT_IP_HEADER, StorefrontError, appendParams, createStorefrontClient, formatMoney, generateIdempotencyKey, isStorefrontError, isTokenPublishable, normalizeClientIp, parseMoney, parseProductFilters, productListQuery, toMoney };
|
|
340
364
|
//# sourceMappingURL=index.js.map
|
|
341
365
|
//# sourceMappingURL=index.js.map
|