@zappie/sdk 0.1.23 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +3 -2
  3. package/dist/cart/branch.d.ts +4 -3
  4. package/dist/cart/item.d.ts +3 -3
  5. package/dist/cart/quote.d.ts +5 -4
  6. package/dist/checkout/embed.d.ts +14 -2
  7. package/dist/checkout/embed.js +17 -4
  8. package/dist/checkout/embed.js.map +2 -2
  9. package/dist/checkout/index.d.ts +2 -1
  10. package/dist/checkout/react.d.ts +13 -2
  11. package/dist/checkout-defaults/index.d.ts +2 -1
  12. package/dist/{client-yJQj780A.d.ts → client-B377AzNC.d.ts} +4 -3
  13. package/dist/config/index.d.ts +4 -1
  14. package/dist/customer/index.d.ts +2 -1
  15. package/dist/debug.d.ts +5 -4
  16. package/dist/debug.js +1 -1
  17. package/dist/debug.js.map +1 -1
  18. package/dist/index.d.ts +9 -7
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/index.d.ts +5 -4
  22. package/dist/internal.d.ts +3 -3
  23. package/dist/{models-DuLY7jMB.d.ts → models-tf9rNoki.d.ts} +2 -1
  24. package/dist/persist/catalogue.d.ts +3 -3
  25. package/dist/places/index.d.ts +2 -1
  26. package/dist/react/index.d.ts +3 -3
  27. package/dist/redirect/index.d.ts +3 -1
  28. package/dist/redirect/index.js +9 -0
  29. package/dist/redirect/index.js.map +2 -2
  30. package/dist/server/index.d.ts +10 -6
  31. package/dist/server/storefront-url/redirect.js.map +1 -1
  32. package/dist/site-config/types.js.map +1 -1
  33. package/dist/site-config/validate.js +2 -1
  34. package/dist/site-config/validate.js.map +2 -2
  35. package/dist/studio/index.d.ts +2 -1
  36. package/dist/template/index.d.ts +2 -1
  37. package/dist/testing/index.d.ts +6 -4
  38. package/dist/transport/category-page.d.ts +1 -1
  39. package/dist/transport/presentation.d.ts +1 -1
  40. package/dist/transport/search.d.ts +3 -3
  41. package/dist/transport/selected-menu.d.ts +3 -3
  42. package/dist/transport/store.d.ts +1 -1
  43. package/dist/{types-COI1jnQ2.d.ts → types-B3Ghmpmy.d.ts} +1 -1
  44. package/dist/url/index.d.ts +4 -3
  45. package/dist/visits/index.d.ts +2 -1
  46. package/dist/vite/index.d.ts +2 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @zappie/sdk
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ea37bcf: `redirectToCheckout` in `@zappie/sdk/redirect` sends a window to a checkout session's page and refuses any URL that isn't Zappie's checkout. `security.links` in `zappie.config.ts` lists the hosts a site's pages may link to.
8
+
9
+ ### Patch Changes
10
+
11
+ - 8ea70dc: Document that a classic URL's site target is served as a permanent redirect (301).
12
+
13
+ ## 0.1.24
14
+
15
+ ### Patch Changes
16
+
17
+ - 1cbfebc: Embedded checkout stays open on a retryable error, so the shopper can use the checkout's Try again button. Any other error closes it with the error code as the reason, as before. The new `onError` option reports both kinds. The frame address now carries the page's origin, so the checkout can tell the site why it can't open even when the browser hides who framed it.
18
+ - b31a689: Build the declaration files with tsup 9. Type-only exports are now written as `export type { ... }` and shared declaration chunks have new file names. Foundations part props name their omitted attributes `PartOwnedAttributes`, so their declarations no longer depend on build order. Every entry point exports the same names with the same types. The JavaScript and source maps are unchanged.
19
+
3
20
  ## 0.1.23
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -156,8 +156,9 @@ The SDK does not load Maps scripts.
156
156
  ### Legacy redirects
157
157
 
158
158
  `storefrontRedirect(input, { installation })` from `@zappie/sdk/url` returns
159
- `string | null`. Callers choose the HTTP status when they serve the redirect.
160
- Zappie's templates use temporary redirects (302) for now.
159
+ `string | null`. Serve the target as a permanent redirect (301): a classic
160
+ URL has moved for good, so search engines carry its ranking to the site in one
161
+ step.
161
162
 
162
163
  V2 URLs use `item_` and `ord_` public IDs, `menu=menu_<id>`, and category
163
164
  anchors `#c-cat_<id>`. Already-prefixed IDs stay unchanged. Legacy cart,
@@ -1,5 +1,5 @@
1
- import { C as CartLine } from '../types-COI1jnQ2.js';
2
- import { M as Money, I as Item, a as Menu } from '../models-DuLY7jMB.js';
1
+ import { CartLine } from '../index.js';
2
+ import { Money, Item, Menu } from '../index.js';
3
3
 
4
4
  /** A line that can move to the destination branch, with its old and new estimates. */
5
5
  type BranchSwitchKeptLine = {
@@ -23,4 +23,5 @@ declare function estimateLine(line: CartLine, item: Item): Money;
23
23
  /** Plan which cart lines move to another branch without changing either cart. */
24
24
  declare function planBranchSwitch(lines: readonly CartLine[], from: Menu, to: Menu): BranchSwitchPlan;
25
25
 
26
- export { type BranchSwitchKeptLine, type BranchSwitchPlan, estimateLine, planBranchSwitch };
26
+ export { estimateLine, planBranchSwitch };
27
+ export type { BranchSwitchKeptLine, BranchSwitchPlan };
@@ -1,6 +1,6 @@
1
- import { Z as Zappie } from '../client-yJQj780A.js';
2
- import { I as Item } from '../models-DuLY7jMB.js';
3
- import '../types-COI1jnQ2.js';
1
+ import { Zappie } from '../index.js';
2
+ import { Item } from '../index.js';
3
+ import '../types-B3Ghmpmy.js';
4
4
 
5
5
  /** Register an item already read by a partial/selected catalogue, without fetching it again. */
6
6
  declare function prepareItem(client: Zappie, item: Item, options: {
@@ -1,6 +1,6 @@
1
- import { T as Transport, R as RequestCtx } from '../client-yJQj780A.js';
2
- import { Q as QuoteRequest, a as Quote } from '../types-COI1jnQ2.js';
3
- import '../models-DuLY7jMB.js';
1
+ import { Transport, RequestCtx } from '../index.js';
2
+ import { QuoteRequest, Quote } from '../index.js';
3
+ import '../models-tf9rNoki.js';
4
4
 
5
5
  type QuoteClientOptions = {
6
6
  customerBase?: string | undefined;
@@ -17,4 +17,5 @@ type CartIssueForAcceptance = {
17
17
  };
18
18
  type QuoteClient = ReturnType<typeof createQuoteClient>;
19
19
 
20
- export { type QuoteClient, createQuoteClient };
20
+ export { createQuoteClient };
21
+ export type { QuoteClient };
@@ -116,12 +116,23 @@ type CheckoutTheme = {
116
116
  successTitle?: string;
117
117
  };
118
118
  };
119
- /** Presentation, theme and step reporting for an embedded checkout. */
119
+ /**
120
+ * Why checkout can't go on. `code` is `session_expired`, `session_invalid` or `internal`. When `retryable` is true,
121
+ * checkout stays open and offers to try again.
122
+ */
123
+ type CheckoutEmbedError = {
124
+ code: string;
125
+ message: string;
126
+ retryable: boolean;
127
+ };
128
+ /** Presentation, theme, step and error reporting for an embedded checkout. */
120
129
  type EmbedOptions = {
121
130
  presentation?: CheckoutPresentation;
122
131
  theme?: CheckoutTheme;
123
132
  /** Called with each checkout step, and whether a payment has started. */
124
133
  onStep?: (step: string, paying: boolean) => void;
134
+ /** Called when checkout can't go on. A retryable error keeps it open, and any other error closes it with its code as the reason. */
135
+ onError?: (error: CheckoutEmbedError) => void;
125
136
  };
126
137
  /** How an embedded checkout ended. A completed result is confirmed by the server first. */
127
138
  type CheckoutResult = {
@@ -145,4 +156,5 @@ declare function resumeCheckout(checkout: CheckoutClient): Promise<CheckoutResul
145
156
  */
146
157
  declare function embedCheckout(checkout: CheckoutClient, session: OpenCheckoutSession, options?: EmbedOptions): Promise<CheckoutResult>;
147
158
 
148
- export { type CheckoutPresentation, type CheckoutResult, type CheckoutTheme, type EmbedOptions, checkoutPresentation, closeCheckout, embedCheckout, resumeCheckout };
159
+ export { checkoutPresentation, closeCheckout, embedCheckout, resumeCheckout };
160
+ export type { CheckoutEmbedError, CheckoutPresentation, CheckoutResult, CheckoutTheme, EmbedOptions };
@@ -45,6 +45,7 @@ function embedCheckout(checkout, session, options = {}) {
45
45
  return Promise.resolve({ status: "redirected" });
46
46
  }
47
47
  url.pathname = `/embed/${encodeURIComponent(session.id)}`;
48
+ url.searchParams.set("origin", location.origin);
48
49
  const doc = document;
49
50
  const body = doc.body;
50
51
  const root = doc.createElement("div");
@@ -176,10 +177,22 @@ function embedCheckout(checkout, session, options = {}) {
176
177
  }
177
178
  );
178
179
  } else if (data.type === "cancelled" || data.type === "error") {
179
- finish({
180
- status: "cancelled",
181
- reason: String(payload.reason ?? payload.code ?? "user")
182
- });
180
+ const retry = data.type === "error" && payload.retryable === true;
181
+ if (data.type === "error") {
182
+ options.onError?.({
183
+ code: payload.code,
184
+ message: payload.message,
185
+ retryable: retry
186
+ });
187
+ }
188
+ if (retry) {
189
+ ready = paying = false;
190
+ } else {
191
+ finish({
192
+ status: "cancelled",
193
+ reason: String(payload.reason ?? payload.code ?? "user")
194
+ });
195
+ }
183
196
  }
184
197
  };
185
198
  let start = 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/checkout/embed.ts"],
4
- "sourcesContent": ["import type { CheckoutClient } from \"./client\";\nimport { ZappieError } from \"./sdk\";\nimport type { OpenCheckoutSession } from \"./types\";\n\n/** How checkout opens. Auto redirects in in-app browsers and framed pages, else a modal on desktop and a sheet on mobile. */\nexport type CheckoutPresentation =\n \"auto\" | \"modal\" | \"sheet\" | \"fullscreen\" | \"redirect\";\n\n/** The checkout look from toCheckoutTheme in @zappie/tokens/checkout. The checkout validates it before use. */\nexport type CheckoutTheme = {\n version: 1;\n tokens: Partial<Record<`--z-${string}`, string>>;\n logo?: { url: string; alt: string };\n fonts?: { family: string; role: \"body\" | \"display\"; weights?: number[] }[];\n copy?: { payButton?: string; successTitle?: string };\n};\n\n/** Presentation, theme and step reporting for an embedded checkout. */\nexport type EmbedOptions = {\n presentation?: CheckoutPresentation;\n theme?: CheckoutTheme;\n /** Called with each checkout step, and whether a payment has started. */\n onStep?: (step: string, paying: boolean) => void;\n};\n\n/** How an embedded checkout ended. A completed result is confirmed by the server first. */\nexport type CheckoutResult =\n | { status: \"completed\"; orderId: string }\n | { status: \"cancelled\"; reason: string }\n | { status: \"redirected\" };\n\nconst PARAM = \"zappie_checkout\";\nconst finished = new Set<string>();\nlet request: (() => void) | undefined;\nconst media = (query: string) => matchMedia(query).matches;\n\n/** Resolve auto to the presentation this browser gets. */\nexport function checkoutPresentation(\n presentation: CheckoutPresentation = \"auto\",\n): Exclude<CheckoutPresentation, \"auto\"> {\n if (presentation !== \"auto\") {\n return presentation;\n }\n if (\n window.top !== window ||\n /FBA[NV]|Instagram|TikTok|musical_ly|Bytedance|Line\\//.test(\n navigator.userAgent,\n )\n ) {\n return \"redirect\";\n }\n return media(\"(min-width:640px) and (pointer:fine),(min-width:1024px)\")\n ? \"modal\"\n : \"sheet\";\n}\n\n/** Ask the open checkout to close. Mid-payment it asks the shopper first. */\nexport function closeCheckout(): void {\n request?.();\n}\n\n/** On load, drop a stale checkout entry from the address and read its session. It never reopens checkout. */\nexport async function resumeCheckout(\n checkout: CheckoutClient,\n): Promise<CheckoutResult | null> {\n const url = new URL(location.href);\n const id = url.searchParams.get(PARAM);\n if (!id) {\n return null;\n }\n url.searchParams.delete(PARAM);\n history.replaceState(history.state, \"\", url);\n const session = await checkout.session(id);\n return session.status === \"completed\" && session.order_id\n ? { status: \"completed\", orderId: session.order_id }\n : null;\n}\n\n/**\n * Open a checkout session over the page and settle when it ends. Only Zappie's checkout, or one on this machine, is framed. Back and Escape ask the checkout to step back or\n * close. A completed result waits for the server to confirm the session. A completed session can't be reopened.\n */\nexport function embedCheckout(\n checkout: CheckoutClient,\n session: OpenCheckoutSession,\n options: EmbedOptions = {},\n): Promise<CheckoutResult> {\n const url = new URL(session.url);\n if (\n request ||\n session.status !== \"open\" ||\n finished.has(session.id) ||\n !(\n /^https:\\/\\/checkout\\.(stg\\.)?zappie\\.ai$/.test(url.origin) ||\n /^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(url.origin)\n )\n ) {\n return Promise.reject(\n new ZappieError(\n \"invalid_request\",\n \"This checkout session can't be opened\",\n ),\n );\n }\n const shown = checkoutPresentation(options.presentation);\n if (shown === \"redirect\") {\n location.assign(session.url);\n return Promise.resolve({ status: \"redirected\" });\n }\n url.pathname = `/embed/${encodeURIComponent(session.id)}`;\n const doc = document;\n const body = doc.body;\n const root = doc.createElement(\"div\");\n const panel = doc.createElement(\"div\");\n const handle = doc.createElement(\"div\");\n const frame = doc.createElement(\"iframe\");\n const sheet = shown === \"sheet\";\n const still = media(\"(prefers-reduced-motion:reduce)\");\n const opener = doc.activeElement as HTMLElement | null;\n const y = scrollY;\n const bodyStyle = body.style.cssText;\n const restoration = history.scrollRestoration;\n const inert = [...body.children].filter(\n (el): el is HTMLElement => !(el as HTMLElement).inert,\n );\n const abort = new AbortController();\n const vv = window.visualViewport;\n let ready = false;\n let paying = false;\n let height = 640;\n let settle: (result: CheckoutResult) => void = () => {};\n\n root.setAttribute(\"data-zappie-checkout\", shown);\n root.setAttribute(\"role\", \"dialog\");\n root.setAttribute(\"aria-modal\", \"true\");\n root.setAttribute(\"aria-label\", \"Checkout\");\n root.style.cssText =\n \"position:fixed;inset:0;z-index:2147483647;background:var(--z-checkout-backdrop,rgb(0 0 0/.5))\";\n panel.style.cssText = `position:absolute;overflow:hidden;display:flex;flex-direction:column;background:var(--z-surface,#fff);${\n shown === \"fullscreen\"\n ? \"inset:0\"\n : sheet\n ? \"left:0;right:0;border-radius:var(--z-checkout-radius,16px) var(--z-checkout-radius,16px) 0 0\"\n : \"inset:0;margin:auto;width:min(480px,100vw - 32px);border-radius:var(--z-checkout-radius,16px)\"\n }`;\n handle.style.cssText =\n \"flex:none;height:24px;touch-action:none;cursor:grab;background:radial-gradient(closest-side,var(--z-border,#ccc) 99%,#0000) 50%/40px 5px no-repeat\";\n frame.title = \"Checkout\";\n frame.src = url.href;\n frame.allow = \"payment 'src'; publickey-credentials-get 'src'\";\n frame.referrerPolicy = \"no-referrer\";\n frame.style.cssText = \"flex:1;width:100%;border:0;display:block\";\n if (sheet) {\n panel.append(handle);\n }\n panel.append(frame);\n root.append(panel);\n\n const layout = () => {\n const view = vv ?? { height: innerHeight, offsetTop: 0 };\n if (sheet) {\n panel.style.height = `${Math.round(view.height * 0.92)}px`;\n panel.style.bottom = `${Math.max(0, innerHeight - view.offsetTop - view.height)}px`;\n } else if (shown === \"modal\") {\n panel.style.height = `${Math.min(height, view.height - 32)}px`;\n }\n };\n const post = (type: string, payload: object = {}) =>\n frame.contentWindow?.postMessage(\n { source: \"zappie-checkout\", v: 1, session: session.id, type, payload },\n url.origin,\n );\n const finish = (result: CheckoutResult, popped?: boolean) => {\n abort.abort();\n request = undefined;\n removeEventListener(\"message\", onMessage);\n removeEventListener(\"popstate\", onPop);\n removeEventListener(\"keydown\", onKey);\n vv?.removeEventListener(\"resize\", layout);\n vv?.removeEventListener(\"scroll\", layout);\n root.remove();\n inert.forEach((el) => (el.inert = false));\n body.style.cssText = bodyStyle;\n // WebKit scrolls the page again after popstate, so the scroll is set once more a task later.\n const restore = () => {\n scrollTo(0, y);\n setTimeout(() => {\n scrollTo(0, y);\n history.scrollRestoration = restoration;\n });\n };\n restore();\n opener?.focus({ preventScroll: true });\n if (!popped && history.state?.[PARAM] === session.id) {\n history.scrollRestoration = \"manual\";\n addEventListener(\"popstate\", restore, { once: true });\n history.back();\n }\n settle(result);\n };\n const close = () =>\n ready\n ? post(\"close_request\")\n : finish({ status: \"cancelled\", reason: \"user\" });\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n close();\n }\n };\n const onPop = () => {\n if (!ready) {\n return finish({ status: \"cancelled\", reason: \"user\" }, true);\n }\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n location.href,\n );\n post(\"close_request\");\n };\n const onMessage = (event: MessageEvent) => {\n const data = event.data;\n if (\n event.origin !== url.origin ||\n event.source !== frame.contentWindow ||\n data?.source !== \"zappie-checkout\" ||\n data.v !== 1 ||\n data.session !== session.id\n ) {\n return;\n }\n const payload = Object(data.payload);\n if (data.type === \"ready\" || data.type === \"step\") {\n if (data.type === \"ready\") {\n ready = true;\n frame.focus();\n if (options.theme) {\n post(\"theme\", options.theme);\n }\n }\n paying = payload.paying === true;\n if (typeof payload.step === \"string\") {\n options.onStep?.(payload.step, paying);\n }\n } else if (data.type === \"resize\" && Number.isFinite(payload.height)) {\n height = payload.height;\n layout();\n } else if (data.type === \"completed\") {\n checkout\n .awaitCompletion(session.id, { signal: abort.signal, intervalMs: 1500 })\n .then(\n (done) => {\n if (done.status === \"completed\") {\n finished.add(session.id);\n finish({ status: \"completed\", orderId: done.order_id });\n } else {\n finish({ status: \"cancelled\", reason: done.status });\n }\n },\n () => {},\n );\n } else if (data.type === \"cancelled\" || data.type === \"error\") {\n finish({\n status: \"cancelled\",\n reason: String(payload.reason ?? payload.code ?? \"user\"),\n });\n }\n };\n\n let start = 0;\n handle.onpointerdown = (event) => {\n if (paying) {\n return;\n }\n start = event.clientY;\n handle.setPointerCapture?.(event.pointerId);\n };\n handle.onpointermove = (event) => {\n if (start) {\n panel.style.transform = `translateY(${Math.max(0, event.clientY - start)}px)`;\n }\n };\n handle.onpointerup = handle.onpointercancel = (event) => {\n const moved = start ? event.clientY - start : 0;\n start = 0;\n panel.style.transform = \"\";\n if (!paying && moved > panel.offsetHeight / 4) {\n finish({ status: \"cancelled\", reason: \"user\" });\n }\n };\n\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n (() => {\n const here = new URL(location.href);\n here.searchParams.set(PARAM, session.id);\n return here;\n })(),\n );\n addEventListener(\"message\", onMessage);\n addEventListener(\"popstate\", onPop);\n addEventListener(\"keydown\", onKey);\n vv?.addEventListener(\"resize\", layout);\n vv?.addEventListener(\"scroll\", layout);\n inert.forEach((el) => (el.inert = true));\n history.scrollRestoration = \"manual\";\n body.style.cssText += `;position:fixed;top:${-y}px;left:0;right:0;overflow:hidden`;\n body.append(root);\n layout();\n if (!still) {\n panel.animate?.(\n sheet\n ? [{ transform: \"translateY(100%)\" }, { transform: \"none\" }]\n : [{ opacity: 0 }, { opacity: 1 }],\n { duration: sheet ? 280 : 160, easing: \"cubic-bezier(.2,.8,.2,1)\" },\n );\n }\n request = close;\n return new Promise((resolve) => (settle = resolve));\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { CheckoutClient } from \"./client\";\nimport { ZappieError } from \"./sdk\";\nimport type { OpenCheckoutSession } from \"./types\";\n\n/** How checkout opens. Auto redirects in in-app browsers and framed pages, else a modal on desktop and a sheet on mobile. */\nexport type CheckoutPresentation =\n \"auto\" | \"modal\" | \"sheet\" | \"fullscreen\" | \"redirect\";\n\n/** The checkout look from toCheckoutTheme in @zappie/tokens/checkout. The checkout validates it before use. */\nexport type CheckoutTheme = {\n version: 1;\n tokens: Partial<Record<`--z-${string}`, string>>;\n logo?: { url: string; alt: string };\n fonts?: { family: string; role: \"body\" | \"display\"; weights?: number[] }[];\n copy?: { payButton?: string; successTitle?: string };\n};\n\n/**\n * Why checkout can't go on. `code` is `session_expired`, `session_invalid` or `internal`. When `retryable` is true,\n * checkout stays open and offers to try again.\n */\nexport type CheckoutEmbedError = {\n code: string;\n message: string;\n retryable: boolean;\n};\n\n/** Presentation, theme, step and error reporting for an embedded checkout. */\nexport type EmbedOptions = {\n presentation?: CheckoutPresentation;\n theme?: CheckoutTheme;\n /** Called with each checkout step, and whether a payment has started. */\n onStep?: (step: string, paying: boolean) => void;\n /** Called when checkout can't go on. A retryable error keeps it open, and any other error closes it with its code as the reason. */\n onError?: (error: CheckoutEmbedError) => void;\n};\n\n/** How an embedded checkout ended. A completed result is confirmed by the server first. */\nexport type CheckoutResult =\n | { status: \"completed\"; orderId: string }\n | { status: \"cancelled\"; reason: string }\n | { status: \"redirected\" };\n\nconst PARAM = \"zappie_checkout\";\nconst finished = new Set<string>();\nlet request: (() => void) | undefined;\nconst media = (query: string) => matchMedia(query).matches;\n\n/** Resolve auto to the presentation this browser gets. */\nexport function checkoutPresentation(\n presentation: CheckoutPresentation = \"auto\",\n): Exclude<CheckoutPresentation, \"auto\"> {\n if (presentation !== \"auto\") {\n return presentation;\n }\n if (\n window.top !== window ||\n /FBA[NV]|Instagram|TikTok|musical_ly|Bytedance|Line\\//.test(\n navigator.userAgent,\n )\n ) {\n return \"redirect\";\n }\n return media(\"(min-width:640px) and (pointer:fine),(min-width:1024px)\")\n ? \"modal\"\n : \"sheet\";\n}\n\n/** Ask the open checkout to close. Mid-payment it asks the shopper first. */\nexport function closeCheckout(): void {\n request?.();\n}\n\n/** On load, drop a stale checkout entry from the address and read its session. It never reopens checkout. */\nexport async function resumeCheckout(\n checkout: CheckoutClient,\n): Promise<CheckoutResult | null> {\n const url = new URL(location.href);\n const id = url.searchParams.get(PARAM);\n if (!id) {\n return null;\n }\n url.searchParams.delete(PARAM);\n history.replaceState(history.state, \"\", url);\n const session = await checkout.session(id);\n return session.status === \"completed\" && session.order_id\n ? { status: \"completed\", orderId: session.order_id }\n : null;\n}\n\n/**\n * Open a checkout session over the page and settle when it ends. Only Zappie's checkout, or one on this machine, is framed. Back and Escape ask the checkout to step back or\n * close. A completed result waits for the server to confirm the session. A completed session can't be reopened.\n */\nexport function embedCheckout(\n checkout: CheckoutClient,\n session: OpenCheckoutSession,\n options: EmbedOptions = {},\n): Promise<CheckoutResult> {\n const url = new URL(session.url);\n if (\n request ||\n session.status !== \"open\" ||\n finished.has(session.id) ||\n !(\n /^https:\\/\\/checkout\\.(stg\\.)?zappie\\.ai$/.test(url.origin) ||\n /^http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(url.origin)\n )\n ) {\n return Promise.reject(\n new ZappieError(\n \"invalid_request\",\n \"This checkout session can't be opened\",\n ),\n );\n }\n const shown = checkoutPresentation(options.presentation);\n if (shown === \"redirect\") {\n location.assign(session.url);\n return Promise.resolve({ status: \"redirected\" });\n }\n url.pathname = `/embed/${encodeURIComponent(session.id)}`;\n // The frame gets no referrer, so the checkout learns this page's origin from its address. It checks it first.\n url.searchParams.set(\"origin\", location.origin);\n const doc = document;\n const body = doc.body;\n const root = doc.createElement(\"div\");\n const panel = doc.createElement(\"div\");\n const handle = doc.createElement(\"div\");\n const frame = doc.createElement(\"iframe\");\n const sheet = shown === \"sheet\";\n const still = media(\"(prefers-reduced-motion:reduce)\");\n const opener = doc.activeElement as HTMLElement | null;\n const y = scrollY;\n const bodyStyle = body.style.cssText;\n const restoration = history.scrollRestoration;\n const inert = [...body.children].filter(\n (el): el is HTMLElement => !(el as HTMLElement).inert,\n );\n const abort = new AbortController();\n const vv = window.visualViewport;\n let ready = false;\n let paying = false;\n let height = 640;\n let settle: (result: CheckoutResult) => void = () => {};\n\n root.setAttribute(\"data-zappie-checkout\", shown);\n root.setAttribute(\"role\", \"dialog\");\n root.setAttribute(\"aria-modal\", \"true\");\n root.setAttribute(\"aria-label\", \"Checkout\");\n root.style.cssText =\n \"position:fixed;inset:0;z-index:2147483647;background:var(--z-checkout-backdrop,rgb(0 0 0/.5))\";\n panel.style.cssText = `position:absolute;overflow:hidden;display:flex;flex-direction:column;background:var(--z-surface,#fff);${\n shown === \"fullscreen\"\n ? \"inset:0\"\n : sheet\n ? \"left:0;right:0;border-radius:var(--z-checkout-radius,16px) var(--z-checkout-radius,16px) 0 0\"\n : \"inset:0;margin:auto;width:min(480px,100vw - 32px);border-radius:var(--z-checkout-radius,16px)\"\n }`;\n handle.style.cssText =\n \"flex:none;height:24px;touch-action:none;cursor:grab;background:radial-gradient(closest-side,var(--z-border,#ccc) 99%,#0000) 50%/40px 5px no-repeat\";\n frame.title = \"Checkout\";\n frame.src = url.href;\n frame.allow = \"payment 'src'; publickey-credentials-get 'src'\";\n frame.referrerPolicy = \"no-referrer\";\n frame.style.cssText = \"flex:1;width:100%;border:0;display:block\";\n if (sheet) {\n panel.append(handle);\n }\n panel.append(frame);\n root.append(panel);\n\n const layout = () => {\n const view = vv ?? { height: innerHeight, offsetTop: 0 };\n if (sheet) {\n panel.style.height = `${Math.round(view.height * 0.92)}px`;\n panel.style.bottom = `${Math.max(0, innerHeight - view.offsetTop - view.height)}px`;\n } else if (shown === \"modal\") {\n panel.style.height = `${Math.min(height, view.height - 32)}px`;\n }\n };\n const post = (type: string, payload: object = {}) =>\n frame.contentWindow?.postMessage(\n { source: \"zappie-checkout\", v: 1, session: session.id, type, payload },\n url.origin,\n );\n const finish = (result: CheckoutResult, popped?: boolean) => {\n abort.abort();\n request = undefined;\n removeEventListener(\"message\", onMessage);\n removeEventListener(\"popstate\", onPop);\n removeEventListener(\"keydown\", onKey);\n vv?.removeEventListener(\"resize\", layout);\n vv?.removeEventListener(\"scroll\", layout);\n root.remove();\n inert.forEach((el) => (el.inert = false));\n body.style.cssText = bodyStyle;\n // WebKit scrolls the page again after popstate, so the scroll is set once more a task later.\n const restore = () => {\n scrollTo(0, y);\n setTimeout(() => {\n scrollTo(0, y);\n history.scrollRestoration = restoration;\n });\n };\n restore();\n opener?.focus({ preventScroll: true });\n if (!popped && history.state?.[PARAM] === session.id) {\n history.scrollRestoration = \"manual\";\n addEventListener(\"popstate\", restore, { once: true });\n history.back();\n }\n settle(result);\n };\n const close = () =>\n ready\n ? post(\"close_request\")\n : finish({ status: \"cancelled\", reason: \"user\" });\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n close();\n }\n };\n const onPop = () => {\n if (!ready) {\n return finish({ status: \"cancelled\", reason: \"user\" }, true);\n }\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n location.href,\n );\n post(\"close_request\");\n };\n const onMessage = (event: MessageEvent) => {\n const data = event.data;\n if (\n event.origin !== url.origin ||\n event.source !== frame.contentWindow ||\n data?.source !== \"zappie-checkout\" ||\n data.v !== 1 ||\n data.session !== session.id\n ) {\n return;\n }\n const payload = Object(data.payload);\n if (data.type === \"ready\" || data.type === \"step\") {\n if (data.type === \"ready\") {\n ready = true;\n frame.focus();\n if (options.theme) {\n post(\"theme\", options.theme);\n }\n }\n paying = payload.paying === true;\n if (typeof payload.step === \"string\") {\n options.onStep?.(payload.step, paying);\n }\n } else if (data.type === \"resize\" && Number.isFinite(payload.height)) {\n height = payload.height;\n layout();\n } else if (data.type === \"completed\") {\n checkout\n .awaitCompletion(session.id, { signal: abort.signal, intervalMs: 1500 })\n .then(\n (done) => {\n if (done.status === \"completed\") {\n finished.add(session.id);\n finish({ status: \"completed\", orderId: done.order_id });\n } else {\n finish({ status: \"cancelled\", reason: done.status });\n }\n },\n () => {},\n );\n } else if (data.type === \"cancelled\" || data.type === \"error\") {\n const retry = data.type === \"error\" && payload.retryable === true;\n if (data.type === \"error\") {\n options.onError?.({\n code: payload.code,\n message: payload.message,\n retryable: retry,\n });\n }\n if (retry) {\n // The checkout shows Try again, and its next ready picks up from there. Until then Back and Escape close.\n ready = paying = false;\n } else {\n finish({\n status: \"cancelled\",\n reason: String(payload.reason ?? payload.code ?? \"user\"),\n });\n }\n }\n };\n\n let start = 0;\n handle.onpointerdown = (event) => {\n if (paying) {\n return;\n }\n start = event.clientY;\n handle.setPointerCapture?.(event.pointerId);\n };\n handle.onpointermove = (event) => {\n if (start) {\n panel.style.transform = `translateY(${Math.max(0, event.clientY - start)}px)`;\n }\n };\n handle.onpointerup = handle.onpointercancel = (event) => {\n const moved = start ? event.clientY - start : 0;\n start = 0;\n panel.style.transform = \"\";\n if (!paying && moved > panel.offsetHeight / 4) {\n finish({ status: \"cancelled\", reason: \"user\" });\n }\n };\n\n history.pushState(\n { ...history.state, [PARAM]: session.id },\n \"\",\n (() => {\n const here = new URL(location.href);\n here.searchParams.set(PARAM, session.id);\n return here;\n })(),\n );\n addEventListener(\"message\", onMessage);\n addEventListener(\"popstate\", onPop);\n addEventListener(\"keydown\", onKey);\n vv?.addEventListener(\"resize\", layout);\n vv?.addEventListener(\"scroll\", layout);\n inert.forEach((el) => (el.inert = true));\n history.scrollRestoration = \"manual\";\n body.style.cssText += `;position:fixed;top:${-y}px;left:0;right:0;overflow:hidden`;\n body.append(root);\n layout();\n if (!still) {\n panel.animate?.(\n sheet\n ? [{ transform: \"translateY(100%)\" }, { transform: \"none\" }]\n : [{ opacity: 0 }, { opacity: 1 }],\n { duration: sheet ? 280 : 160, easing: \"cubic-bezier(.2,.8,.2,1)\" },\n );\n }\n request = close;\n return new Promise((resolve) => (settle = resolve));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -138,4 +138,5 @@ declare function isContactPhoneValid(phone: string): boolean;
138
138
  */
139
139
  declare function payReadiness(state: PayReadinessQuoteState, contactPhoneValid: boolean, addressValid?: boolean): PayReadiness;
140
140
 
141
- export { type AwaitCompletionOptions, type CheckoutAddress, type CheckoutClient, type CheckoutClientOptions, type CheckoutCompletion, CheckoutError, type CheckoutFulfilment, type CheckoutRequest, type CheckoutSession, type OpenCheckoutSession, type PayReadiness, type PayReadinessQuoteState, createCheckout, isContactPhoneValid, payReadiness };
141
+ export { CheckoutError, createCheckout, isContactPhoneValid, payReadiness };
142
+ export type { AwaitCompletionOptions, CheckoutAddress, CheckoutClient, CheckoutClientOptions, CheckoutCompletion, CheckoutFulfilment, CheckoutRequest, CheckoutSession, OpenCheckoutSession, PayReadiness, PayReadinessQuoteState };
@@ -116,12 +116,23 @@ type CheckoutTheme = {
116
116
  successTitle?: string;
117
117
  };
118
118
  };
119
- /** Presentation, theme and step reporting for an embedded checkout. */
119
+ /**
120
+ * Why checkout can't go on. `code` is `session_expired`, `session_invalid` or `internal`. When `retryable` is true,
121
+ * checkout stays open and offers to try again.
122
+ */
123
+ type CheckoutEmbedError = {
124
+ code: string;
125
+ message: string;
126
+ retryable: boolean;
127
+ };
128
+ /** Presentation, theme, step and error reporting for an embedded checkout. */
120
129
  type EmbedOptions = {
121
130
  presentation?: CheckoutPresentation;
122
131
  theme?: CheckoutTheme;
123
132
  /** Called with each checkout step, and whether a payment has started. */
124
133
  onStep?: (step: string, paying: boolean) => void;
134
+ /** Called when checkout can't go on. A retryable error keeps it open, and any other error closes it with its code as the reason. */
135
+ onError?: (error: CheckoutEmbedError) => void;
125
136
  };
126
137
  /** How an embedded checkout ended. A completed result is confirmed by the server first. */
127
138
  type CheckoutResult = {
@@ -143,7 +154,7 @@ declare function closeCheckout(): void;
143
154
  declare function useCheckout(checkout: CheckoutClient): {
144
155
  open: (body: CheckoutRequest, key: string, options?: EmbedOptions) => Promise<CheckoutResult>;
145
156
  close: typeof closeCheckout;
146
- status: "open" | "completed" | "cancelled" | "redirected" | "idle" | "opening";
157
+ status: "completed" | "cancelled" | "redirected" | "idle" | "opening" | "open";
147
158
  step: string | undefined;
148
159
  };
149
160
 
@@ -134,4 +134,5 @@ declare function prefillFulfilment<T extends CartFulfilment | null>(fulfilment:
134
134
  */
135
135
  declare function watchCheckoutDefaults(customer: Pick<CustomerClient, "state" | "customer" | "onChange" | "addresses">, read: () => Omit<CheckoutDefaultsInput, "customer" | "savedAddresses">, listener: (defaults: CheckoutDefaults) => void): () => void;
136
136
 
137
- export { type AddressStorage, type CheckoutDefaults, type CheckoutDefaultsInput, type DeliveryFeeType, type PhoneDefault, checkoutDefaults, defaultAddress, defaultPhone, defaultRegion, prefillFulfilment, rememberAddress, rememberedAddress, restoreSchedule, watchCheckoutDefaults };
137
+ export { checkoutDefaults, defaultAddress, defaultPhone, defaultRegion, prefillFulfilment, rememberAddress, rememberedAddress, restoreSchedule, watchCheckoutDefaults };
138
+ export type { AddressStorage, CheckoutDefaults, CheckoutDefaultsInput, DeliveryFeeType, PhoneDefault };
@@ -1,5 +1,5 @@
1
- import { a as Menu, C as CategoryPage, S as Store, I as Item, M as Money, F as Formatters } from './models-DuLY7jMB.js';
2
- import { Q as QuoteRequest, g as WireQuote, i as CartIssueCode, d as CartModule, b as CartLineInput } from './types-COI1jnQ2.js';
1
+ import { a as Menu, C as CategoryPage, S as Store, I as Item, M as Money, F as Formatters } from './models-tf9rNoki.js';
2
+ import { Q as QuoteRequest, g as WireQuote, i as CartIssueCode, d as CartModule, b as CartLineInput } from './types-B3Ghmpmy.js';
3
3
 
4
4
  /** Stable public error codes. */
5
5
  type ZappieErrorCode = "network_error" | "timeout" | "rate_limited" | "unauthorized" | "forbidden_origin" | "not_found" | "store_not_found" | "branch_not_found" | "item_not_found" | "cart_invalid" | "invalid_request" | "version_unsupported" | "server_error";
@@ -397,4 +397,5 @@ declare function createZappie(config: ZappieConfig): Readonly<{
397
397
  };
398
398
  }>;
399
399
 
400
- export { API_VERSION as A, type SelectedMenuTransport as B, Cache as C, createSelectedMenuReader as D, type ErrorDetails as E, selectedMenu as F, selectedMenuKey as G, type MenuResource as M, type RequestCtx as R, type StorageLike as S, type Transport as T, type Zappie as Z, ZappieError as a, type ZappieConfig as b, type StoreResource as c, type ZappieEvents as d, type ZappieErrorCode as e, type MenuFulfilment as f, type MenuResponse as g, type SelectedMenuRequest as h, type SelectedMenuResponse as i, type SearchRequest as j, type SearchPage as k, type MenuVersion as l, type MenuReadOptions as m, type ReadOptions as n, createZappie as o, isZappieError as p, CATALOGUE_MAX_AGE_MS as q, CATALOGUE_MAX_BYTES as r, type CatalogueScope as s, type SavedCatalogue as t, catalogueDigest as u, validateConfig as v, catalogueKey as w, createCatalogueStorage as x, createSavedCatalogue as y, type SelectedMenu as z };
400
+ export { API_VERSION as A, Cache as C, createSelectedMenuReader as D, selectedMenu as F, selectedMenuKey as G, ZappieError as a, createZappie as o, isZappieError as p, CATALOGUE_MAX_AGE_MS as q, CATALOGUE_MAX_BYTES as r, catalogueDigest as u, validateConfig as v, catalogueKey as w, createCatalogueStorage as x, createSavedCatalogue as y };
401
+ export type { SelectedMenuTransport as B, ErrorDetails as E, MenuResource as M, RequestCtx as R, StorageLike as S, Transport as T, Zappie as Z, ZappieConfig as b, StoreResource as c, ZappieEvents as d, ZappieErrorCode as e, MenuFulfilment as f, MenuResponse as g, SelectedMenuRequest as h, SelectedMenuResponse as i, SearchRequest as j, SearchPage as k, MenuVersion as l, MenuReadOptions as m, ReadOptions as n, CatalogueScope as s, SavedCatalogue as t, SelectedMenu as z };
@@ -45,6 +45,8 @@ type ZappieSiteConfig = {
45
45
  styleSrc?: string[];
46
46
  frameSrc?: string[];
47
47
  };
48
+ /** Hosts outside Zappie that built pages may link to, such as the store's own domains. */
49
+ links?: string[];
48
50
  };
49
51
  /** Each budget left out uses DEFAULT_BUDGETS. */
50
52
  budgets?: {
@@ -126,4 +128,5 @@ declare function validateConfig(input: unknown): ConfigValidation;
126
128
  */
127
129
  declare function defineConfig(config: ZappieSiteConfig): ZappieSiteConfig;
128
130
 
129
- export { type ConfigIssue, type ConfigValidation, DEFAULT_BUDGETS, GLOBAL_PACKAGE_ALLOWLIST, type ZappieSiteConfig, defineConfig, validateConfig };
131
+ export { DEFAULT_BUDGETS, GLOBAL_PACKAGE_ALLOWLIST, defineConfig, validateConfig };
132
+ export type { ConfigIssue, ConfigValidation, ZappieSiteConfig };
@@ -172,4 +172,5 @@ declare class CustomerError extends ZappieError {
172
172
  constructor(status: number, apiCode: string, message: string, param?: string, issues?: unknown);
173
173
  }
174
174
 
175
- export { type AuthState, type CreateAddress, type CustomerClient, type CustomerClientOptions, CustomerError, type CustomerListener, type CustomerOrder, type CustomerProfile, type Money, type OrderChoice, type OrderLine, type OrdersPage, type SavedAddress, type SessionCustomer, type WatchOrderOptions, createCustomer };
175
+ export { CustomerError, createCustomer };
176
+ export type { AuthState, CreateAddress, CustomerClient, CustomerClientOptions, CustomerListener, CustomerOrder, CustomerProfile, Money, OrderChoice, OrderLine, OrdersPage, SavedAddress, SessionCustomer, WatchOrderOptions };
package/dist/debug.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { b as CartLineInput, a as Quote } from './types-COI1jnQ2.js';
2
- import { M as Money, a as Menu, B as Branch } from './models-DuLY7jMB.js';
3
- import { Z as Zappie } from './client-yJQj780A.js';
1
+ import { CartLineInput, Quote } from './index.js';
2
+ import { Money, Menu, Branch } from './index.js';
3
+ import { Zappie } from './index.js';
4
4
 
5
5
  type StoredLine = CartLineInput & {
6
6
  id: string;
@@ -43,4 +43,5 @@ declare global {
43
43
  }
44
44
  declare function installDebug(client: Zappie): void;
45
45
 
46
- export { type DebugEvent, type ZappieDebug, installDebug };
46
+ export { installDebug };
47
+ export type { DebugEvent, ZappieDebug };
package/dist/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/debug.ts
2
- var VERSION = true ? "0.1.23" : "0.0.0-development";
2
+ var VERSION = true ? "0.2.0" : "0.0.0-development";
3
3
  function installDebug(client) {
4
4
  if (typeof window === "undefined" || client.mode !== "test") {
5
5
  return;
package/dist/debug.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/debug.ts"],
4
4
  "sourcesContent": ["import type { StoredLine } from \"./cart/storage\";\nimport type { CartLineInput, Quote } from \"./cart/types\";\nimport type { Zappie } from \"./client\";\nimport type { Branch, Menu } from \"./models\";\n\n// Inline like VERSION in index.ts: tsup fills in the version from package.json.\ndeclare const __ZAPPIE_SDK_VERSION__: string | undefined;\nconst VERSION =\n typeof __ZAPPIE_SDK_VERSION__ === \"string\"\n ? __ZAPPIE_SDK_VERSION__\n : \"0.0.0-development\";\n\nexport type DebugEvent = {\n name: string;\n detail: unknown;\n at: string;\n};\n\nexport type ZappieDebug = {\n version: string;\n store: string;\n /** Read this cart's branch and full menu through its client. */\n catalogue(): Promise<{ menu: Menu; branch: Branch }>;\n cart: (() => ReturnType<Zappie[\"cart\"][\"getSnapshot\"]>) & {\n /**\n * Save test cart lines for the next reload. Reload to reconcile them with today's menu.\n * Your unitPrice is only a local estimate, like a price edited in browser storage.\n * The server re-prices every line and never trusts this estimate.\n */\n inject(input: {\n lines: (CartLineInput & Pick<StoredLine, \"unitPrice\">)[];\n }): Promise<void>;\n };\n quote: () => Quote | undefined;\n events: DebugEvent[];\n};\n\ndeclare global {\n interface Window {\n __zappie?: ZappieDebug;\n }\n // On globalThis so the lazy checkout and customer entries can reach it\n // with the shortest possible call; in a browser it is window's.\n var __zappieEmit: ((name: string, detail: unknown) => void) | undefined;\n}\n\nexport function installDebug(client: Zappie) {\n if (typeof window === \"undefined\" || client.mode !== \"test\") {\n return;\n }\n const events: DebugEvent[] = [];\n let quote: Quote | undefined;\n const emit = (name: string, detail: unknown) => {\n const event = { name, detail, at: new Date().toISOString() };\n events.push(event);\n window.dispatchEvent(new CustomEvent(\"zappie:event\", { detail: event }));\n };\n globalThis.__zappieEmit = emit;\n window.__zappie = {\n version: VERSION,\n store: client.config.store,\n async catalogue() {\n await client.cart.hydrate();\n const branch = (await client.store.get()).branches.find(\n (value) => value.id === client.cart.getSnapshot().branchId,\n )!;\n return { branch, menu: await client.menu.get({ branch: branch.id }) };\n },\n cart: Object.assign(() => client.cart.getSnapshot(), {\n async inject(input: {\n lines: (CartLineInput & Pick<StoredLine, \"unitPrice\">)[];\n }) {\n await client.cart.hydrate();\n if (\n input.lines.length > 100 ||\n input.lines.some(\n (line) =>\n !Number.isInteger(line.quantity ?? 1) ||\n (line.quantity ?? 1) < 1 ||\n (line.quantity ?? 1) > 99,\n )\n ) {\n throw new Error(\n \"Use up to 100 cart lines with whole quantities from 1 to 99.\",\n );\n }\n const snapshot = client.cart.getSnapshot();\n const value = {\n v: 1,\n menuVersion: client._internal.cartCatalogue()!.version,\n updatedAt: Date.now(),\n fulfilment: snapshot.fulfilment,\n promoCode: snapshot.promoCode,\n lines: input.lines.map((line, index) => ({\n ...line,\n id: `debug_${index}`,\n name: line.itemId,\n quantity: line.quantity ?? 1,\n choices: (line.choices ?? []).map((choice) => ({\n ...choice,\n quantity: choice.quantity ?? 1,\n })),\n inputs: line.inputs ?? {},\n addedAt: new Date().toISOString(),\n })),\n };\n const scope = client.config.cart?.storageScope;\n (client.config.storage ?? window.localStorage).setItem(\n `zappie:cart:v1:${client.config.store}:${snapshot.branchId}${scope ? `:${scope}` : \"\"}`,\n JSON.stringify(value),\n );\n },\n }),\n quote: () => quote,\n events,\n };\n client.on(\"add_to_cart\", (detail) => emit(\"add_to_cart\", detail));\n client.cart.subscribe(() => {\n const state = client.cart.state;\n const next = \"quote\" in state ? state.quote : undefined;\n if (next && next !== quote) {\n quote = next;\n emit(\"quote\", next);\n }\n });\n}\n"],
5
- "mappings": ";AAOA,IAAM,UACJ,OACI,WACA;AAoCC,SAAS,aAAa,QAAgB;AAC3C,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,QAAQ;AAC3D;AAAA,EACF;AACA,QAAM,SAAuB,CAAC;AAC9B,MAAI;AACJ,QAAM,OAAO,CAAC,MAAc,WAAoB;AAC9C,UAAM,QAAQ,EAAE,MAAM,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC3D,WAAO,KAAK,KAAK;AACjB,WAAO,cAAc,IAAI,YAAY,gBAAgB,EAAE,QAAQ,MAAM,CAAC,CAAC;AAAA,EACzE;AACA,aAAW,eAAe;AAC1B,SAAO,WAAW;AAAA,IAChB,SAAS;AAAA,IACT,OAAO,OAAO,OAAO;AAAA,IACrB,MAAM,YAAY;AAChB,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,UAAU,MAAM,OAAO,MAAM,IAAI,GAAG,SAAS;AAAA,QACjD,CAAC,UAAU,MAAM,OAAO,OAAO,KAAK,YAAY,EAAE;AAAA,MACpD;AACA,aAAO,EAAE,QAAQ,MAAM,MAAM,OAAO,KAAK,IAAI,EAAE,QAAQ,OAAO,GAAG,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,YAAY,GAAG;AAAA,MACnD,MAAM,OAAO,OAEV;AACD,cAAM,OAAO,KAAK,QAAQ;AAC1B,YACE,MAAM,MAAM,SAAS,OACrB,MAAM,MAAM;AAAA,UACV,CAAC,SACC,CAAC,OAAO,UAAU,KAAK,YAAY,CAAC,MACnC,KAAK,YAAY,KAAK,MACtB,KAAK,YAAY,KAAK;AAAA,QAC3B,GACA;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,cAAM,WAAW,OAAO,KAAK,YAAY;AACzC,cAAM,QAAQ;AAAA,UACZ,GAAG;AAAA,UACH,aAAa,OAAO,UAAU,cAAc,EAAG;AAAA,UAC/C,WAAW,KAAK,IAAI;AAAA,UACpB,YAAY,SAAS;AAAA,UACrB,WAAW,SAAS;AAAA,UACpB,OAAO,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,YACvC,GAAG;AAAA,YACH,IAAI,SAAS,KAAK;AAAA,YAClB,MAAM,KAAK;AAAA,YACX,UAAU,KAAK,YAAY;AAAA,YAC3B,UAAU,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,cAC7C,GAAG;AAAA,cACH,UAAU,OAAO,YAAY;AAAA,YAC/B,EAAE;AAAA,YACF,QAAQ,KAAK,UAAU,CAAC;AAAA,YACxB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,EAAE;AAAA,QACJ;AACA,cAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAC,OAAO,OAAO,WAAW,OAAO,cAAc;AAAA,UAC7C,kBAAkB,OAAO,OAAO,KAAK,IAAI,SAAS,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,UACrF,KAAK,UAAU,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,OAAO,MAAM;AAAA,IACb;AAAA,EACF;AACA,SAAO,GAAG,eAAe,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAChE,SAAO,KAAK,UAAU,MAAM;AAC1B,UAAM,QAAQ,OAAO,KAAK;AAC1B,UAAM,OAAO,WAAW,QAAQ,MAAM,QAAQ;AAC9C,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ;AACR,WAAK,SAAS,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AACH;",
5
+ "mappings": ";AAOA,IAAM,UACJ,OACI,UACA;AAoCC,SAAS,aAAa,QAAgB;AAC3C,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,QAAQ;AAC3D;AAAA,EACF;AACA,QAAM,SAAuB,CAAC;AAC9B,MAAI;AACJ,QAAM,OAAO,CAAC,MAAc,WAAoB;AAC9C,UAAM,QAAQ,EAAE,MAAM,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC3D,WAAO,KAAK,KAAK;AACjB,WAAO,cAAc,IAAI,YAAY,gBAAgB,EAAE,QAAQ,MAAM,CAAC,CAAC;AAAA,EACzE;AACA,aAAW,eAAe;AAC1B,SAAO,WAAW;AAAA,IAChB,SAAS;AAAA,IACT,OAAO,OAAO,OAAO;AAAA,IACrB,MAAM,YAAY;AAChB,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,UAAU,MAAM,OAAO,MAAM,IAAI,GAAG,SAAS;AAAA,QACjD,CAAC,UAAU,MAAM,OAAO,OAAO,KAAK,YAAY,EAAE;AAAA,MACpD;AACA,aAAO,EAAE,QAAQ,MAAM,MAAM,OAAO,KAAK,IAAI,EAAE,QAAQ,OAAO,GAAG,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,YAAY,GAAG;AAAA,MACnD,MAAM,OAAO,OAEV;AACD,cAAM,OAAO,KAAK,QAAQ;AAC1B,YACE,MAAM,MAAM,SAAS,OACrB,MAAM,MAAM;AAAA,UACV,CAAC,SACC,CAAC,OAAO,UAAU,KAAK,YAAY,CAAC,MACnC,KAAK,YAAY,KAAK,MACtB,KAAK,YAAY,KAAK;AAAA,QAC3B,GACA;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,cAAM,WAAW,OAAO,KAAK,YAAY;AACzC,cAAM,QAAQ;AAAA,UACZ,GAAG;AAAA,UACH,aAAa,OAAO,UAAU,cAAc,EAAG;AAAA,UAC/C,WAAW,KAAK,IAAI;AAAA,UACpB,YAAY,SAAS;AAAA,UACrB,WAAW,SAAS;AAAA,UACpB,OAAO,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,YACvC,GAAG;AAAA,YACH,IAAI,SAAS,KAAK;AAAA,YAClB,MAAM,KAAK;AAAA,YACX,UAAU,KAAK,YAAY;AAAA,YAC3B,UAAU,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,cAC7C,GAAG;AAAA,cACH,UAAU,OAAO,YAAY;AAAA,YAC/B,EAAE;AAAA,YACF,QAAQ,KAAK,UAAU,CAAC;AAAA,YACxB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,EAAE;AAAA,QACJ;AACA,cAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAC,OAAO,OAAO,WAAW,OAAO,cAAc;AAAA,UAC7C,kBAAkB,OAAO,OAAO,KAAK,IAAI,SAAS,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,UACrF,KAAK,UAAU,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,OAAO,MAAM;AAAA,IACb;AAAA,EACF;AACA,SAAO,GAAG,eAAe,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAChE,SAAO,KAAK,UAAU,MAAM;AAC1B,UAAM,QAAQ,OAAO,KAAK;AAC1B,UAAM,OAAO,WAAW,QAAQ,MAAM,QAAQ;AAC9C,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ;AACR,WAAK,SAAS,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AACH;",
6
6
  "names": []
7
7
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { a as ZappieError, T as Transport, R as RequestCtx, f as MenuFulfilment, g as MenuResponse, h as SelectedMenuRequest, i as SelectedMenuResponse, j as SearchRequest, k as SearchPage, l as MenuVersion } from './client-yJQj780A.js';
2
- export { A as API_VERSION, E as ErrorDetails, m as MenuReadOptions, M as MenuResource, n as ReadOptions, S as StorageLike, c as StoreResource, Z as Zappie, b as ZappieClientOptions, b as ZappieConfig, e as ZappieErrorCode, d as ZappieEvents, o as createZappie, p as isZappieError } from './client-yJQj780A.js';
3
- import { f as CartIssue, b as CartLineInput, C as CartLine, Q as QuoteRequest, g as WireQuote } from './types-COI1jnQ2.js';
4
- export { h as CartFix, c as CartFulfilment, i as CartIssueCode, d as CartModule, j as CartSnapshot, e as CartState, k as CartSyncAdapter, l as CartSyncDocument, D as DeliveryAddress, a as Quote, m as WireFix, W as WireFulfilment, n as WireIssue, o as WireLine, p as WireMoney } from './types-COI1jnQ2.js';
5
- import { I as Item, O as OptionGroup, M as Money, a as Menu, B as Branch, S as Store, C as CategoryPage } from './models-DuLY7jMB.js';
6
- export { b as Category, c as Choice, d as Currency, F as Formatters, e as Fulfilment, f as MenuSchedule, g as MenuSection, h as MoneyFormatOptions, i as OpeningTime, j as OptionCondition, P as PaymentMethodKind, k as SpecialHours, V as Variant, l as format, m as money } from './models-DuLY7jMB.js';
1
+ import { a as ZappieError, T as Transport, R as RequestCtx, f as MenuFulfilment, g as MenuResponse, j as SearchRequest, k as SearchPage, l as MenuVersion } from './client-B377AzNC.js';
2
+ import { SelectedMenuRequest, SelectedMenuResponse } from './transport/selected-menu.js';
3
+ export { A as API_VERSION, E as ErrorDetails, m as MenuReadOptions, M as MenuResource, n as ReadOptions, S as StorageLike, c as StoreResource, Z as Zappie, b as ZappieClientOptions, b as ZappieConfig, e as ZappieErrorCode, d as ZappieEvents, o as createZappie, p as isZappieError } from './client-B377AzNC.js';
4
+ import { f as CartIssue, b as CartLineInput, C as CartLine, Q as QuoteRequest, g as WireQuote } from './types-B3Ghmpmy.js';
5
+ export { h as CartFix, c as CartFulfilment, i as CartIssueCode, d as CartModule, j as CartSnapshot, e as CartState, k as CartSyncAdapter, l as CartSyncDocument, D as DeliveryAddress, a as Quote, m as WireFix, W as WireFulfilment, n as WireIssue, o as WireLine, p as WireMoney } from './types-B3Ghmpmy.js';
6
+ import { I as Item, O as OptionGroup, M as Money, a as Menu, B as Branch, S as Store, C as CategoryPage } from './models-tf9rNoki.js';
7
+ export { b as Category, c as Choice, d as Currency, F as Formatters, e as Fulfilment, f as MenuSchedule, g as MenuSection, h as MoneyFormatOptions, i as OpeningTime, j as OptionCondition, P as PaymentMethodKind, k as SpecialHours, V as Variant, l as format, m as money } from './models-tf9rNoki.js';
7
8
 
8
9
  declare class ZappieCartError extends ZappieError {
9
10
  readonly issues: CartIssue[];
@@ -85,4 +86,5 @@ declare class RestTransport implements Transport {
85
86
  /** The installed @zappie/sdk version. */
86
87
  declare const VERSION: string;
87
88
 
88
- export { Branch, CartIssue, CartLine, CartLineInput, CategoryPage, Item, Menu, MenuFulfilment, MenuResponse, MenuVersion, Money, OptionGroup, QuoteRequest, RequestCtx, RestTransport, type RestTransportOptions, SearchPage, SearchRequest, Store, Transport, VERSION, WireQuote, ZappieCartError, ZappieError, estimateUnitPrice, previewItem, resolveSourceMenu, validateLine };
89
+ export { Branch, CartIssue, CartLine, CartLineInput, CategoryPage, Item, Menu, MenuFulfilment, MenuResponse, MenuVersion, Money, OptionGroup, QuoteRequest, RequestCtx, RestTransport, SearchPage, SearchRequest, Store, Transport, VERSION, WireQuote, ZappieCartError, ZappieError, estimateUnitPrice, previewItem, resolveSourceMenu, validateLine };
90
+ export type { RestTransportOptions };
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { ZappieError, isZappieError } from "./errors.js";
10
10
  export * from "./models.js";
11
11
  import { format, money } from "./money.js";
12
12
  import { RestTransport } from "./transport/rest.js";
13
- var VERSION = true ? "0.1.23" : "0.0.0-development";
13
+ var VERSION = true ? "0.2.0" : "0.0.0-development";
14
14
  export {
15
15
  API_VERSION,
16
16
  RestTransport,
package/dist/index.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
4
  "sourcesContent": ["// tsup replaces __ZAPPIE_SDK_VERSION__ with package.json's version. Source run\n// without a build, as in a plain Node script, gets the development label.\n// Kept inline, not in a shared module, so the core chunks don't split differently.\ndeclare const __ZAPPIE_SDK_VERSION__: string | undefined;\n\n/** The installed @zappie/sdk version. */\nexport const VERSION: string =\n typeof __ZAPPIE_SDK_VERSION__ === \"string\"\n ? __ZAPPIE_SDK_VERSION__\n : \"0.0.0-development\";\nexport { ZappieCartError } from \"./cart/errors\";\nexport { previewItem } from \"./cart/preview\";\nexport { estimateUnitPrice } from \"./cart/price\";\nexport { resolveSourceMenu } from \"./cart/source-menu\";\nexport type * from \"./cart/types\";\nexport { validateLine } from \"./cart/validate\";\nexport { createZappie } from \"./client\";\nexport type {\n MenuFulfilment,\n MenuReadOptions,\n MenuResource,\n ReadOptions,\n StoreResource,\n Zappie,\n ZappieEvents,\n} from \"./client\";\nexport { API_VERSION } from \"./config\";\nexport type {\n StorageLike,\n ZappieConfig as ZappieClientOptions,\n ZappieConfig,\n} from \"./config\";\nexport { ZappieError, isZappieError } from \"./errors\";\nexport type { ErrorDetails, ZappieErrorCode } from \"./errors\";\nexport * from \"./models\";\nexport { format, money } from \"./money\";\nexport type { Currency, Formatters, Money, MoneyFormatOptions } from \"./money\";\nexport { RestTransport } from \"./transport/rest\";\nexport type { RestTransportOptions } from \"./transport/rest\";\nexport type {\n MenuResponse,\n MenuVersion,\n RequestCtx,\n SearchPage,\n SearchRequest,\n Transport,\n} from \"./transport/types\";\n"],
5
- "mappings": ";AAUA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAU7B,SAAS,mBAAmB;AAM5B,SAAS,aAAa,qBAAqB;AAE3C,cAAc;AACd,SAAS,QAAQ,aAAa;AAE9B,SAAS,qBAAqB;AA/BvB,IAAM,UACX,OACI,WACA;",
5
+ "mappings": ";AAUA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAU7B,SAAS,mBAAmB;AAM5B,SAAS,aAAa,qBAAqB;AAE3C,cAAc;AACd,SAAS,QAAQ,aAAa;AAE9B,SAAS,qBAAqB;AA/BvB,IAAM,UACX,OACI,UACA;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,6 @@
1
- import { R as RequestCtx } from '../client-yJQj780A.js';
2
- import '../models-DuLY7jMB.js';
3
- import '../types-COI1jnQ2.js';
1
+ import { RequestCtx } from '../index.js';
2
+ import '../models-tf9rNoki.js';
3
+ import '../types-B3Ghmpmy.js';
4
4
 
5
5
  /** Public integration IDs and browser keys. Your app decides when to load scripts. */
6
6
  type Integrations = {
@@ -39,4 +39,5 @@ declare function createIntegrations(options: IntegrationsOptions): {
39
39
  /** The integrations read returned by createIntegrations. */
40
40
  type IntegrationsClient = ReturnType<typeof createIntegrations>;
41
41
 
42
- export { type Integrations, type IntegrationsClient, type IntegrationsOptions, type IntegrationsTransport, createIntegrations };
42
+ export { createIntegrations };
43
+ export type { Integrations, IntegrationsClient, IntegrationsOptions, IntegrationsTransport };
@@ -1,6 +1,6 @@
1
- import { c as CartFulfilment, W as WireFulfilment } from './types-COI1jnQ2.js';
2
- export { v as validateConfig } from './client-yJQj780A.js';
3
- import './models-DuLY7jMB.js';
1
+ import { CartFulfilment, WireFulfilment } from './index.js';
2
+ export { v as validateConfig } from './client-B377AzNC.js';
3
+ import './models-tf9rNoki.js';
4
4
 
5
5
  /**
6
6
  * Convert a cart fulfilment selection to the API field names used by quotes and checkout.
@@ -271,4 +271,5 @@ type CategoryPage = {
271
271
  nextCursor?: string;
272
272
  };
273
273
 
274
- export { type Branch as B, type CategoryPage as C, type Formatters as F, type Item as I, type Money as M, type OptionGroup as O, type PaymentMethodKind as P, type Store as S, type Variant as V, type Menu as a, type Category as b, type Choice as c, type Currency as d, type Fulfilment as e, type MenuSchedule as f, type MenuSection as g, type MoneyFormatOptions as h, type OpeningTime as i, type OptionCondition as j, type SpecialHours as k, format as l, money as m };
274
+ export { format as l, money as m };
275
+ export type { Branch as B, CategoryPage as C, Formatters as F, Item as I, Money as M, OptionGroup as O, PaymentMethodKind as P, Store as S, Variant as V, Menu as a, Category as b, Choice as c, Currency as d, Fulfilment as e, MenuSchedule as f, MenuSection as g, MoneyFormatOptions as h, OpeningTime as i, OptionCondition as j, SpecialHours as k };
@@ -1,3 +1,3 @@
1
- export { q as CATALOGUE_MAX_AGE_MS, r as CATALOGUE_MAX_BYTES, s as CatalogueScope, t as SavedCatalogue, u as catalogueDigest, w as catalogueKey, x as createCatalogueStorage, y as createSavedCatalogue } from '../client-yJQj780A.js';
2
- import '../models-DuLY7jMB.js';
3
- import '../types-COI1jnQ2.js';
1
+ export { q as CATALOGUE_MAX_AGE_MS, r as CATALOGUE_MAX_BYTES, s as CatalogueScope, t as SavedCatalogue, u as catalogueDigest, w as catalogueKey, x as createCatalogueStorage, y as createSavedCatalogue } from '../client-B377AzNC.js';
2
+ import '../models-tf9rNoki.js';
3
+ import '../types-B3Ghmpmy.js';
@@ -122,4 +122,5 @@ declare class PlacesError extends ZappieError {
122
122
  constructor(status: number, apiCode: string, message: string, param?: string, issues?: unknown);
123
123
  }
124
124
 
125
- export { type AddressSearchOptions, type Place, type PlaceOptions, type PlaceSuggestion, type PlacesClient, type PlacesClientOptions, PlacesError, type SignalOptions, type ZoneAddress, type ZoneCheck, type ZoneCheckRequest, createPlaces };
125
+ export { PlacesError, createPlaces };
126
+ export type { AddressSearchOptions, Place, PlaceOptions, PlaceSuggestion, PlacesClient, PlacesClientOptions, SignalOptions, ZoneAddress, ZoneCheck, ZoneCheckRequest };
@@ -1,9 +1,9 @@
1
1
  export { VERSION } from '../index.js';
2
- import { a as ZappieError, Z as Zappie } from '../client-yJQj780A.js';
3
- import { I as Item, a as Menu, S as Store, M as Money } from '../models-DuLY7jMB.js';
2
+ import { ZappieError, Zappie } from '../index.js';
3
+ import { Item, Menu, Store, Money } from '../index.js';
4
4
  import * as react from 'react';
5
5
  import { ReactNode } from 'react';
6
- import { d as CartModule, C as CartLine, c as CartFulfilment, e as CartState, f as CartIssue } from '../types-COI1jnQ2.js';
6
+ import { CartModule, CartLine, CartFulfilment, CartState, CartIssue } from '../index.js';
7
7
 
8
8
  /** Subscribe to storefront identity and branch-directory data. */
9
9
  declare function useStore(): {
@@ -11,5 +11,7 @@
11
11
  * @returns A same-origin relative target or `fallback`.
12
12
  */
13
13
  declare function safeRedirect(to: string | null | undefined, fallback: string): string;
14
+ /** Move a window, this one by default, to Zappie's checkout. Any other URL throws a TypeError. */
15
+ declare function redirectToCheckout(url: string, target?: Window): void;
14
16
 
15
- export { safeRedirect };
17
+ export { redirectToCheckout, safeRedirect };
@@ -16,7 +16,16 @@ function safeRedirect(to, fallback) {
16
16
  return fallback;
17
17
  }
18
18
  }
19
+ function redirectToCheckout(url, target) {
20
+ if (!/^(https:\/\/checkout(\.stg)?\.zappie\.ai|http:\/\/(localhost|127\.0\.0\.1)(:\d+)?)\//.test(
21
+ url
22
+ )) {
23
+ throw new TypeError("Not a Zappie checkout URL");
24
+ }
25
+ (target ?? globalThis).location.assign(url);
26
+ }
19
27
  export {
28
+ redirectToCheckout,
20
29
  safeRedirect
21
30
  };
22
31
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/redirect/index.ts"],
4
- "sourcesContent": ["/**\n * Return a same-origin redirect target, or a trusted fallback.\n *\n * The target must resolve to an HTTP(S) URL on the current origin and its\n * returned path must begin with exactly one slash. On the server, only relative\n * targets can match the sentinel origin because there is no request origin to\n * compare.\n *\n * @param to - The untrusted redirect target, such as a query parameter.\n * @param fallback - The trusted target to use when `to` is unsafe.\n * @returns A same-origin relative target or `fallback`.\n */\nexport function safeRedirect(\n to: string | null | undefined,\n fallback: string,\n): string {\n if (\n typeof to !== \"string\" ||\n /[\\u0000-\\u0020\\u007f\\s\\\\]/u.test(to) ||\n /%(?:0[\\da-f]|1[\\da-f]|20|7f)/iu.test(to)\n ) {\n return fallback;\n }\n try {\n const browserOrigin =\n typeof location === \"undefined\" ? undefined : location.origin;\n if (browserOrigin === \"null\") {\n return fallback;\n }\n const origin = browserOrigin ?? \"https://safe-redirect.invalid\";\n const target = new URL(to, origin);\n const path = `${target.pathname}${target.search}${target.hash}`;\n return (target.protocol === \"http:\" || target.protocol === \"https:\") &&\n target.origin === origin &&\n /^\\/(?!\\/)/.test(path)\n ? path\n : fallback;\n } catch {\n return fallback;\n }\n}\n"],
5
- "mappings": ";AAYO,SAAS,aACd,IACA,UACQ;AACR,MACE,OAAO,OAAO,YACd,6BAA6B,KAAK,EAAE,KACpC,iCAAiC,KAAK,EAAE,GACxC;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,gBACJ,OAAO,aAAa,cAAc,SAAY,SAAS;AACzD,QAAI,kBAAkB,QAAQ;AAC5B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,iBAAiB;AAChC,UAAM,SAAS,IAAI,IAAI,IAAI,MAAM;AACjC,UAAM,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,MAAM,GAAG,OAAO,IAAI;AAC7D,YAAQ,OAAO,aAAa,WAAW,OAAO,aAAa,aACzD,OAAO,WAAW,UAClB,YAAY,KAAK,IAAI,IACnB,OACA;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * Return a same-origin redirect target, or a trusted fallback.\n *\n * The target must resolve to an HTTP(S) URL on the current origin and its\n * returned path must begin with exactly one slash. On the server, only relative\n * targets can match the sentinel origin because there is no request origin to\n * compare.\n *\n * @param to - The untrusted redirect target, such as a query parameter.\n * @param fallback - The trusted target to use when `to` is unsafe.\n * @returns A same-origin relative target or `fallback`.\n */\nexport function safeRedirect(\n to: string | null | undefined,\n fallback: string,\n): string {\n if (\n typeof to !== \"string\" ||\n /[\\u0000-\\u0020\\u007f\\s\\\\]/u.test(to) ||\n /%(?:0[\\da-f]|1[\\da-f]|20|7f)/iu.test(to)\n ) {\n return fallback;\n }\n try {\n const browserOrigin =\n typeof location === \"undefined\" ? undefined : location.origin;\n if (browserOrigin === \"null\") {\n return fallback;\n }\n const origin = browserOrigin ?? \"https://safe-redirect.invalid\";\n const target = new URL(to, origin);\n const path = `${target.pathname}${target.search}${target.hash}`;\n return (target.protocol === \"http:\" || target.protocol === \"https:\") &&\n target.origin === origin &&\n /^\\/(?!\\/)/.test(path)\n ? path\n : fallback;\n } catch {\n return fallback;\n }\n}\n\n/** Move a window, this one by default, to Zappie's checkout. Any other URL throws a TypeError. */\nexport function redirectToCheckout(url: string, target?: Window): void {\n // The slash after the host rules out look-alike hosts.\n if (\n !/^(https:\\/\\/checkout(\\.stg)?\\.zappie\\.ai|http:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?)\\//.test(\n url,\n )\n ) {\n throw new TypeError(\"Not a Zappie checkout URL\");\n }\n (target ?? globalThis).location.assign(url);\n}\n"],
5
+ "mappings": ";AAYO,SAAS,aACd,IACA,UACQ;AACR,MACE,OAAO,OAAO,YACd,6BAA6B,KAAK,EAAE,KACpC,iCAAiC,KAAK,EAAE,GACxC;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,gBACJ,OAAO,aAAa,cAAc,SAAY,SAAS;AACzD,QAAI,kBAAkB,QAAQ;AAC5B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,iBAAiB;AAChC,UAAM,SAAS,IAAI,IAAI,IAAI,MAAM;AACjC,UAAM,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,MAAM,GAAG,OAAO,IAAI;AAC7D,YAAQ,OAAO,aAAa,WAAW,OAAO,aAAa,aACzD,OAAO,WAAW,UAClB,YAAY,KAAK,IAAI,IACnB,OACA;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,mBAAmB,KAAa,QAAuB;AAErE,MACE,CAAC,uFAAuF;AAAA,IACtF;AAAA,EACF,GACA;AACA,UAAM,IAAI,UAAU,2BAA2B;AAAA,EACjD;AACA,GAAC,UAAU,YAAY,SAAS,OAAO,GAAG;AAC5C;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,8 @@
1
- import { a as Menu, S as Store, F as Formatters } from '../models-DuLY7jMB.js';
2
- import { a as ZappieError, b as ZappieConfig, T as Transport, S as StorageLike, c as StoreResource, M as MenuResource, d as ZappieEvents, C as Cache } from '../client-yJQj780A.js';
1
+ import { Menu, Store, Formatters } from '../index.js';
2
+ import { C as Cache } from '../client-B377AzNC.js';
3
+ import { ZappieError, ZappieConfig, Transport, StorageLike, StoreResource, MenuResource, ZappieEvents } from '../index.js';
3
4
  export { VERSION } from '../index.js';
4
- import { d as CartModule, b as CartLineInput } from '../types-COI1jnQ2.js';
5
+ import { CartModule, CartLineInput } from '../index.js';
5
6
 
6
7
  /**
7
8
  * A URL string, URL object or Request you can parse as a storefront route.
@@ -155,8 +156,8 @@ declare const parseStorefrontUrl: (input: StorefrontUrlInput, options?: Storefro
155
156
  * Find the canonical v2 URL for a recognized storefront route.
156
157
  * Compares normalized paths and sorted query parameters to avoid unnecessary redirects.
157
158
  *
158
- * Callers choose the HTTP status when serving the redirect.
159
- * Zappie templates use temporary redirects (302) for now.
159
+ * Serve the target as a permanent redirect (301). A classic URL has moved for
160
+ * good, so search engines carry its ranking to the site in one step.
160
161
  *
161
162
  * @returns A redirect target, or null for v1, an unknown route, a parse/build failure or an equivalent URL.
162
163
  */
@@ -518,6 +519,8 @@ type ZappieSiteConfig = {
518
519
  styleSrc?: string[];
519
520
  frameSrc?: string[];
520
521
  };
522
+ /** Hosts outside Zappie that built pages may link to, such as the store's own domains. */
523
+ links?: string[];
521
524
  };
522
525
  /** Each budget left out uses DEFAULT_BUDGETS. */
523
526
  budgets?: {
@@ -660,4 +663,5 @@ declare function createZappieServer({ secretKey, ...config }: ZappieServerConfig
660
663
  };
661
664
  }>;
662
665
 
663
- export { type AvailabilityPatch, type AvailabilityPatchInput, type CookieSessionStoreOptions, type EdgeSnapshotReader, type IdTokenChecks, type IdTokenClaims, type MenuSnapshot, type MenuSnapshotInput, type MenuSnapshotPayloadInput, type PkcePair, STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, type SealOptions, type SealSecret, type SecurityHeadersOptions, type SessionData, type SessionStore, type SnapshotVersionRecord, type StoreSnapshot, type StorefrontInstallation, type StorefrontPageState, type StorefrontQuery, type StorefrontRoute, type StorefrontUrlInput, type StorefrontUrlOptions, type TokenAlgorithm, type TokenClaims, type TokenErrorReason, type TokenVerifier, type TokenVerifierOptions, type VerifyTokenChecks, type VerifyWebhookOptions, type WebhookEvent, type WebhookEventType, type ZappieServerConfig, ZappieTokenError, ZappieWebhookSignatureError, applyAvailability, buildStorefrontUrl, canonicalJson, cookieSessionStore, createAvailabilityPatch, createMenuSnapshot, createPkce, createSecurityNonce, createStoreSnapshot, createTokenVerifier, createZappieServer, inlineScriptHashes, isMenuSnapshot, menuSnapshotFromPayload, openSealedValue, parseStorefrontUrl, randomToken, sealValue, securityHeaders, snapshotHash, snapshotKeys, storefrontRedirect, verifyWebhook };
666
+ export { STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, ZappieTokenError, ZappieWebhookSignatureError, applyAvailability, buildStorefrontUrl, canonicalJson, cookieSessionStore, createAvailabilityPatch, createMenuSnapshot, createPkce, createSecurityNonce, createStoreSnapshot, createTokenVerifier, createZappieServer, inlineScriptHashes, isMenuSnapshot, menuSnapshotFromPayload, openSealedValue, parseStorefrontUrl, randomToken, sealValue, securityHeaders, snapshotHash, snapshotKeys, storefrontRedirect, verifyWebhook };
667
+ export type { AvailabilityPatch, AvailabilityPatchInput, CookieSessionStoreOptions, EdgeSnapshotReader, IdTokenChecks, IdTokenClaims, MenuSnapshot, MenuSnapshotInput, MenuSnapshotPayloadInput, PkcePair, SealOptions, SealSecret, SecurityHeadersOptions, SessionData, SessionStore, SnapshotVersionRecord, StoreSnapshot, StorefrontInstallation, StorefrontPageState, StorefrontQuery, StorefrontRoute, StorefrontUrlInput, StorefrontUrlOptions, TokenAlgorithm, TokenClaims, TokenErrorReason, TokenVerifier, TokenVerifierOptions, VerifyTokenChecks, VerifyWebhookOptions, WebhookEvent, WebhookEventType, ZappieServerConfig };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/redirect.ts"],
4
- "sourcesContent": ["import { buildStorefrontUrl } from \"./build\";\nimport { usesV2 } from \"./contract\";\nimport { normalizedPath } from \"./installation\";\nimport { parseStorefrontUrl } from \"./parse\";\nimport type { StorefrontUrlInput, StorefrontUrlOptions } from \"./types\";\nimport { inputUrl, isAttributionKey } from \"./url\";\n\nconst comparable = (url: URL) => {\n url.searchParams.sort();\n return `${normalizedPath(url.pathname)}${url.search}${url.hash}`;\n};\n\n/**\n * Find the canonical v2 URL for a recognized storefront route.\n * Compares normalized paths and sorted query parameters to avoid unnecessary redirects.\n *\n * Callers choose the HTTP status when serving the redirect.\n * Zappie templates use temporary redirects (302) for now.\n *\n * @returns A redirect target, or null for v1, an unknown route, a parse/build failure or an equivalent URL.\n */\nexport const storefrontRedirect = (\n input: StorefrontUrlInput,\n options: StorefrontUrlOptions = {},\n): string | null => {\n if (!usesV2(options)) {\n return null;\n }\n try {\n const route = parseStorefrontUrl(input, options);\n if (route.type === \"unknown\") {\n return null;\n }\n const targetUrl = inputUrl(buildStorefrontUrl(route, options));\n const original = inputUrl(input);\n for (const key of [...targetUrl.searchParams.keys()]) {\n if (isAttributionKey(key)) {\n targetUrl.searchParams.delete(key);\n }\n }\n original.searchParams.forEach((value, key) => {\n if (isAttributionKey(key)) {\n targetUrl.searchParams.append(key, value);\n }\n });\n const path = comparable(targetUrl);\n if (comparable(original) === path) {\n return null;\n }\n return options.origin ? targetUrl.href : path;\n } catch {\n return null;\n }\n};\n"],
4
+ "sourcesContent": ["import { buildStorefrontUrl } from \"./build\";\nimport { usesV2 } from \"./contract\";\nimport { normalizedPath } from \"./installation\";\nimport { parseStorefrontUrl } from \"./parse\";\nimport type { StorefrontUrlInput, StorefrontUrlOptions } from \"./types\";\nimport { inputUrl, isAttributionKey } from \"./url\";\n\nconst comparable = (url: URL) => {\n url.searchParams.sort();\n return `${normalizedPath(url.pathname)}${url.search}${url.hash}`;\n};\n\n/**\n * Find the canonical v2 URL for a recognized storefront route.\n * Compares normalized paths and sorted query parameters to avoid unnecessary redirects.\n *\n * Serve the target as a permanent redirect (301). A classic URL has moved for\n * good, so search engines carry its ranking to the site in one step.\n *\n * @returns A redirect target, or null for v1, an unknown route, a parse/build failure or an equivalent URL.\n */\nexport const storefrontRedirect = (\n input: StorefrontUrlInput,\n options: StorefrontUrlOptions = {},\n): string | null => {\n if (!usesV2(options)) {\n return null;\n }\n try {\n const route = parseStorefrontUrl(input, options);\n if (route.type === \"unknown\") {\n return null;\n }\n const targetUrl = inputUrl(buildStorefrontUrl(route, options));\n const original = inputUrl(input);\n for (const key of [...targetUrl.searchParams.keys()]) {\n if (isAttributionKey(key)) {\n targetUrl.searchParams.delete(key);\n }\n }\n original.searchParams.forEach((value, key) => {\n if (isAttributionKey(key)) {\n targetUrl.searchParams.append(key, value);\n }\n });\n const path = comparable(targetUrl);\n if (comparable(original) === path) {\n return null;\n }\n return options.origin ? targetUrl.href : path;\n } catch {\n return null;\n }\n};\n"],
5
5
  "mappings": ";AAAA,SAAS,0BAA0B;AACnC,SAAS,cAAc;AACvB,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;AAEnC,SAAS,UAAU,wBAAwB;AAE3C,IAAM,aAAa,CAAC,QAAa;AAC/B,MAAI,aAAa,KAAK;AACtB,SAAO,GAAG,eAAe,IAAI,QAAQ,CAAC,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI;AAChE;AAWO,IAAM,qBAAqB,CAChC,OACA,UAAgC,CAAC,MACf;AAClB,MAAI,CAAC,OAAO,OAAO,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,QAAQ,mBAAmB,OAAO,OAAO;AAC/C,QAAI,MAAM,SAAS,WAAW;AAC5B,aAAO;AAAA,IACT;AACA,UAAM,YAAY,SAAS,mBAAmB,OAAO,OAAO,CAAC;AAC7D,UAAM,WAAW,SAAS,KAAK;AAC/B,eAAW,OAAO,CAAC,GAAG,UAAU,aAAa,KAAK,CAAC,GAAG;AACpD,UAAI,iBAAiB,GAAG,GAAG;AACzB,kBAAU,aAAa,OAAO,GAAG;AAAA,MACnC;AAAA,IACF;AACA,aAAS,aAAa,QAAQ,CAAC,OAAO,QAAQ;AAC5C,UAAI,iBAAiB,GAAG,GAAG;AACzB,kBAAU,aAAa,OAAO,KAAK,KAAK;AAAA,MAC1C;AAAA,IACF,CAAC;AACD,UAAM,OAAO,WAAW,SAAS;AACjC,QAAI,WAAW,QAAQ,MAAM,MAAM;AACjC,aAAO;AAAA,IACT;AACA,WAAO,QAAQ,SAAS,UAAU,OAAO;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/site-config/types.ts"],
4
- "sourcesContent": ["/**\n * Default site budgets: 200 KB gzipped JavaScript, 50 KB gzipped CSS, 2500 ms LCP and 3000 ms until the first product card is tappable.\n */\nexport const DEFAULT_BUDGETS = {\n jsKbGz: 200,\n cssKbGz: 50,\n lcpMs: 2500,\n tappableMs: 3000,\n} as const;\n\n/**\n * Package names and patterns allowed globally without a site-specific allowlist entry.\n */\nexport const GLOBAL_PACKAGE_ALLOWLIST = [\n \"react\",\n \"react-dom\",\n \"react-router\",\n \"@react-router/*\",\n \"@zappie/*\",\n \"tailwindcss\",\n \"@tailwindcss/*\",\n \"clsx\",\n \"tailwind-merge\",\n \"class-variance-authority\",\n \"lucide-react\",\n \"date-fns\",\n \"embla-carousel-react\",\n \"framer-motion/motion\",\n \"zod\",\n \"@radix-ui/*\",\n \"sonner\",\n \"vaul\",\n] as const;\n\n/**\n * The store, theme and optional build policies you export from zappie.config.ts.\n */\nexport type ZappieSiteConfig = {\n store: string;\n branches?: string[];\n theme: string;\n prerender?: string[];\n menu?: { hiddenCategories?: string[] };\n packages?: { allow?: string[] };\n /** Time-bounded acknowledgements for advisories that have no fixed version. */\n audit?: {\n ignore?: { id: string; reason: string; until: string }[];\n };\n outbound?: { allow?: string[] };\n /** Add sources to the fixed storefront Content Security Policy. */\n security?: {\n csp?: {\n imgSrc?: string[];\n connectSrc?: string[];\n fontSrc?: string[];\n styleSrc?: string[];\n frameSrc?: string[];\n };\n };\n /** Each budget left out uses DEFAULT_BUDGETS. */\n budgets?: {\n jsKbGz?: number;\n cssKbGz?: number;\n lcpMs?: number;\n /** V15's limit for the first product card to become tappable. Values below 3000 use 3000. */\n tappableMs?: number;\n override?: { reason: string };\n };\n /** Records explained exceptions to storefront readiness checks. */\n readiness?: {\n /** Lets this site skip the initial route discovery requirement. */\n routeDiscovery?: {\n /** Explains why this site does not use initial route discovery. */\n optOut: {\n /** States the constraint that prevents initial route discovery. */\n reason: string;\n };\n };\n };\n analytics?: { forward?: { ga4?: string; metaPixel?: string } };\n /** Where `zappie verify` opens the storefront and finds each catalogue. */\n verify?: {\n /**\n * The route verify opens as the menu, such as /menu. It must list items. Defaults to /.\n * `{menu}` stands for the first menu that holds items and `{branch}` for the default branch,\n * such as `/menu?menu={menu}`.\n */\n startPath?: string;\n /**\n * The page for each menu, with `{menu}` where its id goes and, optionally, `{branch}` for the\n * branch id, such as `/menu?menu={menu}`. Verify then judges each menu on its own page.\n */\n cataloguePath?: string;\n };\n};\n\n/**\n * A configuration diagnostic with its field path, stable code, message and severity.\n */\nexport type ConfigIssue = {\n path: string;\n code:\n | \"invalid_type\"\n | \"missing_required\"\n | \"unknown_key\"\n | \"invalid_format\"\n | \"duplicate\"\n | \"redundant_allowlist\"\n | \"budget_override\";\n message: string;\n severity: \"error\" | \"warning\";\n};\n\n/**\n * Configuration validation results. Warnings alone do not make valid false.\n */\nexport type ConfigValidation = { valid: boolean; issues: ConfigIssue[] };\n"],
4
+ "sourcesContent": ["/**\n * Default site budgets: 200 KB gzipped JavaScript, 50 KB gzipped CSS, 2500 ms LCP and 3000 ms until the first product card is tappable.\n */\nexport const DEFAULT_BUDGETS = {\n jsKbGz: 200,\n cssKbGz: 50,\n lcpMs: 2500,\n tappableMs: 3000,\n} as const;\n\n/**\n * Package names and patterns allowed globally without a site-specific allowlist entry.\n */\nexport const GLOBAL_PACKAGE_ALLOWLIST = [\n \"react\",\n \"react-dom\",\n \"react-router\",\n \"@react-router/*\",\n \"@zappie/*\",\n \"tailwindcss\",\n \"@tailwindcss/*\",\n \"clsx\",\n \"tailwind-merge\",\n \"class-variance-authority\",\n \"lucide-react\",\n \"date-fns\",\n \"embla-carousel-react\",\n \"framer-motion/motion\",\n \"zod\",\n \"@radix-ui/*\",\n \"sonner\",\n \"vaul\",\n] as const;\n\n/**\n * The store, theme and optional build policies you export from zappie.config.ts.\n */\nexport type ZappieSiteConfig = {\n store: string;\n branches?: string[];\n theme: string;\n prerender?: string[];\n menu?: { hiddenCategories?: string[] };\n packages?: { allow?: string[] };\n /** Time-bounded acknowledgements for advisories that have no fixed version. */\n audit?: {\n ignore?: { id: string; reason: string; until: string }[];\n };\n outbound?: { allow?: string[] };\n /** Add sources to the fixed storefront Content Security Policy. */\n security?: {\n csp?: {\n imgSrc?: string[];\n connectSrc?: string[];\n fontSrc?: string[];\n styleSrc?: string[];\n frameSrc?: string[];\n };\n /** Hosts outside Zappie that built pages may link to, such as the store's own domains. */\n links?: string[];\n };\n /** Each budget left out uses DEFAULT_BUDGETS. */\n budgets?: {\n jsKbGz?: number;\n cssKbGz?: number;\n lcpMs?: number;\n /** V15's limit for the first product card to become tappable. Values below 3000 use 3000. */\n tappableMs?: number;\n override?: { reason: string };\n };\n /** Records explained exceptions to storefront readiness checks. */\n readiness?: {\n /** Lets this site skip the initial route discovery requirement. */\n routeDiscovery?: {\n /** Explains why this site does not use initial route discovery. */\n optOut: {\n /** States the constraint that prevents initial route discovery. */\n reason: string;\n };\n };\n };\n analytics?: { forward?: { ga4?: string; metaPixel?: string } };\n /** Where `zappie verify` opens the storefront and finds each catalogue. */\n verify?: {\n /**\n * The route verify opens as the menu, such as /menu. It must list items. Defaults to /.\n * `{menu}` stands for the first menu that holds items and `{branch}` for the default branch,\n * such as `/menu?menu={menu}`.\n */\n startPath?: string;\n /**\n * The page for each menu, with `{menu}` where its id goes and, optionally, `{branch}` for the\n * branch id, such as `/menu?menu={menu}`. Verify then judges each menu on its own page.\n */\n cataloguePath?: string;\n };\n};\n\n/**\n * A configuration diagnostic with its field path, stable code, message and severity.\n */\nexport type ConfigIssue = {\n path: string;\n code:\n | \"invalid_type\"\n | \"missing_required\"\n | \"unknown_key\"\n | \"invalid_format\"\n | \"duplicate\"\n | \"redundant_allowlist\"\n | \"budget_override\";\n message: string;\n severity: \"error\" | \"warning\";\n};\n\n/**\n * Configuration validation results. Warnings alone do not make valid false.\n */\nexport type ConfigValidation = { valid: boolean; issues: ConfigIssue[] };\n"],
5
5
  "mappings": ";AAGO,IAAM,kBAAkB;AAAA,EAC7B,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY;AACd;AAKO,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": []
7
7
  }
@@ -90,7 +90,8 @@ function validateConfig(input) {
90
90
  nested(input.outbound, "outbound", ["allow"], issue, (value) => {
91
91
  hostList(value.allow, "outbound.allow", error);
92
92
  });
93
- nested(input.security, "security", ["csp"], issue, (security) => {
93
+ nested(input.security, "security", ["csp", "links"], issue, (security) => {
94
+ hostList(security.links, "security.links", error);
94
95
  nested(
95
96
  security.csp,
96
97
  "security.csp",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/site-config/validate.ts"],
4
- "sourcesContent": ["import {\n globallyAllowed,\n hostname,\n nonblank,\n packageName,\n record,\n unknownKeys,\n} from \"./helpers\";\nimport { analytics, budgets, nested } from \"./sections\";\nimport type { ConfigIssue, ConfigValidation } from \"./types\";\n\nconst rootKeys = [\n \"store\",\n \"branches\",\n \"theme\",\n \"prerender\",\n \"menu\",\n \"packages\",\n \"audit\",\n \"outbound\",\n \"security\",\n \"budgets\",\n \"readiness\",\n \"analytics\",\n \"verify\",\n] as const;\n\n/**\n * Validate a site configuration and collect errors and warnings without changing it.\n * Checks required fields, unknown keys, allowlists, budgets and analytics settings.\n *\n * @param input - The configuration value to inspect.\n * @returns Diagnostics and a valid flag that is true when there are no errors.\n */\nexport function validateConfig(input: unknown): ConfigValidation {\n const issues: ConfigIssue[] = [];\n const issue = (value: ConfigIssue) => issues.push(value);\n const error = (path: string, code: ConfigIssue[\"code\"], message: string) =>\n issue({ path, code, message, severity: \"error\" });\n if (!record(input)) {\n return {\n valid: false,\n issues: [\n {\n path: \"$\",\n code: \"invalid_type\",\n message:\n \"Expected a configuration object. Export an object from zappie.config.ts.\",\n severity: \"error\",\n },\n ],\n };\n }\n unknownKeys(input, rootKeys, \"\", issue);\n if (!nonblank(input.store)) {\n error(\n \"store\",\n input.store === undefined ? \"missing_required\" : \"invalid_type\",\n \"Expected a non-empty storefront slug. Set store to the slug used by createZappie().\",\n );\n }\n theme(input.theme, error);\n stringList(input.branches, \"branches\", error, {\n nonempty: true,\n unique: true,\n slug: true,\n });\n stringList(input.prerender, \"prerender\", error, { unique: true })?.forEach(\n (path, index) => {\n if (!path.startsWith(\"/\")) {\n error(\n `prerender[${index}]`,\n \"invalid_format\",\n \"Expected a route starting with /. Prefix the route with /.\",\n );\n }\n },\n );\n nested(input.menu, \"menu\", [\"hiddenCategories\"], issue, (value) => {\n stringList(value.hiddenCategories, \"menu.hiddenCategories\", error);\n });\n nested(input.packages, \"packages\", [\"allow\"], issue, (value) => {\n const allow = stringList(value.allow, \"packages.allow\", error);\n allow?.forEach((name, index) => {\n if (globallyAllowed(name)) {\n issue({\n path: `packages.allow[${index}]`,\n code: \"redundant_allowlist\",\n message: `${name} is already globally allowed. Remove it from packages.allow.`,\n severity: \"warning\",\n });\n } else if (!packageName(name)) {\n error(\n `packages.allow[${index}]`,\n \"invalid_format\",\n \"Expected a lowercase npm package name. Use a package name such as lucide-react or @scope/name.\",\n );\n }\n });\n });\n nested(input.outbound, \"outbound\", [\"allow\"], issue, (value) => {\n hostList(value.allow, \"outbound.allow\", error);\n });\n nested(input.security, \"security\", [\"csp\"], issue, (security) => {\n nested(\n security.csp,\n \"security.csp\",\n [\"imgSrc\", \"connectSrc\", \"fontSrc\", \"styleSrc\", \"frameSrc\"],\n issue,\n (csp) => {\n for (const [name, value] of Object.entries(csp)) {\n hostList(value, `security.csp.${name}`, error);\n }\n },\n );\n });\n budgets(input.budgets, issue, error);\n nested(\n input.readiness,\n \"readiness\",\n [\"routeDiscovery\"],\n issue,\n (readiness) => {\n nested(\n readiness.routeDiscovery,\n \"readiness.routeDiscovery\",\n [\"optOut\"],\n issue,\n (routeDiscovery) => {\n nested(\n routeDiscovery.optOut,\n \"readiness.routeDiscovery.optOut\",\n [\"reason\"],\n issue,\n (optOut) => {\n if (typeof optOut.reason !== \"string\" || !optOut.reason.trim()) {\n error(\n \"readiness.routeDiscovery.optOut.reason\",\n \"invalid_format\",\n \"Expected a non-blank reason. Explain why the site disables initial route discovery.\",\n );\n }\n },\n );\n },\n );\n },\n );\n analytics(input.analytics, issue, error);\n // Verify checks the routes themselves when it runs.\n nested(\n input.verify,\n \"verify\",\n [\"startPath\", \"cataloguePath\"],\n issue,\n () => {},\n );\n return {\n valid: !issues.some((entry) => entry.severity === \"error\"),\n issues,\n };\n}\n\ntype AddError = (\n path: string,\n code: ConfigIssue[\"code\"],\n message: string,\n) => void;\n\nfunction theme(value: unknown, error: AddError) {\n if (typeof value !== \"string\") {\n error(\n \"theme\",\n value === undefined ? \"missing_required\" : \"invalid_type\",\n \"Expected a relative .json path. Set theme to a value such as ./theme.json.\",\n );\n } else if (\n !/\\.json$/.test(value) ||\n /^(?:[a-z][a-z\\d+.-]*:|\\/|\\\\)/i.test(value) ||\n value.includes(\"\\\\\")\n ) {\n error(\n \"theme\",\n \"invalid_format\",\n \"Expected a relative .json path. Use a value such as ./theme.json.\",\n );\n }\n}\n\nfunction stringList(\n value: unknown,\n path: string,\n error: AddError,\n options: { nonempty?: boolean; unique?: boolean; slug?: boolean } = {},\n) {\n if (value === undefined) {\n return undefined;\n }\n if (\n !Array.isArray(value) ||\n value.some((entry) => typeof entry !== \"string\")\n ) {\n error(\n path,\n \"invalid_type\",\n `Expected an array of strings. Set ${path} to string values.`,\n );\n return undefined;\n }\n if (options.nonempty && value.length === 0) {\n error(\n path,\n \"invalid_format\",\n `Expected at least one value. Add a value to ${path} or remove the field.`,\n );\n }\n value.forEach((entry, index) => {\n if (options.slug && !nonblank(entry)) {\n error(\n `${path}[${index}]`,\n \"invalid_format\",\n \"Expected a non-empty branch slug. Remove the empty value or use a branch slug.\",\n );\n }\n });\n if (options.unique && new Set(value).size !== value.length) {\n error(\n path,\n \"duplicate\",\n `Expected unique values. Remove duplicates from ${path}.`,\n );\n }\n return value;\n}\n\nfunction hostList(value: unknown, path: string, error: AddError) {\n stringList(value, path, error)?.forEach((host, index) => {\n if (!hostname(host)) {\n error(\n `${path}[${index}]`,\n \"invalid_format\",\n \"Expected a hostname or leading *. subdomain pattern without a scheme, path, port or IP. Use a value such as api.example.com.\",\n );\n }\n });\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n globallyAllowed,\n hostname,\n nonblank,\n packageName,\n record,\n unknownKeys,\n} from \"./helpers\";\nimport { analytics, budgets, nested } from \"./sections\";\nimport type { ConfigIssue, ConfigValidation } from \"./types\";\n\nconst rootKeys = [\n \"store\",\n \"branches\",\n \"theme\",\n \"prerender\",\n \"menu\",\n \"packages\",\n \"audit\",\n \"outbound\",\n \"security\",\n \"budgets\",\n \"readiness\",\n \"analytics\",\n \"verify\",\n] as const;\n\n/**\n * Validate a site configuration and collect errors and warnings without changing it.\n * Checks required fields, unknown keys, allowlists, budgets and analytics settings.\n *\n * @param input - The configuration value to inspect.\n * @returns Diagnostics and a valid flag that is true when there are no errors.\n */\nexport function validateConfig(input: unknown): ConfigValidation {\n const issues: ConfigIssue[] = [];\n const issue = (value: ConfigIssue) => issues.push(value);\n const error = (path: string, code: ConfigIssue[\"code\"], message: string) =>\n issue({ path, code, message, severity: \"error\" });\n if (!record(input)) {\n return {\n valid: false,\n issues: [\n {\n path: \"$\",\n code: \"invalid_type\",\n message:\n \"Expected a configuration object. Export an object from zappie.config.ts.\",\n severity: \"error\",\n },\n ],\n };\n }\n unknownKeys(input, rootKeys, \"\", issue);\n if (!nonblank(input.store)) {\n error(\n \"store\",\n input.store === undefined ? \"missing_required\" : \"invalid_type\",\n \"Expected a non-empty storefront slug. Set store to the slug used by createZappie().\",\n );\n }\n theme(input.theme, error);\n stringList(input.branches, \"branches\", error, {\n nonempty: true,\n unique: true,\n slug: true,\n });\n stringList(input.prerender, \"prerender\", error, { unique: true })?.forEach(\n (path, index) => {\n if (!path.startsWith(\"/\")) {\n error(\n `prerender[${index}]`,\n \"invalid_format\",\n \"Expected a route starting with /. Prefix the route with /.\",\n );\n }\n },\n );\n nested(input.menu, \"menu\", [\"hiddenCategories\"], issue, (value) => {\n stringList(value.hiddenCategories, \"menu.hiddenCategories\", error);\n });\n nested(input.packages, \"packages\", [\"allow\"], issue, (value) => {\n const allow = stringList(value.allow, \"packages.allow\", error);\n allow?.forEach((name, index) => {\n if (globallyAllowed(name)) {\n issue({\n path: `packages.allow[${index}]`,\n code: \"redundant_allowlist\",\n message: `${name} is already globally allowed. Remove it from packages.allow.`,\n severity: \"warning\",\n });\n } else if (!packageName(name)) {\n error(\n `packages.allow[${index}]`,\n \"invalid_format\",\n \"Expected a lowercase npm package name. Use a package name such as lucide-react or @scope/name.\",\n );\n }\n });\n });\n nested(input.outbound, \"outbound\", [\"allow\"], issue, (value) => {\n hostList(value.allow, \"outbound.allow\", error);\n });\n nested(input.security, \"security\", [\"csp\", \"links\"], issue, (security) => {\n hostList(security.links, \"security.links\", error);\n nested(\n security.csp,\n \"security.csp\",\n [\"imgSrc\", \"connectSrc\", \"fontSrc\", \"styleSrc\", \"frameSrc\"],\n issue,\n (csp) => {\n for (const [name, value] of Object.entries(csp)) {\n hostList(value, `security.csp.${name}`, error);\n }\n },\n );\n });\n budgets(input.budgets, issue, error);\n nested(\n input.readiness,\n \"readiness\",\n [\"routeDiscovery\"],\n issue,\n (readiness) => {\n nested(\n readiness.routeDiscovery,\n \"readiness.routeDiscovery\",\n [\"optOut\"],\n issue,\n (routeDiscovery) => {\n nested(\n routeDiscovery.optOut,\n \"readiness.routeDiscovery.optOut\",\n [\"reason\"],\n issue,\n (optOut) => {\n if (typeof optOut.reason !== \"string\" || !optOut.reason.trim()) {\n error(\n \"readiness.routeDiscovery.optOut.reason\",\n \"invalid_format\",\n \"Expected a non-blank reason. Explain why the site disables initial route discovery.\",\n );\n }\n },\n );\n },\n );\n },\n );\n analytics(input.analytics, issue, error);\n // Verify checks the routes themselves when it runs.\n nested(\n input.verify,\n \"verify\",\n [\"startPath\", \"cataloguePath\"],\n issue,\n () => {},\n );\n return {\n valid: !issues.some((entry) => entry.severity === \"error\"),\n issues,\n };\n}\n\ntype AddError = (\n path: string,\n code: ConfigIssue[\"code\"],\n message: string,\n) => void;\n\nfunction theme(value: unknown, error: AddError) {\n if (typeof value !== \"string\") {\n error(\n \"theme\",\n value === undefined ? \"missing_required\" : \"invalid_type\",\n \"Expected a relative .json path. Set theme to a value such as ./theme.json.\",\n );\n } else if (\n !/\\.json$/.test(value) ||\n /^(?:[a-z][a-z\\d+.-]*:|\\/|\\\\)/i.test(value) ||\n value.includes(\"\\\\\")\n ) {\n error(\n \"theme\",\n \"invalid_format\",\n \"Expected a relative .json path. Use a value such as ./theme.json.\",\n );\n }\n}\n\nfunction stringList(\n value: unknown,\n path: string,\n error: AddError,\n options: { nonempty?: boolean; unique?: boolean; slug?: boolean } = {},\n) {\n if (value === undefined) {\n return undefined;\n }\n if (\n !Array.isArray(value) ||\n value.some((entry) => typeof entry !== \"string\")\n ) {\n error(\n path,\n \"invalid_type\",\n `Expected an array of strings. Set ${path} to string values.`,\n );\n return undefined;\n }\n if (options.nonempty && value.length === 0) {\n error(\n path,\n \"invalid_format\",\n `Expected at least one value. Add a value to ${path} or remove the field.`,\n );\n }\n value.forEach((entry, index) => {\n if (options.slug && !nonblank(entry)) {\n error(\n `${path}[${index}]`,\n \"invalid_format\",\n \"Expected a non-empty branch slug. Remove the empty value or use a branch slug.\",\n );\n }\n });\n if (options.unique && new Set(value).size !== value.length) {\n error(\n path,\n \"duplicate\",\n `Expected unique values. Remove duplicates from ${path}.`,\n );\n }\n return value;\n}\n\nfunction hostList(value: unknown, path: string, error: AddError) {\n stringList(value, path, error)?.forEach((host, index) => {\n if (!hostname(host)) {\n error(\n `${path}[${index}]`,\n \"invalid_format\",\n \"Expected a hostname or leading *. subdomain pattern without a scheme, path, port or IP. Use a value such as api.example.com.\",\n );\n }\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -36,4 +36,5 @@ declare function regionName(element: Element): string;
36
36
  */
37
37
  declare function startStudio(parent: string, post: (message: object) => void): (() => void) | null;
38
38
 
39
- export { KIT_REGION_NAMES, STUDIO_ORIGINS, type StudioRegion, regionName, startStudio };
39
+ export { KIT_REGION_NAMES, STUDIO_ORIGINS, regionName, startStudio };
40
+ export type { StudioRegion };
@@ -42,4 +42,5 @@ declare function resized(src: string, width: number, quality?: number, resizeHos
42
42
  */
43
43
  declare function srcSet(src: string, quality?: number, resizeHosts?: readonly string[]): string;
44
44
 
45
- export { EDITOR_ORIGINS, IMAGE_WIDTHS, RESIZE_HOSTS, type TemplateMode, type TemplateSettingValue, canResize, editorOrigin, resized, settingsFromMessage, srcSet, templateSettings };
45
+ export { EDITOR_ORIGINS, IMAGE_WIDTHS, RESIZE_HOSTS, canResize, editorOrigin, resized, settingsFromMessage, srcSet, templateSettings };
46
+ export type { TemplateMode, TemplateSettingValue };
@@ -1,7 +1,8 @@
1
- import { T as Transport, e as ZappieErrorCode, S as StorageLike, c as StoreResource, M as MenuResource, d as ZappieEvents, C as Cache } from '../client-yJQj780A.js';
2
- import { S as Store, a as Menu, F as Formatters } from '../models-DuLY7jMB.js';
1
+ import { C as Cache } from '../client-B377AzNC.js';
2
+ import { Transport, ZappieErrorCode, StorageLike, StoreResource, MenuResource, ZappieEvents } from '../index.js';
3
+ import { Store, Menu, Formatters } from '../index.js';
3
4
  export { VERSION } from '../index.js';
4
- import { d as CartModule, b as CartLineInput } from '../types-COI1jnQ2.js';
5
+ import { CartModule, CartLineInput } from '../index.js';
5
6
 
6
7
  /** Store and branch menus supplied to the deterministic mock transport. */
7
8
  type MockFixtures = {
@@ -63,4 +64,5 @@ declare const fixtures: {
63
64
  };
64
65
  };
65
66
 
66
- export { type MockFixtures, type MockOptions, createMockZappie, fixtures };
67
+ export { createMockZappie, fixtures };
68
+ export type { MockFixtures, MockOptions };
@@ -1,4 +1,4 @@
1
- import { a as Menu, C as CategoryPage } from '../models-DuLY7jMB.js';
1
+ import { Menu, CategoryPage } from '../index.js';
2
2
 
3
3
  /**
4
4
  * Validates and maps one category page. Only progressive catalogues read
@@ -1,4 +1,4 @@
1
- import { a as Menu, S as Store } from '../models-DuLY7jMB.js';
1
+ import { Menu, Store } from '../index.js';
2
2
 
3
3
  type Invalid = () => Error;
4
4
  declare function presentStore(store: Store, value: unknown, invalid: Invalid): Store;
@@ -1,6 +1,6 @@
1
- import { a as Menu } from '../models-DuLY7jMB.js';
2
- import { j as SearchRequest, k as SearchPage } from '../client-yJQj780A.js';
3
- import '../types-COI1jnQ2.js';
1
+ import { Menu } from '../index.js';
2
+ import { SearchRequest, SearchPage } from '../index.js';
3
+ import '../types-B3Ghmpmy.js';
4
4
 
5
5
  /** Loaded only when search is used; request authentication/retries stay in REST. */
6
6
  declare function searchPage(ctx: SearchRequest, read: (path: string) => Promise<unknown>, mapMenu: (value: unknown, branchId: string) => Menu, invalid: () => Error): Promise<SearchPage>;
@@ -1,3 +1,3 @@
1
- export { z as SelectedMenu, h as SelectedMenuRequest, i as SelectedMenuResponse, B as SelectedMenuTransport, D as createSelectedMenuReader, F as selectedMenu, G as selectedMenuKey } from '../client-yJQj780A.js';
2
- import '../models-DuLY7jMB.js';
3
- import '../types-COI1jnQ2.js';
1
+ export { z as SelectedMenu, h as SelectedMenuRequest, i as SelectedMenuResponse, B as SelectedMenuTransport, D as createSelectedMenuReader, F as selectedMenu, G as selectedMenuKey } from '../client-B377AzNC.js';
2
+ import '../models-tf9rNoki.js';
3
+ import '../types-B3Ghmpmy.js';
@@ -1,4 +1,4 @@
1
- import { i as OpeningTime, M as Money, d as Currency, S as Store } from '../models-DuLY7jMB.js';
1
+ import { OpeningTime, Money, Currency, Store } from '../index.js';
2
2
 
3
3
  type Guard<T> = (value: unknown) => value is T;
4
4
  type Optional = <K extends string, T>(key: K, value: unknown, is: Guard<T>) => {
@@ -1,4 +1,4 @@
1
- import { d as Currency, M as Money, I as Item } from './models-DuLY7jMB.js';
1
+ import { d as Currency, M as Money, I as Item } from './models-tf9rNoki.js';
2
2
 
3
3
  /**
4
4
  * An item selection you submit to the cart, with optional quantity, choices, inputs and source menu.
@@ -150,11 +150,12 @@ declare const parseStorefrontUrl: (input: StorefrontUrlInput, options?: Storefro
150
150
  * Find the canonical v2 URL for a recognized storefront route.
151
151
  * Compares normalized paths and sorted query parameters to avoid unnecessary redirects.
152
152
  *
153
- * Callers choose the HTTP status when serving the redirect.
154
- * Zappie templates use temporary redirects (302) for now.
153
+ * Serve the target as a permanent redirect (301). A classic URL has moved for
154
+ * good, so search engines carry its ranking to the site in one step.
155
155
  *
156
156
  * @returns A redirect target, or null for v1, an unknown route, a parse/build failure or an equivalent URL.
157
157
  */
158
158
  declare const storefrontRedirect: (input: StorefrontUrlInput, options?: StorefrontUrlOptions) => string | null;
159
159
 
160
- export { STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, type StorefrontInstallation, type StorefrontPageState, type StorefrontQuery, type StorefrontRoute, type StorefrontUrlInput, type StorefrontUrlOptions, buildStorefrontUrl, parseStorefrontUrl, storefrontRedirect };
160
+ export { STOREFRONT_QUERY_PARAMETERS, STOREFRONT_V1_QUERY_PARAMETERS, buildStorefrontUrl, parseStorefrontUrl, storefrontRedirect };
161
+ export type { StorefrontInstallation, StorefrontPageState, StorefrontQuery, StorefrontRoute, StorefrontUrlInput, StorefrontUrlOptions };
@@ -86,4 +86,5 @@ declare function currentSource(): {
86
86
  campaign: string;
87
87
  };
88
88
 
89
- export { type CountPageInput, type PageView, type PageViewInput, type VisitEvent, type VisitEventName, type VisitStorage, countEvent, countPage, currentSource, pageView, visitEvent, visitSource };
89
+ export { countEvent, countPage, currentSource, pageView, visitEvent, visitSource };
90
+ export type { CountPageInput, PageView, PageViewInput, VisitEvent, VisitEventName, VisitStorage };
@@ -38,4 +38,5 @@ declare function zappieSource({ previewHost }: ZappieSourceOptions): Plugin;
38
38
  /** Add source and component attributes to the intrinsic elements of one saved `.tsx` or `.jsx` file. */
39
39
  declare function markSource(code: string, file: string): MarkedSource | null;
40
40
 
41
- export { COMPONENT_ATTRIBUTE, type MarkedSource, SOURCE_ATTRIBUTE, type ZappieSourceOptions, markSource, zappieSource };
41
+ export { COMPONENT_ATTRIBUTE, SOURCE_ATTRIBUTE, markSource, zappieSource };
42
+ export type { MarkedSource, ZappieSourceOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zappie/sdk",
3
- "version": "0.1.23",
3
+ "version": "0.2.0",
4
4
  "description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
5
5
  "type": "module",
6
6
  "sideEffects": false,