@zappie/sdk 0.2.0 → 0.3.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 (129) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cart/branch.js.map +0 -1
  3. package/dist/cart/errors.js.map +0 -1
  4. package/dist/cart/index.js.map +0 -1
  5. package/dist/cart/item.d.ts +1 -1
  6. package/dist/cart/item.js.map +0 -1
  7. package/dist/cart/preview.js.map +0 -1
  8. package/dist/cart/price.js.map +0 -1
  9. package/dist/cart/quote-client.js.map +0 -1
  10. package/dist/cart/quote-response.js.map +0 -1
  11. package/dist/cart/quote.d.ts +1 -1
  12. package/dist/cart/reconcile.js.map +0 -1
  13. package/dist/cart/source-menu.js.map +0 -1
  14. package/dist/cart/storage.js.map +0 -1
  15. package/dist/cart/types.js.map +0 -1
  16. package/dist/cart/validate.js.map +0 -1
  17. package/dist/cart/wire.js.map +0 -1
  18. package/dist/cart/working-set.js.map +0 -1
  19. package/dist/checkout/client.js.map +0 -1
  20. package/dist/checkout/embed.js.map +0 -1
  21. package/dist/checkout/error.js.map +0 -1
  22. package/dist/checkout/index.js.map +0 -1
  23. package/dist/checkout/react.js.map +0 -1
  24. package/dist/checkout/readiness.js.map +0 -1
  25. package/dist/checkout/sdk.js.map +0 -1
  26. package/dist/checkout/session.js.map +0 -1
  27. package/dist/checkout/types.js.map +0 -1
  28. package/dist/checkout-defaults/index.js.map +0 -1
  29. package/dist/{client-B377AzNC.d.ts → client-DHxakZrv.d.ts} +2 -2
  30. package/dist/client.js.map +0 -1
  31. package/dist/config/index.js.map +0 -1
  32. package/dist/config.js.map +0 -1
  33. package/dist/customer/client.js.map +0 -1
  34. package/dist/customer/error.js.map +0 -1
  35. package/dist/customer/index.js.map +0 -1
  36. package/dist/customer/navigation.js.map +0 -1
  37. package/dist/customer/react.js.map +0 -1
  38. package/dist/customer/request.js.map +0 -1
  39. package/dist/customer/sdk.js.map +0 -1
  40. package/dist/customer/types.js.map +0 -1
  41. package/dist/customer/watch-order.js.map +0 -1
  42. package/dist/debug.js +1 -1
  43. package/dist/debug.js.map +0 -1
  44. package/dist/errors.js.map +0 -1
  45. package/dist/index.d.ts +6 -6
  46. package/dist/index.js +1 -1
  47. package/dist/index.js.map +0 -1
  48. package/dist/integrations/index.d.ts +2 -2
  49. package/dist/integrations/index.js.map +0 -1
  50. package/dist/internal/abort.js.map +0 -1
  51. package/dist/internal/cache.js.map +0 -1
  52. package/dist/internal/customer-http.js.map +0 -1
  53. package/dist/internal/digest.js.map +0 -1
  54. package/dist/internal/emitter.js.map +0 -1
  55. package/dist/internal/money-math.js.map +0 -1
  56. package/dist/internal/return-path.js.map +0 -1
  57. package/dist/internal.d.ts +2 -2
  58. package/dist/internal.js.map +0 -1
  59. package/dist/{models-tf9rNoki.d.ts → models-C-kEg1Id.d.ts} +1 -1
  60. package/dist/models.js.map +0 -1
  61. package/dist/money.js.map +0 -1
  62. package/dist/persist/catalogue.d.ts +3 -3
  63. package/dist/persist/catalogue.js.map +0 -1
  64. package/dist/places/client.js.map +0 -1
  65. package/dist/places/error.js.map +0 -1
  66. package/dist/places/index.js.map +0 -1
  67. package/dist/places/request.js.map +0 -1
  68. package/dist/places/sdk.js.map +0 -1
  69. package/dist/places/search.js.map +0 -1
  70. package/dist/places/types.js.map +0 -1
  71. package/dist/pricing/index.js.map +0 -1
  72. package/dist/react/cart.js.map +0 -1
  73. package/dist/react/hooks.js.map +0 -1
  74. package/dist/react/index.js.map +0 -1
  75. package/dist/react/provider.js.map +0 -1
  76. package/dist/redirect/index.js.map +0 -1
  77. package/dist/server/auth/crypto.js.map +0 -1
  78. package/dist/server/auth/jwt.js.map +0 -1
  79. package/dist/server/auth/session-store.js.map +0 -1
  80. package/dist/server/edge-snapshot.js.map +0 -1
  81. package/dist/server/index.d.ts +1 -1
  82. package/dist/server/index.js.map +0 -1
  83. package/dist/server/security.js.map +0 -1
  84. package/dist/server/storefront-url/build-v1.js.map +0 -1
  85. package/dist/server/storefront-url/build-v2.js.map +0 -1
  86. package/dist/server/storefront-url/build.js.map +0 -1
  87. package/dist/server/storefront-url/canonical.js.map +0 -1
  88. package/dist/server/storefront-url/contract.js.map +0 -1
  89. package/dist/server/storefront-url/index.js.map +0 -1
  90. package/dist/server/storefront-url/installation.js.map +0 -1
  91. package/dist/server/storefront-url/params.js.map +0 -1
  92. package/dist/server/storefront-url/parse-v1.js.map +0 -1
  93. package/dist/server/storefront-url/parse-v2.js.map +0 -1
  94. package/dist/server/storefront-url/parse.js.map +0 -1
  95. package/dist/server/storefront-url/public-id.js.map +0 -1
  96. package/dist/server/storefront-url/redirect.js.map +0 -1
  97. package/dist/server/storefront-url/sign-in.js.map +0 -1
  98. package/dist/server/storefront-url/types.js.map +0 -1
  99. package/dist/server/storefront-url/url.js.map +0 -1
  100. package/dist/server/webhook.js.map +0 -1
  101. package/dist/services/index.d.ts +21 -0
  102. package/dist/services/index.js +24 -0
  103. package/dist/services/index.js.map +6 -0
  104. package/dist/site-config/helpers.js.map +0 -1
  105. package/dist/site-config/sections.js.map +0 -1
  106. package/dist/site-config/types.js.map +0 -1
  107. package/dist/site-config/validate.js.map +0 -1
  108. package/dist/template/index.d.ts +2 -2
  109. package/dist/template/index.js +1 -1
  110. package/dist/template/index.js.map +1 -2
  111. package/dist/template/react.js.map +0 -1
  112. package/dist/testing/index.d.ts +1 -1
  113. package/dist/testing/index.js +2 -0
  114. package/dist/transport/category-page.js.map +0 -1
  115. package/dist/transport/mappers.js.map +0 -1
  116. package/dist/transport/presentation.js.map +0 -1
  117. package/dist/transport/quote-body.js.map +0 -1
  118. package/dist/transport/rest.js.map +0 -1
  119. package/dist/transport/search.d.ts +1 -1
  120. package/dist/transport/search.js.map +0 -1
  121. package/dist/transport/selected-menu.d.ts +3 -3
  122. package/dist/transport/selected-menu.js.map +0 -1
  123. package/dist/transport/store.js.map +0 -1
  124. package/dist/transport/types.js.map +0 -1
  125. package/dist/{types-B3Ghmpmy.d.ts → types-CxY5C6AN.d.ts} +1 -1
  126. package/dist/url/index.js.map +0 -1
  127. package/dist/visits/index.js.map +0 -1
  128. package/dist/visits/page-views.js.map +0 -1
  129. package/package.json +6 -2
@@ -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-B377AzNC.js';
2
- import '../models-tf9rNoki.js';
3
- import '../types-B3Ghmpmy.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-DHxakZrv.js';
2
+ import '../models-C-kEg1Id.js';
3
+ import '../types-CxY5C6AN.js';
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/persist/catalogue.ts"],
4
- "sourcesContent": ["import type { CategoryPage, Menu } from \"../models\";\n\n/**\n * The store, branch, order context and source menu that identify a saved catalogue.\n */\nexport type CatalogueScope = {\n store: string;\n branchId: string;\n orderContext: \"delivery\" | \"pickup\" | \"dineIn\";\n menuId: string;\n};\n/** The saved coverage. A complete source menu still covers only part of the branch. */\nexport type SavedCatalogue = {\n scope: CatalogueScope;\n menu: Menu;\n coverage: { menuIds: string[]; complete: boolean };\n pages: { categoryId: string; cursor?: string; page: CategoryPage }[];\n etag: string;\n savedAt: number;\n /**\n * The host's catalogue revision when saved. An unchanged revision means the\n * saved copy is current and needs no background refresh.\n */\n revision?: string;\n};\ntype RecordValue = SavedCatalogue & {\n key: string;\n bytes: number;\n usedAt: number;\n};\n/**\n * Default saved-catalogue lifetime in milliseconds: 24 hours.\n */\nexport const CATALOGUE_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n/**\n * Default per-store catalogue storage budget in serialized bytes: 16 MiB.\n */\nexport const CATALOGUE_MAX_BYTES = 16 * 1024 * 1024;\n/**\n * Build a versioned JSON storage key from the store, branch, order context and menu ID.\n */\nexport function catalogueKey(scope: CatalogueScope) {\n return JSON.stringify([\n \"catalogue:v1\",\n scope.store,\n scope.branchId,\n scope.orderContext,\n scope.menuId,\n ]);\n}\n/** Opt-in browser persistence. Storage failures never escape into shopper UI. */\nexport function createCatalogueStorage(\n store: string,\n options: { maxAgeMs?: number; maxBytes?: number } = {},\n) {\n const maxAge = options.maxAgeMs ?? CATALOGUE_MAX_AGE_MS;\n const maxBytes = options.maxBytes ?? CATALOGUE_MAX_BYTES;\n function open(): Promise<IDBDatabase | undefined> {\n return new Promise((resolve) => {\n let settled = false;\n const timer = setTimeout(() => finish(), 1000);\n function finish(db?: IDBDatabase) {\n if (settled) {\n db?.close();\n return;\n }\n settled = true;\n clearTimeout(timer);\n resolve(db);\n }\n try {\n const request = indexedDB.open(`zappie:catalogue:v1:${store}`, 1);\n request.onupgradeneeded = () => {\n request.result.createObjectStore(\"catalogues\", { keyPath: \"key\" });\n };\n request.onerror = () => finish();\n request.onblocked = () => finish();\n request.onsuccess = () => finish(request.result);\n } catch {\n finish();\n }\n });\n }\n function idle(work: () => void) {\n if (typeof requestIdleCallback === \"function\") {\n requestIdleCallback(work, { timeout: 2000 });\n } else {\n setTimeout(work, 0);\n }\n }\n function mutate(work: (table: IDBObjectStore) => void) {\n idle(() => {\n void open().then((db) => {\n if (!db) {\n return;\n }\n try {\n const tx = db.transaction(\"catalogues\", \"readwrite\");\n tx.oncomplete = tx.onabort = tx.onerror = () => db.close();\n work(tx.objectStore(\"catalogues\"));\n } catch {\n db.close();\n }\n });\n });\n }\n return {\n async read(scope: CatalogueScope): Promise<SavedCatalogue | undefined> {\n if (scope.store !== store) {\n return;\n }\n const db = await open();\n if (!db) {\n return;\n }\n return new Promise((resolve) => {\n const key = catalogueKey(scope);\n try {\n const tx = db.transaction(\"catalogues\", \"readonly\");\n tx.oncomplete = () => db.close();\n tx.onabort = tx.onerror = () => {\n db.close();\n resolve(undefined);\n };\n const request = tx.objectStore(\"catalogues\").get(key);\n request.onsuccess = () => {\n const value = request.result as RecordValue | undefined;\n const now = Date.now();\n if (\n !value ||\n !Number.isFinite(value.savedAt) ||\n now < value.savedAt ||\n now - value.savedAt >= maxAge ||\n value.menu?.branchId !== scope.branchId ||\n value.scope?.store !== store ||\n catalogueKey(value.scope) !== key ||\n !Array.isArray(value.coverage?.menuIds) ||\n value.coverage.menuIds.length !== 1 ||\n value.coverage.menuIds[0] !== scope.menuId ||\n !value.etag\n ) {\n if (value) {\n mutate((table) => table.delete(key));\n }\n resolve(undefined);\n return;\n }\n mutate((table) => {\n const latest = table.get(key);\n latest.onsuccess = () => {\n if (latest.result) {\n try {\n table.put({ ...latest.result, usedAt: now });\n } catch {\n // A quota/security failure only disables this cache write.\n }\n }\n };\n });\n resolve(value);\n };\n } catch {\n db.close();\n resolve(undefined);\n }\n });\n },\n save(value: SavedCatalogue) {\n if (value.scope.store !== store) {\n return;\n }\n mutate((table) => {\n try {\n const bytes = new TextEncoder().encode(JSON.stringify(value)).length;\n if (bytes > maxBytes) {\n return;\n }\n const key = catalogueKey(value.scope);\n const request = table.getAll();\n request.onsuccess = () => {\n const now = Date.now();\n const entries = (request.result as RecordValue[])\n .filter((entry) => entry.key !== key)\n .sort((a, b) => a.usedAt - b.usedAt);\n let total = entries.reduce(\n (sum, entry) => sum + entry.bytes,\n bytes,\n );\n for (const entry of entries) {\n if (now - entry.savedAt >= maxAge || total > maxBytes) {\n table.delete(entry.key);\n total -= entry.bytes;\n }\n }\n try {\n table.put({ ...value, key, bytes, usedAt: now });\n } catch {\n // Quota failures can throw synchronously as well as abort a transaction.\n }\n };\n } catch {\n // Serialization/storage failures are a cache miss, never a form error.\n }\n });\n },\n };\n}\n\n/** Digest excludes transport versions: publication follows shopper-visible content. */\nexport async function catalogueDigest(menu: Menu): Promise<string> {\n const canonical = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n return value.map(canonical);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, entry]) => [key, canonical(entry)]),\n );\n }\n return value;\n };\n const { version: _version, ...content } = menu;\n const bytes = new TextEncoder().encode(JSON.stringify(canonical(content)));\n const hash = await crypto.subtle.digest(\"SHA-256\", bytes);\n return Array.from(new Uint8Array(hash), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n/** A saved source menu that stays separate from the complete branch catalogue. */\nexport function createSavedCatalogue(\n client: import(\"../client\").Zappie,\n scope: CatalogueScope,\n) {\n const storage = createCatalogueStorage(scope.store);\n const key = catalogueKey(scope);\n // Digests run after the saved copy has rendered, never in front of it.\n let lastDigest: Promise<string> | undefined;\n return {\n async restore() {\n const saved = await storage.read(scope);\n if (saved) {\n lastDigest = catalogueDigest(saved.menu);\n client._internal.cache.set(key, saved, saved.etag);\n }\n return saved;\n },\n /** Save in the background and return whether the content changed. */\n async publish(value: SavedCatalogue) {\n const previous = lastDigest;\n const next = catalogueDigest(value.menu);\n lastDigest = next;\n const digest = await next;\n const changed = digest !== (await previous);\n const saved = { ...value, etag: digest };\n if (changed) {\n client._internal.cache.set(key, saved, digest);\n }\n storage.save(saved);\n return changed;\n },\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/client.ts"],
4
- "sourcesContent": ["import { createPlacesRequest } from \"./request\";\nimport { API_VERSION, RestTransport, validateConfig } from \"./sdk\";\nimport { createAddressSearch } from \"./search\";\nimport type {\n AddressSearchOptions,\n Place,\n PlaceOptions,\n PlacesClientOptions,\n PlaceSuggestion,\n SignalOptions,\n ZoneCheck,\n ZoneCheckRequest,\n} from \"./types\";\n\nconst DEFAULT_CUSTOMER_BASE = \"/_zappie/api\";\n\n/**\n * Create a client for address autocomplete, place lookup and delivery-zone checks.\n * Also provides reverse lookup, static-map URLs and debounced address search controllers.\n *\n * @param options - Your publishable key and optional transport settings.\n * @returns A places client. Creating it validates the key format without fetching data.\n */\nexport function createPlaces(options: PlacesClientOptions) {\n validateConfig({ store: \"places\", key: options.key });\n const base = new RestTransport({\n ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),\n ...(options.fetch ? { fetch: options.fetch } : {}),\n }).baseUrl;\n const customerBase = (options.customerBase ?? DEFAULT_CUSTOMER_BASE).replace(\n /\\/+$/,\n \"\",\n );\n const request = createPlacesRequest(\n base,\n options.key,\n options.version ?? API_VERSION,\n options.fetch ?? globalThis.fetch,\n );\n const readPlace = (\n id: string,\n read: SignalOptions & { branchId?: string; sessionToken?: string },\n ) => {\n const params = new URLSearchParams();\n if (read.branchId !== undefined) {\n params.set(\"branch_id\", read.branchId);\n }\n if (read.sessionToken !== undefined) {\n params.set(\"session\", read.sessionToken);\n }\n const suffix = params.size > 0 ? `?${params}` : \"\";\n return request<Place>(`/places/${encodeURIComponent(id)}${suffix}`, {\n method: \"GET\",\n ...(read.signal ? { signal: read.signal } : {}),\n });\n };\n const client = {\n async autocomplete(\n query: string,\n sessionToken: string,\n read: SignalOptions = {},\n ): Promise<PlaceSuggestion[]> {\n if (!query.trim()) {\n return [];\n }\n const params = new URLSearchParams({\n q: query.trim(),\n session: sessionToken,\n });\n const result = await request<{ suggestions: PlaceSuggestion[] }>(\n `/places/autocomplete?${params}`,\n { method: \"GET\", ...(read.signal ? { signal: read.signal } : {}) },\n );\n return result.suggestions;\n },\n place(id: string, read: PlaceOptions): Promise<Place> {\n return readPlace(id, read);\n },\n reverse(lat: number, lng: number, read: SignalOptions = {}) {\n const params = new URLSearchParams({\n lat: String(lat),\n lng: String(lng),\n });\n return request<Place>(`/places/reverse?${params}`, {\n method: \"GET\",\n ...(read.signal ? { signal: read.signal } : {}),\n });\n },\n staticMapUrl(lat: number, lng: number, branchId?: string) {\n const params = new URLSearchParams({\n lat: String(lat),\n lng: String(lng),\n });\n if (branchId !== undefined) {\n params.set(\"branch_id\", branchId);\n }\n return `${customerBase}/places/static-map?${params}`;\n },\n zoneCheck(body: ZoneCheckRequest, read: SignalOptions = {}) {\n return request<ZoneCheck>(\"/delivery/zones/check\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n ...(read.signal ? { signal: read.signal } : {}),\n });\n },\n createAddressSearch(searchOptions: AddressSearchOptions = {}) {\n return createAddressSearch(\n {\n autocomplete: client.autocomplete,\n place: readPlace,\n },\n searchOptions,\n );\n },\n };\n return client;\n}\n\n/**\n * The place lookup, address search and delivery-zone operations returned by createPlaces.\n */\nexport type PlacesClient = ReturnType<typeof createPlaces>;\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/error.ts"],
4
- "sourcesContent": ["import { stableErrorCode } from \"../internal/customer-http\";\nimport { ZappieError } from \"./sdk\";\n\n/**\n * A places API failure with its HTTP status, API code and optional validation issues.\n * Statuses 429 and 5xx are marked retryable.\n */\nexport class PlacesError extends ZappieError {\n readonly issues?: unknown;\n\n constructor(\n status: number,\n readonly apiCode: string,\n message: string,\n param?: string,\n issues?: unknown,\n ) {\n super(stableErrorCode(status), message, {\n status,\n retryable: status === 429 || status >= 500,\n ...(param !== undefined ? { param } : {}),\n });\n if (issues !== undefined) {\n this.issues = issues;\n }\n }\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAMrB,IAAM,cAAN,cAA0B,YAAY;AAAA,EAG3C,YACE,QACS,SACT,SACA,OACA,QACA;AACA,UAAM,gBAAgB,MAAM,GAAG,SAAS;AAAA,MACtC;AAAA,MACA,WAAW,WAAW,OAAO,UAAU;AAAA,MACvC,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,IACzC,CAAC;AATQ;AAUT,QAAI,WAAW,QAAW;AACxB,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/index.ts"],
4
- "sourcesContent": ["export { createPlaces } from \"./client\";\nexport type { PlacesClient } from \"./client\";\nexport { PlacesError } from \"./error\";\nexport type * from \"./types\";\n"],
5
4
  "mappings": ";AAAA,SAAS,oBAAoB;AAE7B,SAAS,mBAAmB;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/request.ts"],
4
- "sourcesContent": ["import { apiError, requestWithRetry } from \"../internal/customer-http\";\nimport { PlacesError } from \"./error\";\n\nexport type PlacesRequest = <T>(path: string, init?: RequestInit) => Promise<T>;\n\nexport function createPlacesRequest(\n base: string,\n key: string,\n version: string,\n fetcher: typeof fetch,\n): PlacesRequest {\n return async <T>(path: string, init: RequestInit = {}): Promise<T> => {\n const response = await requestWithRetry(\n fetcher,\n base + path,\n {\n ...init,\n // Workers reject redirect: \"error\"; !response.ok below refuses a redirect.\n redirect: \"manual\",\n headers: {\n Authorization: `Bearer ${key}`,\n \"Zappie-Version\": version,\n ...init.headers,\n },\n },\n \"Could not reach the places service\",\n );\n if (!response.ok) {\n const value = await apiError(\n response,\n `The places service answered ${response.status}`,\n );\n throw new PlacesError(\n response.status,\n value.apiCode,\n value.message,\n value.param,\n value.issues,\n );\n }\n return (await response.json()) as T;\n };\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,UAAU,wBAAwB;AAC3C,SAAS,mBAAmB;AAIrB,SAAS,oBACd,MACA,KACA,SACA,SACe;AACf,SAAO,OAAU,MAAc,OAAoB,CAAC,MAAkB;AACpE,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,MACP;AAAA,QACE,GAAG;AAAA;AAAA,QAEH,UAAU;AAAA,QACV,SAAS;AAAA,UACP,eAAe,UAAU,GAAG;AAAA,UAC5B,kBAAkB;AAAA,UAClB,GAAG,KAAK;AAAA,QACV;AAAA,MACF;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA,+BAA+B,SAAS,MAAM;AAAA,MAChD;AACA,YAAM,IAAI;AAAA,QACR,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MACR;AAAA,IACF;AACA,WAAQ,MAAM,SAAS,KAAK;AAAA,EAC9B;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/sdk.ts"],
4
- "sourcesContent": ["// Isolated places errors import the installed core so their identity is shared.\n// eslint-disable-next-line no-restricted-imports\nexport { API_VERSION, RestTransport, ZappieError } from \"@zappie/sdk\";\n// eslint-disable-next-line no-restricted-imports\nexport { validateConfig } from \"@zappie/sdk/internal\";\n"],
5
4
  "mappings": ";AAEA,SAAS,aAAa,eAAe,mBAAmB;AAExD,SAAS,sBAAsB;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/places/search.ts"],
4
- "sourcesContent": ["import type { AddressSearchOptions, Place, PlaceSuggestion } from \"./types\";\n\ntype SearchClient = {\n autocomplete(\n query: string,\n token: string,\n options: { signal?: AbortSignal },\n ): Promise<PlaceSuggestion[]>;\n place(\n id: string,\n options: {\n branchId?: string;\n sessionToken?: string;\n signal?: AbortSignal;\n },\n ): Promise<Place>;\n};\n\nconst aborted = () => new DOMException(\"Superseded\", \"AbortError\");\n\nexport function createAddressSearch(\n client: SearchClient,\n options: AddressSearchOptions = {},\n) {\n const debounceMs = options.debounceMs ?? 250;\n let token = crypto.randomUUID();\n let controller: AbortController | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let rejectPending: ((reason: unknown) => void) | undefined;\n let revision = 0;\n const cancel = () => {\n revision++;\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n controller?.abort(aborted());\n controller = undefined;\n rejectPending?.(aborted());\n rejectPending = undefined;\n };\n return {\n suggest(query: string): Promise<PlaceSuggestion[]> {\n cancel();\n const current = revision;\n if (!query.trim()) {\n return Promise.resolve([]);\n }\n return new Promise((resolve, reject) => {\n rejectPending = reject;\n timer = setTimeout(async () => {\n timer = undefined;\n controller = new AbortController();\n try {\n const value = await client.autocomplete(query.trim(), token, {\n signal: controller.signal,\n });\n if (current === revision) {\n resolve(value);\n }\n } catch (failure) {\n if (current === revision) {\n reject(failure);\n }\n } finally {\n if (current === revision) {\n rejectPending = undefined;\n controller = undefined;\n }\n }\n }, debounceMs);\n });\n },\n async select(placeId: string): Promise<Place> {\n cancel();\n const current = revision;\n controller = new AbortController();\n try {\n const value = await client.place(placeId, {\n ...(options.branchId !== undefined\n ? { branchId: options.branchId }\n : {}),\n sessionToken: token,\n signal: controller.signal,\n });\n if (current !== revision) {\n throw aborted();\n }\n token = crypto.randomUUID();\n return value;\n } finally {\n if (current === revision) {\n controller = undefined;\n }\n }\n },\n dispose: cancel,\n };\n}\n"],
5
4
  "mappings": ";AAkBA,IAAM,UAAU,MAAM,IAAI,aAAa,cAAc,YAAY;AAE1D,SAAS,oBACd,QACA,UAAgC,CAAC,GACjC;AACA,QAAM,aAAa,QAAQ,cAAc;AACzC,MAAI,QAAQ,OAAO,WAAW;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI,WAAW;AACf,QAAM,SAAS,MAAM;AACnB;AACA,QAAI,UAAU,QAAW;AACvB,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AACA,gBAAY,MAAM,QAAQ,CAAC;AAC3B,iBAAa;AACb,oBAAgB,QAAQ,CAAC;AACzB,oBAAgB;AAAA,EAClB;AACA,SAAO;AAAA,IACL,QAAQ,OAA2C;AACjD,aAAO;AACP,YAAM,UAAU;AAChB,UAAI,CAAC,MAAM,KAAK,GAAG;AACjB,eAAO,QAAQ,QAAQ,CAAC,CAAC;AAAA,MAC3B;AACA,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,wBAAgB;AAChB,gBAAQ,WAAW,YAAY;AAC7B,kBAAQ;AACR,uBAAa,IAAI,gBAAgB;AACjC,cAAI;AACF,kBAAM,QAAQ,MAAM,OAAO,aAAa,MAAM,KAAK,GAAG,OAAO;AAAA,cAC3D,QAAQ,WAAW;AAAA,YACrB,CAAC;AACD,gBAAI,YAAY,UAAU;AACxB,sBAAQ,KAAK;AAAA,YACf;AAAA,UACF,SAAS,SAAS;AAChB,gBAAI,YAAY,UAAU;AACxB,qBAAO,OAAO;AAAA,YAChB;AAAA,UACF,UAAE;AACA,gBAAI,YAAY,UAAU;AACxB,8BAAgB;AAChB,2BAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF,GAAG,UAAU;AAAA,MACf,CAAC;AAAA,IACH;AAAA,IACA,MAAM,OAAO,SAAiC;AAC5C,aAAO;AACP,YAAM,UAAU;AAChB,mBAAa,IAAI,gBAAgB;AACjC,UAAI;AACF,cAAM,QAAQ,MAAM,OAAO,MAAM,SAAS;AAAA,UACxC,GAAI,QAAQ,aAAa,SACrB,EAAE,UAAU,QAAQ,SAAS,IAC7B,CAAC;AAAA,UACL,cAAc;AAAA,UACd,QAAQ,WAAW;AAAA,QACrB,CAAC;AACD,YAAI,YAAY,UAAU;AACxB,gBAAM,QAAQ;AAAA,QAChB;AACA,gBAAQ,OAAO,WAAW;AAC1B,eAAO;AAAA,MACT,UAAE;AACA,YAAI,YAAY,UAAU;AACxB,uBAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,EACX;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": [],
4
- "sourcesContent": [],
5
4
  "mappings": "",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/pricing/index.ts"],
4
- "sourcesContent": ["import { sumMoney } from \"../internal/money-math\";\n// The declarations import the installed core so Money keeps the core's brand.\n// eslint-disable-next-line no-restricted-imports\nimport type { Item, Money } from \"@zappie/sdk\";\n\nconst cheapest = (prices: Money[]): Money | undefined =>\n prices\n .filter((price) => price.amountMinor >= 0)\n .sort((a, b) => a.amountMinor - b.amountMinor)[0];\n\n/**\n * The lowest price a shopper can pay for one of this item, by the same rule\n * Zappie uses for a product's starting price: the cheapest available variant\n * plus the cheapest choice of each required add-on group. When no variant is\n * available, the cheapest variant is used.\n *\n * @param item - The catalogue item.\n * @returns A display price. Use a server quote for checkout totals.\n */\nexport function startingPrice(item: Item): Money {\n const available = item.variants.filter((variant) => variant.isAvailable);\n const base =\n cheapest(\n (available.length ? available : item.variants).map(\n (variant) => variant.price,\n ),\n ) ?? item.price;\n return sumMoney(\n item.optionGroups.flatMap((group) =>\n !group.isAttribute && group.required && group.kind === \"select\"\n ? (cheapest(group.choices.map((choice) => choice.price)) ?? [])\n : [],\n ),\n base,\n );\n}\n\n/**\n * Whether to show the starting price as \"From\": true when available variants\n * have different prices, or a required add-on raises the starting price.\n *\n * @param item - The catalogue item.\n */\nexport function showsFromPrice(item: Item): boolean {\n const prices = [\n ...new Set(\n item.variants\n .filter((variant) => variant.isAvailable)\n .map((variant) => variant.price.amountMinor),\n ),\n ];\n return (\n prices.length > 1 ||\n (prices.length === 1 && startingPrice(item).amountMinor > prices[0]!)\n );\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,gBAAgB;AAKzB,IAAM,WAAW,CAAC,WAChB,OACG,OAAO,CAAC,UAAU,MAAM,eAAe,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;AAW7C,SAAS,cAAc,MAAmB;AAC/C,QAAM,YAAY,KAAK,SAAS,OAAO,CAAC,YAAY,QAAQ,WAAW;AACvE,QAAM,OACJ;AAAA,KACG,UAAU,SAAS,YAAY,KAAK,UAAU;AAAA,MAC7C,CAAC,YAAY,QAAQ;AAAA,IACvB;AAAA,EACF,KAAK,KAAK;AACZ,SAAO;AAAA,IACL,KAAK,aAAa;AAAA,MAAQ,CAAC,UACzB,CAAC,MAAM,eAAe,MAAM,YAAY,MAAM,SAAS,WAClD,SAAS,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,KAAK,CAAC,IAC3D,CAAC;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,eAAe,MAAqB;AAClD,QAAM,SAAS;AAAA,IACb,GAAG,IAAI;AAAA,MACL,KAAK,SACF,OAAO,CAAC,YAAY,QAAQ,WAAW,EACvC,IAAI,CAAC,YAAY,QAAQ,MAAM,WAAW;AAAA,IAC/C;AAAA,EACF;AACA,SACE,OAAO,SAAS,KACf,OAAO,WAAW,KAAK,cAAc,IAAI,EAAE,cAAc,OAAO,CAAC;AAEtE;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/cart.ts"],
4
- "sourcesContent": ["\"use client\";\nimport { useEffect, useSyncExternalStore } from \"react\";\nimport { EMPTY_CART } from \"../cart\";\nimport { useZappie } from \"./provider\";\n/** Load saved cart state after mount. Server and initial hydration snapshots are empty. */\nexport function useCart() {\n const { cart } = useZappie();\n const snapshot = useSyncExternalStore(\n cart.subscribe,\n cart.getSnapshot,\n () => EMPTY_CART,\n );\n useEffect(() => cart.mount(), [cart]);\n return { ...snapshot, actions: cart };\n}\n/**\n * Subscribe to the cart and return the line with the given ID, or undefined if it is absent.\n * Use within a ZappieProvider. Server and initial hydration snapshots contain no lines.\n */\nexport function useCartLine(lineId: string) {\n return useCart().lines.find((line) => line.id === lineId);\n}\n/** Read the cart validation catalogue, including held items, without fetching it. */\nexport function useCartCatalogue() {\n const client = useZappie();\n useCart();\n return client._internal.cartCatalogue();\n}\n"],
5
4
  "mappings": ";;;AACA,SAAS,WAAW,4BAA4B;AAChD,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAEnB,SAAS,UAAU;AACxB,QAAM,EAAE,KAAK,IAAI,UAAU;AAC3B,QAAM,WAAW;AAAA,IACf,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AACA,YAAU,MAAM,KAAK,MAAM,GAAG,CAAC,IAAI,CAAC;AACpC,SAAO,EAAE,GAAG,UAAU,SAAS,KAAK;AACtC;AAKO,SAAS,YAAY,QAAgB;AAC1C,SAAO,QAAQ,EAAE,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM;AAC1D;AAEO,SAAS,mBAAmB;AACjC,QAAM,SAAS,UAAU;AACzB,UAAQ;AACR,SAAO,OAAO,UAAU,cAAc;AACxC;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/hooks.ts"],
4
- "sourcesContent": ["\"use client\";\nimport { useEffect, useMemo, useSyncExternalStore } from \"react\";\nimport { ZappieError } from \"../errors\";\nimport type { Menu, Store } from \"../models\";\nimport { useZappie } from \"./provider\";\n/** Subscribe to storefront identity and branch-directory data. */\nexport function useStore() {\n const client = useZappie();\n const cache = client._internal.cache;\n const snapshot = useSyncExternalStore(\n cache.subscribe,\n () => cache.snapshot<Store>(\"store\"),\n () => cache.snapshot<Store>(\"store\"),\n );\n useEffect(() => {\n const abort = new AbortController();\n void client.store.get({ signal: abort.signal }).catch(() => {});\n return () => abort.abort();\n }, [client]);\n return {\n data: snapshot.value,\n error: snapshot.error,\n isLoading: !snapshot.value && !snapshot.error,\n };\n}\n/** Subscribe to a branch catalogue and refresh stale data in the background. */\nexport function useMenu(\n branch?: string,\n options: { metadataOnly?: boolean } = {},\n) {\n const metadataOnly = options.metadataOnly === true;\n const client = useZappie();\n const store = useStore();\n const cache = client._internal.cache;\n let key: string;\n let branchError: ZappieError | undefined;\n try {\n key = client._internal.menuKey(branch);\n if (metadataOnly && !cache.snapshot<Menu>(key).value) {\n key += \":partial\";\n }\n } catch (error) {\n key = \"menu:invalid\";\n branchError = error as ZappieError;\n }\n const snapshot = useSyncExternalStore(\n cache.subscribe,\n () => cache.snapshot<Menu>(key),\n () => cache.snapshot<Menu>(key),\n );\n useEffect(() => {\n if (!store.data || branchError) {\n return;\n }\n if (\n metadataOnly &&\n cache.snapshot<Menu>(client._internal.menuKey(branch)).value\n ) {\n return;\n }\n const abort = new AbortController();\n void client.menu\n .get({\n ...(branch ? { branch } : {}),\n ...(metadataOnly ? { metadataOnly: true } : {}),\n signal: abort.signal,\n })\n .catch(() => {});\n return () => abort.abort();\n }, [client, branch, store.data, branchError?.code, metadataOnly, cache]);\n const error = branchError ?? snapshot.error ?? store.error;\n return {\n data: snapshot.value,\n error,\n isLoading: !snapshot.value && !error,\n isStale: cache.stale(key),\n };\n}\n/** Select one item from the branch catalogue without duplicating a request. */\nexport function useItem(id: string, branch?: string) {\n const menu = useMenu(branch);\n const data =\n menu.data && Object.prototype.hasOwnProperty.call(menu.data.items, id)\n ? menu.data.items[id]\n : undefined;\n const missing = Boolean(menu.data) && !data;\n const notFound = useMemo(\n () =>\n missing ? new ZappieError(\"item_not_found\", \"Item not found\") : undefined,\n [missing, id],\n );\n return {\n data,\n error: menu.error ?? notFound,\n isLoading: menu.isLoading,\n };\n}\n"],
5
4
  "mappings": ";;;AACA,SAAS,WAAW,SAAS,4BAA4B;AACzD,SAAS,mBAAmB;AAE5B,SAAS,iBAAiB;AAEnB,SAAS,WAAW;AACzB,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,OAAO,UAAU;AAC/B,QAAM,WAAW;AAAA,IACf,MAAM;AAAA,IACN,MAAM,MAAM,SAAgB,OAAO;AAAA,IACnC,MAAM,MAAM,SAAgB,OAAO;AAAA,EACrC;AACA,YAAU,MAAM;AACd,UAAM,QAAQ,IAAI,gBAAgB;AAClC,SAAK,OAAO,MAAM,IAAI,EAAE,QAAQ,MAAM,OAAO,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAC9D,WAAO,MAAM,MAAM,MAAM;AAAA,EAC3B,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,OAAO,SAAS;AAAA,IAChB,WAAW,CAAC,SAAS,SAAS,CAAC,SAAS;AAAA,EAC1C;AACF;AAEO,SAAS,QACd,QACA,UAAsC,CAAC,GACvC;AACA,QAAM,eAAe,QAAQ,iBAAiB;AAC9C,QAAM,SAAS,UAAU;AACzB,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ,OAAO,UAAU;AAC/B,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,UAAU,QAAQ,MAAM;AACrC,QAAI,gBAAgB,CAAC,MAAM,SAAe,GAAG,EAAE,OAAO;AACpD,aAAO;AAAA,IACT;AAAA,EACF,SAASA,QAAO;AACd,UAAM;AACN,kBAAcA;AAAA,EAChB;AACA,QAAM,WAAW;AAAA,IACf,MAAM;AAAA,IACN,MAAM,MAAM,SAAe,GAAG;AAAA,IAC9B,MAAM,MAAM,SAAe,GAAG;AAAA,EAChC;AACA,YAAU,MAAM;AACd,QAAI,CAAC,MAAM,QAAQ,aAAa;AAC9B;AAAA,IACF;AACA,QACE,gBACA,MAAM,SAAe,OAAO,UAAU,QAAQ,MAAM,CAAC,EAAE,OACvD;AACA;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,gBAAgB;AAClC,SAAK,OAAO,KACT,IAAI;AAAA,MACH,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B,GAAI,eAAe,EAAE,cAAc,KAAK,IAAI,CAAC;AAAA,MAC7C,QAAQ,MAAM;AAAA,IAChB,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AACjB,WAAO,MAAM,MAAM,MAAM;AAAA,EAC3B,GAAG,CAAC,QAAQ,QAAQ,MAAM,MAAM,aAAa,MAAM,cAAc,KAAK,CAAC;AACvE,QAAM,QAAQ,eAAe,SAAS,SAAS,MAAM;AACrD,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf;AAAA,IACA,WAAW,CAAC,SAAS,SAAS,CAAC;AAAA,IAC/B,SAAS,MAAM,MAAM,GAAG;AAAA,EAC1B;AACF;AAEO,SAAS,QAAQ,IAAY,QAAiB;AACnD,QAAM,OAAO,QAAQ,MAAM;AAC3B,QAAM,OACJ,KAAK,QAAQ,OAAO,UAAU,eAAe,KAAK,KAAK,KAAK,OAAO,EAAE,IACjE,KAAK,KAAK,MAAM,EAAE,IAClB;AACN,QAAM,UAAU,QAAQ,KAAK,IAAI,KAAK,CAAC;AACvC,QAAM,WAAW;AAAA,IACf,MACE,UAAU,IAAI,YAAY,kBAAkB,gBAAgB,IAAI;AAAA,IAClE,CAAC,SAAS,EAAE;AAAA,EACd;AACA,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK,SAAS;AAAA,IACrB,WAAW,KAAK;AAAA,EAClB;AACF;",
6
5
  "names": ["error"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/index.ts"],
4
- "sourcesContent": ["\"use client\";\nexport { VERSION } from \"../index\";\nexport { useItem, useMenu, useStore } from \"./hooks\";\nexport { ZappieProvider, useZappie } from \"./provider\";\n\nexport { useCart, useCartCatalogue, useCartLine } from \"./cart\";\n"],
5
4
  "mappings": ";;;AACA,SAAS,eAAe;AACxB,SAAS,SAAS,SAAS,gBAAgB;AAC3C,SAAS,gBAAgB,iBAAiB;AAE1C,SAAS,SAAS,kBAAkB,mBAAmB;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/react/provider.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { ReactNode } from \"react\";\nimport { createContext, useContext, useMemo } from \"react\";\nimport type { Zappie } from \"../client\";\nimport type { Menu, Store } from \"../models\";\nconst Context = createContext<Zappie | null>(null);\n/** Supply initial storefront data before children subscribe. Use one client per server request. */\nexport function ZappieProvider({\n client,\n initialData,\n children,\n}: {\n client: Zappie;\n initialData?: { store?: Store; menu?: Menu };\n children: ReactNode;\n}) {\n const value = useMemo(() => {\n if (initialData?.store && !client.store.peek()) {\n client._internal.hydrateStore(initialData.store);\n }\n if (\n initialData?.menu &&\n !client._internal.cache.snapshot(`menu:${initialData.menu.branchId}`)\n .value\n ) {\n client.menu.hydrate(initialData.menu);\n }\n return client;\n }, [client, initialData]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n/** Read your storefront client from the nearest ZappieProvider. */\nexport function useZappie(): Zappie {\n const client = useContext(Context);\n if (!client) {\n throw new Error(\"useZappie requires ZappieProvider\");\n }\n return client;\n}\n"],
5
4
  "mappings": ";;;AAEA,SAAS,eAAe,YAAY,eAAe;AA2B1C;AAxBT,IAAM,UAAU,cAA6B,IAAI;AAE1C,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,QAAQ,QAAQ,MAAM;AAC1B,QAAI,aAAa,SAAS,CAAC,OAAO,MAAM,KAAK,GAAG;AAC9C,aAAO,UAAU,aAAa,YAAY,KAAK;AAAA,IACjD;AACA,QACE,aAAa,QACb,CAAC,OAAO,UAAU,MAAM,SAAS,QAAQ,YAAY,KAAK,QAAQ,EAAE,EACjE,OACH;AACA,aAAO,KAAK,QAAQ,YAAY,IAAI;AAAA,IACtC;AACA,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,WAAW,CAAC;AACxB,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AACnD;AAEO,SAAS,YAAoB;AAClC,QAAM,SAAS,WAAW,OAAO;AACjC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,SAAO;AACT;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
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\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
4
  "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
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/auth/crypto.ts"],
4
- "sourcesContent": ["export const encoder = new TextEncoder();\nexport const decoder = new TextDecoder();\n\nexport function base64UrlEncode(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n}\n\n// Null for anything but unpadded base64url, so a forged segment is refused\n// as malformed rather than thrown as a DOMException.\nexport function base64UrlDecode(text: string): Uint8Array<ArrayBuffer> | null {\n return /^[\\w-]*$/.test(text) && text.length % 4 !== 1\n ? Uint8Array.from(\n atob(text.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\")),\n (c) => c.charCodeAt(0),\n )\n : null;\n}\n\n/** A PKCE code verifier and its S256 challenge for one sign-in attempt. */\nexport type PkcePair = {\n /** Keep this on the server and send it with the code exchange. */\n verifier: string;\n /** Send this to the authorize endpoint as `code_challenge`. */\n challenge: string;\n /** Send this as `code_challenge_method`. */\n method: \"S256\";\n};\n\n/** One sealing secret, or several while you rotate them. The first one seals. */\nexport type SealSecret = string | readonly string[];\n\n/** Options for sealing a value into a cookie-safe string. */\nexport type SealOptions = {\n /**\n * At least 32 bytes each, from a random generator such as `randomToken()`,\n * never a password or phrase. The key is the secret's SHA-256 with no\n * stretching, so a guessable secret can be found offline from one sealed\n * value. List the new secret first while you rotate.\n */\n secret: SealSecret;\n /**\n * Binds the sealed value to one use, such as `\"tx\"`, so it won't open for\n * another. Leave it out to match the hosted sign-in Worker's format.\n */\n purpose?: string;\n};\n\n/** Create a random URL-safe token from `bytes` random bytes, for state, nonces and ids. */\nexport function randomToken(bytes = 32): string {\n if (!Number.isInteger(bytes) || bytes < 16 || bytes > 1024) {\n throw new RangeError(\"randomToken takes between 16 and 1024 bytes.\");\n }\n return base64UrlEncode(crypto.getRandomValues(new Uint8Array(bytes)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n encoder.encode(verifier),\n );\n return base64UrlEncode(new Uint8Array(digest));\n}\n\n/** Create a PKCE verifier and its S256 challenge. */\nexport async function createPkce(): Promise<PkcePair> {\n const verifier = randomToken();\n return { verifier, challenge: await pkceChallenge(verifier), method: \"S256\" };\n}\n\nexport function secretsOf(secret: SealSecret): readonly string[] {\n const list = typeof secret === \"string\" ? [secret] : secret;\n if (!list.length || list.some((value) => encoder.encode(value).length < 32)) {\n throw new TypeError(\"Each sealing secret needs at least 32 bytes.\");\n }\n return list;\n}\n\nconst keys = new Map<string, Promise<CryptoKey>>();\n\n// The key is the SHA-256 of the secret, as in the hosted sign-in Worker, so a\n// value it sealed opens here with the same secret.\nfunction keyFor(secret: string) {\n if (!keys.has(secret)) {\n if (keys.size >= 16) {\n keys.clear();\n }\n keys.set(\n secret,\n crypto.subtle\n .digest(\"SHA-256\", encoder.encode(secret))\n .then((digest) =>\n crypto.subtle.importKey(\"raw\", digest, \"AES-GCM\", false, [\n \"encrypt\",\n \"decrypt\",\n ]),\n ),\n );\n }\n return keys.get(secret) as Promise<CryptoKey>;\n}\n\nconst params = (iv: Uint8Array<ArrayBuffer>, purpose?: string) => ({\n name: \"AES-GCM\",\n iv,\n ...(purpose === undefined ? {} : { additionalData: encoder.encode(purpose) }),\n});\n\n/** Encrypt a JSON value with AES-GCM into a cookie-safe string. */\nexport async function sealValue(\n value: unknown,\n { secret, purpose }: SealOptions,\n): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const cipher = await crypto.subtle.encrypt(\n params(iv, purpose),\n await keyFor(secretsOf(secret)[0] as string),\n encoder.encode(JSON.stringify(value)),\n );\n return `${base64UrlEncode(iv)}.${base64UrlEncode(new Uint8Array(cipher))}`;\n}\n\n/** Decrypt a sealed value with any listed secret. Returns null when none opens it. */\nexport async function openSealedValue<T = unknown>(\n sealed: string | null | undefined,\n { secret, purpose }: SealOptions,\n): Promise<T | null> {\n const list = secretsOf(secret);\n const [ivText = \"\", cipherText = \"\", extra] = (sealed ?? \"\").split(\".\");\n const iv = base64UrlDecode(ivText);\n const cipher = base64UrlDecode(cipherText);\n if (iv?.length !== 12 || !cipher?.length || extra !== undefined) {\n return null;\n }\n for (const candidate of list) {\n try {\n const plain = await crypto.subtle.decrypt(\n params(iv, purpose),\n await keyFor(candidate),\n cipher,\n );\n return JSON.parse(decoder.decode(plain)) as T;\n } catch {\n // Try the next secret.\n }\n }\n return null;\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/auth/jwt.ts"],
4
- "sourcesContent": ["import { ZappieError } from \"../../errors\";\nimport { base64UrlDecode, decoder, encoder } from \"./crypto\";\n\n/** Signing algorithms Sign in with Zappie tokens can use. */\nexport type TokenAlgorithm = \"RS256\" | \"ES256\";\n\nconst messages = {\n malformed: \"The token isn't a signed JWT.\",\n unsupported_algorithm: \"The token uses an algorithm this verifier refuses.\",\n unknown_key: \"The token was signed with a key the issuer doesn't publish.\",\n invalid_signature: \"The token's signature doesn't match.\",\n wrong_issuer: \"The token comes from a different issuer.\",\n wrong_audience: \"The token was issued for a different audience.\",\n wrong_nonce: \"The token wasn't issued for this sign-in.\",\n wrong_subject: \"The token's subject isn't a Zappie customer.\",\n wrong_type: \"The token isn't an ID token.\",\n expired: \"The token has expired.\",\n not_yet_valid: \"The token isn't valid yet.\",\n keys_unavailable: \"The identity issuer's signing keys couldn't be read.\",\n};\n\n/** Why a token was refused. */\nexport type TokenErrorReason = keyof typeof messages;\n\n/** A token Zappie's identity issuer didn't sign for you, or one that is no longer valid. */\nexport class ZappieTokenError extends ZappieError {\n constructor(\n readonly reason: TokenErrorReason,\n cause?: unknown,\n ) {\n super(\n reason === \"keys_unavailable\" ? \"network_error\" : \"unauthorized\",\n messages[reason],\n cause === undefined ? {} : { cause },\n );\n this.name = \"ZappieTokenError\";\n }\n}\n\n/** The verified claims of a token. Extra claims stay on the object. */\nexport type TokenClaims = {\n iss: string;\n aud: string | string[];\n exp: number;\n sub?: string;\n iat?: number;\n nbf?: number;\n nonce?: string;\n sid?: string;\n [claim: string]: unknown;\n};\n\n/** Settings for verifying tokens from Zappie's identity issuer. */\nexport type TokenVerifierOptions = {\n /** The issuer URL, such as `https://accounts.zappie.ai`. The token's `iss` must equal it. A trailing slash on either is ignored. */\n issuer: string;\n /** The audience the token must name: your site's client id for ID tokens, or the API audience for access tokens. */\n audience: string;\n /** Where to read the signing keys. Defaults to `<issuer>/.well-known/jwks.json`. */\n jwksUrl?: string;\n /** Allowed algorithms. Defaults to RS256 and ES256. */\n algorithms?: readonly TokenAlgorithm[];\n /** Seconds of clock skew allowed on `exp`, `nbf` and `iat`. Defaults to 60. */\n clockToleranceSec?: number;\n /** Seconds to keep the key set before reading it again, 0 or more. Defaults to one hour. */\n cacheTtlSec?: number;\n /** Prefix the `sub` claim must start with. Zappie customers are `cus_`. A token without `sub` is refused. Pass `null` to skip both checks. */\n subjectPrefix?: string | null;\n /** The fetch to read keys with. Defaults to the global fetch. */\n fetch?: typeof fetch;\n /** The current time in milliseconds. For tests. */\n now?: () => number;\n};\n\n/** Per-call checks on top of the verifier's settings. */\nexport type VerifyTokenChecks = {\n /** The nonce you sent with the sign-in. When you pass it, the token's `nonce` must equal it. */\n nonce?: string;\n /** Overrides the verifier's audience for this token. */\n audience?: string;\n};\n\n/** Checks for an ID token from a sign-in. */\nexport type IdTokenChecks = {\n /** The nonce you sent with this sign-in. The token's `nonce` must equal it. */\n nonce: string;\n /** Overrides the verifier's audience for this token. */\n audience?: string;\n};\n\n/** The verified claims of an ID token. */\nexport type IdTokenClaims = TokenClaims & { sub: string; nonce: string };\n\n/** Verifies signed tokens against the issuer's published keys. */\nexport type TokenVerifier = {\n /** Verify a token's signature and claims, and return its claims. */\n verify(token: string, checks?: VerifyTokenChecks): Promise<TokenClaims>;\n /**\n * Verify the ID token from a sign-in. It needs the nonce you sent, a `sub`,\n * and refuses a token whose `typ` header names another kind of token, such\n * as an access token (`at+jwt`).\n */\n verifyIdToken(token: string, checks: IdTokenChecks): Promise<IdTokenClaims>;\n};\n\ntype Jwk = Record<string, unknown>;\ntype KeySet = { keys: Map<string, CryptoKey>; at: number };\n\nconst specs = {\n RS256: {\n kty: \"RSA\",\n jwk: (key: Jwk) => ({ kty: \"RSA\", n: key.n, e: key.e }),\n import: { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" },\n verify: { name: \"RSASSA-PKCS1-v1_5\" },\n },\n ES256: {\n kty: \"EC\",\n jwk: (key: Jwk) => ({ kty: \"EC\", crv: \"P-256\", x: key.x, y: key.y }),\n import: { name: \"ECDSA\", namedCurve: \"P-256\" },\n verify: { name: \"ECDSA\", hash: \"SHA-256\" },\n },\n} as const;\n\nconst fail = (reason: TokenErrorReason, cause?: unknown): never => {\n throw new ZappieTokenError(reason, cause);\n};\nconst numeric = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value);\nconst trimSlash = (value: string) => value.replace(/\\/+$/, \"\");\nconst KEY_RETRY_MS = 30_000;\n\nfunction decodeJson(text: string): Record<string, unknown> {\n const bytes = base64UrlDecode(text) ?? fail(\"malformed\");\n let value: unknown;\n try {\n value = JSON.parse(decoder.decode(bytes));\n } catch {\n // Refused below.\n }\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : fail(\"malformed\");\n}\n\n/**\n * Create a verifier for tokens from Zappie's identity issuer. It caches the\n * issuer's key set, reads it again when a token names an unknown key, and\n * keeps the last good set while the issuer is down. It uses Web Crypto only,\n * so it runs in Workers and Node 20 or later.\n */\nexport function createTokenVerifier(\n options: TokenVerifierOptions,\n): TokenVerifier {\n const issuer = trimSlash(options.issuer);\n const allowed: readonly string[] = options.algorithms ?? [\"RS256\", \"ES256\"];\n const tolerance = options.clockToleranceSec ?? 60;\n const ttl = (options.cacheTtlSec ?? 3600) * 1000;\n const prefix =\n options.subjectPrefix === undefined ? \"cus_\" : options.subjectPrefix;\n const now = options.now ?? Date.now;\n const read = options.fetch ?? ((url, init) => fetch(url, init));\n if (\n !issuer ||\n !options.audience ||\n !allowed.length ||\n allowed.some((alg) => !(alg in specs)) ||\n !numeric(tolerance) ||\n tolerance < 0 ||\n !numeric(ttl) ||\n ttl < 0\n ) {\n throw new TypeError(\n \"createTokenVerifier needs an issuer, an audience, RS256 or ES256, and a clock tolerance and cache time of 0 or more.\",\n );\n }\n\n let cached: KeySet | undefined;\n let pending: Promise<KeySet> | undefined;\n let triedAt = Number.NEGATIVE_INFINITY;\n let lastError: unknown;\n\n const load = async (): Promise<KeySet> => {\n try {\n const response = await read(\n options.jwksUrl ?? `${issuer}/.well-known/jwks.json`,\n {\n headers: { accept: \"application/json\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n const text = response.ok ? await response.text() : \"\";\n if (!text || text.length > 1 << 20) {\n throw new Error(`The key set answered ${response.status}.`);\n }\n const keys = new Map<string, CryptoKey>();\n const list = (JSON.parse(text) as { keys?: unknown }).keys;\n for (const jwk of Array.isArray(list) ? (list as Jwk[]) : []) {\n for (const alg of allowed as TokenAlgorithm[]) {\n const spec = specs[alg];\n if (\n jwk.kty === spec.kty &&\n (jwk.use ?? \"sig\") === \"sig\" &&\n (jwk.alg ?? alg) === alg &&\n (alg === \"RS256\" || jwk.crv === \"P-256\")\n ) {\n // A key this runtime can't import can't have signed a token we accept.\n await crypto.subtle\n .importKey(\n \"jwk\",\n spec.jwk(jwk) as JsonWebKey,\n spec.import,\n false,\n [\"verify\"],\n )\n .then((key) => keys.set(`${alg}:${String(jwk.kid ?? \"\")}`, key))\n .catch(() => undefined);\n }\n }\n }\n // An empty or unusable set is an issuer fault, not a rotation, so it\n // never replaces the keys that verified tokens until now.\n if (!keys.size) {\n throw new Error(\"The key set has no keys this verifier can use.\");\n }\n return (cached = { keys, at: now() });\n } catch (error) {\n return fail(\"keys_unavailable\", error);\n }\n };\n\n const find = (set: KeySet, kid: unknown, alg: string) => {\n if (typeof kid === \"string\") {\n return set.keys.get(`${alg}:${kid}`);\n }\n // Without a kid, only a key set with one key for the algorithm can answer.\n const fits = [...set.keys].filter(([id]) => id.startsWith(`${alg}:`));\n return fits.length === 1 ? fits[0]?.[1] : undefined;\n };\n\n // The key set is read at most every 30 seconds, whether a forged kid\n // missed the cache or the issuer is down, so neither can make every\n // request call the issuer.\n const keyFor = async (kid: unknown, alg: string) => {\n if (!pending) {\n const key = cached && find(cached, kid, alg);\n if (key && cached && now() - cached.at < ttl) {\n return key;\n }\n if (now() - triedAt < KEY_RETRY_MS) {\n return cached ? key : fail(\"keys_unavailable\", lastError);\n }\n triedAt = now();\n pending = load().finally(() => (pending = undefined));\n }\n try {\n const set = await pending;\n lastError = undefined;\n return find(set, kid, alg);\n } catch (error) {\n lastError = error;\n return (\n (cached && find(cached, kid, alg)) ?? fail(\"keys_unavailable\", error)\n );\n }\n };\n\n const check = async (\n token: string,\n checks: VerifyTokenChecks,\n idToken: boolean,\n ): Promise<TokenClaims> => {\n const parts =\n typeof token === \"string\" && token.length < 16_384\n ? token.split(\".\")\n : [];\n const [head = \"\", body = \"\", sig = \"\"] =\n parts.length === 3 ? parts : fail(\"malformed\");\n const header = decodeJson(head);\n const claims = decodeJson(body);\n const signature = base64UrlDecode(sig) ?? fail(\"malformed\");\n const alg = header.alg as TokenAlgorithm;\n if (!allowed.includes(alg) || header.crit !== undefined) {\n fail(\"unsupported_algorithm\");\n }\n if (\n idToken &&\n header.typ !== undefined &&\n (typeof header.typ !== \"string\" ||\n header.typ.toLowerCase().replace(/^application\\//, \"\") !== \"jwt\")\n ) {\n fail(\"wrong_type\");\n }\n const key = (await keyFor(header.kid, alg)) ?? fail(\"unknown_key\");\n if (\n (alg === \"ES256\" && signature.length !== 64) ||\n !(await crypto.subtle.verify(\n specs[alg].verify,\n key,\n signature,\n encoder.encode(`${head}.${body}`),\n ))\n ) {\n fail(\"invalid_signature\");\n }\n\n const seconds = now() / 1000;\n const times = [claims.nbf, claims.iat];\n const sub = claims.sub;\n if (typeof claims.iss !== \"string\" || trimSlash(claims.iss) !== issuer) {\n fail(\"wrong_issuer\");\n }\n if (![claims.aud].flat().includes(checks.audience ?? options.audience)) {\n fail(\"wrong_audience\");\n }\n if (\n !numeric(claims.exp) ||\n times.some((time) => time !== undefined && !numeric(time))\n ) {\n fail(\"malformed\");\n }\n if (seconds > (claims.exp as number) + tolerance) {\n fail(\"expired\");\n }\n if (times.some((time) => (time as number) > seconds + tolerance)) {\n fail(\"not_yet_valid\");\n }\n if (checks.nonce !== undefined && claims.nonce !== checks.nonce) {\n fail(\"wrong_nonce\");\n }\n if (\n (prefix !== null || idToken) &&\n (typeof sub !== \"string\" ||\n !sub ||\n (prefix !== null &&\n (!sub.startsWith(prefix) || sub.length === prefix.length)))\n ) {\n fail(\"wrong_subject\");\n }\n return claims as TokenClaims;\n };\n\n return {\n verify: (token, checks = {}) => check(token, checks, false),\n async verifyIdToken(token, checks) {\n if (typeof checks?.nonce !== \"string\" || !checks.nonce) {\n throw new TypeError(\n \"verifyIdToken needs the nonce you sent with the sign-in.\",\n );\n }\n return (await check(token, checks, true)) as IdTokenClaims;\n },\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/auth/session-store.ts"],
4
- "sourcesContent": ["import { ZappieError } from \"../../errors\";\nimport {\n openSealedValue,\n sealValue,\n secretsOf,\n type SealSecret,\n} from \"./crypto\";\n\n/** What a signed-in session holds on the server. Tokens never reach the browser. */\nexport type SessionData = {\n /** The customer's subject, such as `cus_7Kd`. */\n sub: string;\n /** The OAuth client the session belongs to, your site's client id. */\n clientId: string;\n /** What the site may show about the customer. */\n customer: { id: string; givenName?: string; phoneMasked: string };\n accessToken: string;\n /** When the access token expires, in epoch milliseconds. */\n accessExp: number;\n refreshToken: string;\n /** The issuer's session id from the ID token, for back-channel logout. */\n idTokenSid?: string;\n scopes: string[];\n /** Epoch milliseconds. */\n createdAt: number;\n /** Epoch milliseconds. */\n lastSeenAt: number;\n mode: \"test\" | \"live\";\n};\n\n/**\n * Where signed-in sessions live. The session id is what the browser's\n * session cookie holds.\n */\nexport interface SessionStore {\n /** Save a new session and return its id. */\n create(data: SessionData): Promise<string>;\n /** Read a session, or null when it doesn't exist or has expired. */\n get(sid: string): Promise<SessionData | null>;\n /**\n * Change a session and return the id to keep in the cookie, or null when\n * the session no longer exists. Stores that keep sessions on the server\n * return the same id. The cookie store returns a new one.\n */\n update(sid: string, patch: Partial<SessionData>): Promise<string | null>;\n /** End one session. */\n delete(sid: string): Promise<void>;\n /** End every session for a customer and client, for back-channel logout. */\n deleteBySub(sub: string, clientId: string): Promise<void>;\n}\n\n/** Settings for the encrypted-cookie session store. */\nexport type CookieSessionStoreOptions = {\n /**\n * At least 32 bytes each, from a random generator such as `randomToken()`,\n * never a password or phrase. The key is the secret's SHA-256 with no\n * stretching, so a guessable secret can be found offline from one sealed\n * value. List the new secret first while you rotate.\n */\n secret: SealSecret;\n /** How long a session lasts from sign-in, in seconds. Defaults to 30 days. */\n maxAgeSec?: number;\n /** The current time in milliseconds. For tests. */\n now?: () => number;\n};\n\ntype Envelope = { v: 1; exp: number; data: SessionData };\n\n/**\n * Create a session store that keeps the whole session, sealed with AES-GCM,\n * in the cookie itself, so it needs no database. It can't end a session from\n * the server, so back-channel logout needs a store that keeps sessions there.\n * Signing out clears the cookie in that browser only. A copy of the cookie\n * still opens the session until it expires, 30 days after sign-in by\n * default, or until you rotate the secret out.\n */\nexport function cookieSessionStore(\n options: CookieSessionStoreOptions,\n): SessionStore {\n const maxAge = (options.maxAgeSec ?? 30 * 24 * 60 * 60) * 1000;\n const now = options.now ?? Date.now;\n const sealing = { secret: options.secret, purpose: \"zappie_session\" };\n secretsOf(options.secret);\n if (!(maxAge > 0 && Number.isFinite(maxAge))) {\n throw new TypeError(\"maxAgeSec must be a positive number of seconds.\");\n }\n const refuse = (message: string) => {\n throw new ZappieError(\"invalid_request\", message);\n };\n const seal = async (envelope: Envelope) => {\n const sealed = await sealValue(envelope, sealing);\n // Browsers drop a cookie over 4 KB without an error.\n return sealed.length > 3800\n ? refuse(\n \"This session is too large for a cookie. Use a session store that keeps sessions on the server.\",\n )\n : sealed;\n };\n const open = async (sid: string) => {\n const envelope = await openSealedValue<Envelope>(sid, sealing);\n return envelope?.v === 1 && envelope.exp > now() ? envelope : null;\n };\n\n return {\n create: (data) => seal({ v: 1, exp: now() + maxAge, data }),\n get: async (sid) => (await open(sid))?.data ?? null,\n async update(sid, patch) {\n const envelope = await open(sid);\n return envelope\n ? seal({ ...envelope, data: { ...envelope.data, ...patch } })\n : null;\n },\n // The session is the cookie, so clearing the cookie ends it.\n delete: async () => undefined,\n deleteBySub: async () =>\n refuse(\n \"A cookie session store can't end sessions from the server. Use a session store that keeps sessions on the server for back-channel logout.\",\n ),\n };\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAsEA,SAAS,mBACd,SACc;AACd,QAAM,UAAU,QAAQ,aAAa,KAAK,KAAK,KAAK,MAAM;AAC1D,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,UAAU,EAAE,QAAQ,QAAQ,QAAQ,SAAS,iBAAiB;AACpE,YAAU,QAAQ,MAAM;AACxB,MAAI,EAAE,SAAS,KAAK,OAAO,SAAS,MAAM,IAAI;AAC5C,UAAM,IAAI,UAAU,iDAAiD;AAAA,EACvE;AACA,QAAM,SAAS,CAAC,YAAoB;AAClC,UAAM,IAAI,YAAY,mBAAmB,OAAO;AAAA,EAClD;AACA,QAAM,OAAO,OAAO,aAAuB;AACzC,UAAM,SAAS,MAAM,UAAU,UAAU,OAAO;AAEhD,WAAO,OAAO,SAAS,OACnB;AAAA,MACE;AAAA,IACF,IACA;AAAA,EACN;AACA,QAAM,OAAO,OAAO,QAAgB;AAClC,UAAM,WAAW,MAAM,gBAA0B,KAAK,OAAO;AAC7D,WAAO,UAAU,MAAM,KAAK,SAAS,MAAM,IAAI,IAAI,WAAW;AAAA,EAChE;AAEA,SAAO;AAAA,IACL,QAAQ,CAAC,SAAS,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,IAAI,QAAQ,KAAK,CAAC;AAAA,IAC1D,KAAK,OAAO,SAAS,MAAM,KAAK,GAAG,IAAI,QAAQ;AAAA,IAC/C,MAAM,OAAO,KAAK,OAAO;AACvB,YAAM,WAAW,MAAM,KAAK,GAAG;AAC/B,aAAO,WACH,KAAK,EAAE,GAAG,UAAU,MAAM,EAAE,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,IAC1D;AAAA,IACN;AAAA;AAAA,IAEA,QAAQ,YAAY;AAAA,IACpB,aAAa,YACX;AAAA,MACE;AAAA,IACF;AAAA,EACJ;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/server/edge-snapshot.ts"],
4
- "sourcesContent": ["import type { Menu, Store } from \"../models\";\nimport { mapMenu } from \"../transport/mappers\";\n\n/**\n * A branch catalogue as the edge keeps it: the SDK `Menu` that `menu.get()`\n * returns, with the facts a snapshot writer and reader need around it.\n */\nexport type MenuSnapshot = {\n v: 1;\n storeId: string;\n branchId: string;\n /** The API's menu version, the same value `GET /menus/version` returns. */\n version: string;\n /** The first 16 hex digits of the SHA-256 of the menu's canonical JSON. */\n contentHash: string;\n /** When the snapshot was built, as an ISO time. */\n builtAt: string;\n /** The latest change among the source menus, as an ISO time, when the API sends it. */\n sourceUpdatedAt?: string;\n /** A complete branch catalogue. Partial category pages never become snapshots. */\n menu: Menu;\n};\n\n/** Store details as the edge keeps them: the SDK `Store` that `store.get()` returns. */\nexport type StoreSnapshot = {\n v: 1;\n storeId: string;\n /** The first 16 hex digits of the SHA-256 of the store's canonical JSON. */\n contentHash: string;\n /** When the snapshot was built, as an ISO time. */\n builtAt: string;\n store: Store;\n};\n\n/**\n * What sold out since a branch's menu snapshot was built. It is small, so an\n * availability change rewrites about 1 KB instead of the whole menu.\n */\nexport type AvailabilityPatch = {\n v: 1;\n /** The first 16 hex digits of the SHA-256 of this patch's canonical JSON. */\n version: string;\n /** The API's menu version after this change. A patched menu reports it. */\n menuVersion: string;\n soldOutItems: string[];\n soldOutVariants: string[];\n /** Choice ids in option groups. */\n soldOutOptions: string[];\n /** When the branch reopens, as an ISO time, if it is closed for now. */\n closedUntil?: string;\n /** When the patch was built, as an ISO time. */\n updatedAt: string;\n};\n\n/** The version record a writer saves last, so readers never see unwritten data. */\nexport type SnapshotVersionRecord = {\n /** The default branch's menu version. */\n version: string;\n builtAt: string;\n branches: Record<string, string>;\n};\n\n/** The edge key-value names for each snapshot. */\nexport const snapshotKeys = {\n /** `store:<storeId>` holds a StoreSnapshot. */\n store: (storeId: string) => `store:${storeId}`,\n /** `menu:<storeId>:<branchId>`, or `menu:<storeId>` for the default branch. */\n menu: (storeId: string, branchId?: string) =>\n branchId === undefined ? `menu:${storeId}` : `menu:${storeId}:${branchId}`,\n /** `avail:<storeId>:<branchId>` holds an AvailabilityPatch. */\n availability: (storeId: string, branchId: string) =>\n `avail:${storeId}:${branchId}`,\n /** `ver:<storeId>` holds a SnapshotVersionRecord. */\n version: (storeId: string) => `ver:${storeId}`,\n} as const;\n\nfunction canonical(value: unknown, seen: Set<object>): string {\n if (value === null || typeof value === \"boolean\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"string\") {\n return JSON.stringify(value);\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw new TypeError(\"Canonical JSON can't hold a non-finite number.\");\n }\n return JSON.stringify(value);\n }\n if (typeof value !== \"object\") {\n throw new TypeError(`Canonical JSON can't hold a ${typeof value}.`);\n }\n if (seen.has(value)) {\n throw new TypeError(\"Canonical JSON can't hold a cycle.\");\n }\n seen.add(value);\n let out: string;\n if (Array.isArray(value)) {\n out = `[${value\n .map((entry) => (entry === undefined ? \"null\" : canonical(entry, seen)))\n .join(\",\")}]`;\n } else {\n const prototype = Object.getPrototypeOf(value) as unknown;\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError(\"Canonical JSON holds plain objects only.\");\n }\n out = `{${Object.keys(value)\n .filter((key) => (value as Record<string, unknown>)[key] !== undefined)\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key], seen)}`,\n )\n .join(\",\")}}`;\n }\n seen.delete(value);\n return out;\n}\n\n/**\n * Serialise a JSON value with object keys in sorted order and undefined\n * properties left out, so equal values always give the same text.\n */\nexport function canonicalJson(value: unknown): string {\n return canonical(value, new Set());\n}\n\n/** Hash a JSON value: the first 16 hex digits of the SHA-256 of its canonical JSON. */\nexport async function snapshotHash(value: unknown): Promise<string> {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(canonicalJson(value)),\n );\n return Array.from(new Uint8Array(digest).slice(0, 8), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n\nconst isoTime = (value: Date | string | undefined) => {\n const time = value === undefined ? new Date() : new Date(value);\n if (Number.isNaN(time.getTime())) {\n throw new TypeError(\"Snapshot times must be valid dates.\");\n }\n return time.toISOString();\n};\n\n/** Input for building a MenuSnapshot from an SDK menu. */\nexport type MenuSnapshotInput = {\n storeId: string;\n menu: Menu;\n /** Defaults to now. */\n builtAt?: Date | string;\n sourceUpdatedAt?: Date | string;\n};\n\n/** Build a MenuSnapshot from a complete SDK menu. */\nexport async function createMenuSnapshot({\n storeId,\n menu,\n builtAt,\n sourceUpdatedAt,\n}: MenuSnapshotInput): Promise<MenuSnapshot> {\n if (!storeId || !menu.branchId || !menu.version) {\n throw new TypeError(\n \"A menu snapshot needs a store, a branch and a menu version.\",\n );\n }\n if (menu.completeness === \"partial\") {\n throw new TypeError(\"A partial menu can't become a snapshot.\");\n }\n return {\n v: 1,\n storeId,\n branchId: menu.branchId,\n version: menu.version,\n contentHash: await snapshotHash(menu),\n builtAt: isoTime(builtAt),\n ...(sourceUpdatedAt === undefined\n ? {}\n : { sourceUpdatedAt: isoTime(sourceUpdatedAt) }),\n menu,\n };\n}\n\n/** Input for building a MenuSnapshot from the API's menu payload. */\nexport type MenuSnapshotPayloadInput = {\n storeId: string;\n branchId: string;\n /** The JSON body of the API's branch menu read. */\n payload: unknown;\n /** Defaults to now. */\n builtAt?: Date | string;\n};\n\n/**\n * Build a MenuSnapshot from the API's branch menu payload. It maps the payload\n * with the same mapper `menu.get()` uses, so the snapshot's menu is exactly\n * what the client SDK returns.\n */\nexport async function menuSnapshotFromPayload({\n storeId,\n branchId,\n payload,\n builtAt,\n}: MenuSnapshotPayloadInput): Promise<MenuSnapshot> {\n const menu = mapMenu(payload, branchId);\n const updates = (\n Array.isArray((payload as { menus?: unknown } | null)?.menus)\n ? ((payload as { menus: unknown[] }).menus as { updated_at?: unknown }[])\n : []\n )\n .map((source) => source?.updated_at)\n .filter(\n (value): value is string =>\n typeof value === \"string\" && !Number.isNaN(Date.parse(value)),\n )\n .sort((left, right) => Date.parse(right) - Date.parse(left));\n return createMenuSnapshot({\n storeId,\n menu,\n ...(builtAt === undefined ? {} : { builtAt }),\n ...(updates[0] === undefined ? {} : { sourceUpdatedAt: updates[0] }),\n });\n}\n\n/** Build a StoreSnapshot from an SDK store. */\nexport async function createStoreSnapshot({\n store,\n builtAt,\n}: {\n store: Store;\n builtAt?: Date | string;\n}): Promise<StoreSnapshot> {\n if (!store.id) {\n throw new TypeError(\"A store snapshot needs a store id.\");\n }\n return {\n v: 1,\n storeId: store.id,\n contentHash: await snapshotHash(store),\n builtAt: isoTime(builtAt),\n store,\n };\n}\n\n/** Input for building an AvailabilityPatch. */\nexport type AvailabilityPatchInput = {\n menuVersion: string;\n soldOutItems?: readonly string[];\n soldOutVariants?: readonly string[];\n soldOutOptions?: readonly string[];\n closedUntil?: Date | string;\n /** Defaults to now. */\n updatedAt?: Date | string;\n};\n\nconst ids = (values: readonly string[] | undefined) =>\n [...new Set(values ?? [])].sort();\n\n/** Build an AvailabilityPatch with sorted, unique ids and its content hash. */\nexport async function createAvailabilityPatch(\n input: AvailabilityPatchInput,\n): Promise<AvailabilityPatch> {\n if (!input.menuVersion) {\n throw new TypeError(\"An availability patch needs the API's menu version.\");\n }\n const content = {\n v: 1 as const,\n menuVersion: input.menuVersion,\n soldOutItems: ids(input.soldOutItems),\n soldOutVariants: ids(input.soldOutVariants),\n soldOutOptions: ids(input.soldOutOptions),\n ...(input.closedUntil === undefined\n ? {}\n : { closedUntil: isoTime(input.closedUntil) }),\n };\n return {\n ...content,\n version: await snapshotHash(content),\n updatedAt: isoTime(input.updatedAt),\n };\n}\n\n/**\n * Return the snapshot's menu with an availability patch applied. A patch\n * built before the snapshot is ignored, because the rebuilt menu already\n * holds newer availability. The result reports the patch's menu version.\n */\nexport function applyAvailability(\n snapshot: MenuSnapshot,\n patch: AvailabilityPatch | null | undefined,\n): Menu {\n if (!patch || Date.parse(patch.updatedAt) < Date.parse(snapshot.builtAt)) {\n return snapshot.menu;\n }\n const items = new Set(patch.soldOutItems);\n const variants = new Set(patch.soldOutVariants);\n const options = new Set(patch.soldOutOptions);\n return {\n ...snapshot.menu,\n version: patch.menuVersion,\n items: Object.fromEntries(\n Object.entries(snapshot.menu.items).map(([id, item]) => {\n const touched = item.variants.some((variant) =>\n variants.has(variant.id),\n );\n const patched = item.variants.map((variant) =>\n variants.has(variant.id)\n ? { ...variant, isAvailable: false }\n : variant,\n ) as typeof item.variants;\n return [\n id,\n {\n ...item,\n // Selling out every variant sells out the item.\n isAvailable:\n item.isAvailable &&\n !items.has(id) &&\n (!touched || patched.some((variant) => variant.isAvailable)),\n variants: patched,\n optionGroups: item.optionGroups.map((group) => ({\n ...group,\n choices: group.choices.map((choice) =>\n options.has(choice.id)\n ? { ...choice, isAvailable: false }\n : choice,\n ),\n })),\n },\n ];\n }),\n ),\n };\n}\n\n/** Check that a value read from the edge is a MenuSnapshot this SDK can use. */\nexport function isMenuSnapshot(value: unknown): value is MenuSnapshot {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const snapshot = value as Partial<MenuSnapshot>;\n const menu = snapshot.menu as Partial<Menu> | undefined;\n return (\n snapshot.v === 1 &&\n typeof snapshot.storeId === \"string\" &&\n typeof snapshot.branchId === \"string\" &&\n typeof snapshot.version === \"string\" &&\n typeof snapshot.contentHash === \"string\" &&\n typeof snapshot.builtAt === \"string\" &&\n !!menu &&\n typeof menu === \"object\" &&\n menu.branchId === snapshot.branchId &&\n menu.version === snapshot.version &&\n menu.completeness !== \"partial\" &&\n Array.isArray(menu.categories) &&\n !!menu.items &&\n typeof menu.items === \"object\"\n );\n}\n"],
5
4
  "mappings": ";AACA,SAAS,eAAe;AA8DjB,IAAM,eAAe;AAAA;AAAA,EAE1B,OAAO,CAAC,YAAoB,SAAS,OAAO;AAAA;AAAA,EAE5C,MAAM,CAAC,SAAiB,aACtB,aAAa,SAAY,QAAQ,OAAO,KAAK,QAAQ,OAAO,IAAI,QAAQ;AAAA;AAAA,EAE1E,cAAc,CAAC,SAAiB,aAC9B,SAAS,OAAO,IAAI,QAAQ;AAAA;AAAA,EAE9B,SAAS,CAAC,YAAoB,OAAO,OAAO;AAC9C;AAEA,SAAS,UAAU,OAAgB,MAA2B;AAC5D,MAAI,UAAU,QAAQ,OAAO,UAAU,WAAW;AAChD,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,YAAM,IAAI,UAAU,gDAAgD;AAAA,IACtE;AACA,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,UAAU,+BAA+B,OAAO,KAAK,GAAG;AAAA,EACpE;AACA,MAAI,KAAK,IAAI,KAAK,GAAG;AACnB,UAAM,IAAI,UAAU,oCAAoC;AAAA,EAC1D;AACA,OAAK,IAAI,KAAK;AACd,MAAI;AACJ,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,IAAI,MACP,IAAI,CAAC,UAAW,UAAU,SAAY,SAAS,UAAU,OAAO,IAAI,CAAE,EACtE,KAAK,GAAG,CAAC;AAAA,EACd,OAAO;AACL,UAAM,YAAY,OAAO,eAAe,KAAK;AAC7C,QAAI,cAAc,OAAO,aAAa,cAAc,MAAM;AACxD,YAAM,IAAI,UAAU,0CAA0C;AAAA,IAChE;AACA,UAAM,IAAI,OAAO,KAAK,KAAK,EACxB,OAAO,CAAC,QAAS,MAAkC,GAAG,MAAM,MAAS,EACrE,KAAK,EACL;AAAA,MACC,CAAC,QACC,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,UAAW,MAAkC,GAAG,GAAG,IAAI,CAAC;AAAA,IACtF,EACC,KAAK,GAAG,CAAC;AAAA,EACd;AACA,OAAK,OAAO,KAAK;AACjB,SAAO;AACT;AAMO,SAAS,cAAc,OAAwB;AACpD,SAAO,UAAU,OAAO,oBAAI,IAAI,CAAC;AACnC;AAGA,eAAsB,aAAa,OAAiC;AAClE,QAAM,SAAS,MAAM,OAAO,OAAO;AAAA,IACjC;AAAA,IACA,IAAI,YAAY,EAAE,OAAO,cAAc,KAAK,CAAC;AAAA,EAC/C;AACA,SAAO,MAAM;AAAA,IAAK,IAAI,WAAW,MAAM,EAAE,MAAM,GAAG,CAAC;AAAA,IAAG,CAAC,SACrD,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EACnC,EAAE,KAAK,EAAE;AACX;AAEA,IAAM,UAAU,CAAC,UAAqC;AACpD,QAAM,OAAO,UAAU,SAAY,oBAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AAC9D,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChC,UAAM,IAAI,UAAU,qCAAqC;AAAA,EAC3D;AACA,SAAO,KAAK,YAAY;AAC1B;AAYA,eAAsB,mBAAmB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6C;AAC3C,MAAI,CAAC,WAAW,CAAC,KAAK,YAAY,CAAC,KAAK,SAAS;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAAK,iBAAiB,WAAW;AACnC,UAAM,IAAI,UAAU,yCAAyC;AAAA,EAC/D;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,UAAU,KAAK;AAAA,IACf,SAAS,KAAK;AAAA,IACd,aAAa,MAAM,aAAa,IAAI;AAAA,IACpC,SAAS,QAAQ,OAAO;AAAA,IACxB,GAAI,oBAAoB,SACpB,CAAC,IACD,EAAE,iBAAiB,QAAQ,eAAe,EAAE;AAAA,IAChD;AAAA,EACF;AACF;AAiBA,eAAsB,wBAAwB;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoD;AAClD,QAAM,OAAO,QAAQ,SAAS,QAAQ;AACtC,QAAM,WACJ,MAAM,QAAS,SAAwC,KAAK,IACtD,QAAiC,QACnC,CAAC,GAEJ,IAAI,CAAC,WAAW,QAAQ,UAAU,EAClC;AAAA,IACC,CAAC,UACC,OAAO,UAAU,YAAY,CAAC,OAAO,MAAM,KAAK,MAAM,KAAK,CAAC;AAAA,EAChE,EACC,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;AAC7D,SAAO,mBAAmB;AAAA,IACxB;AAAA,IACA;AAAA,IACA,GAAI,YAAY,SAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,IAC3C,GAAI,QAAQ,CAAC,MAAM,SAAY,CAAC,IAAI,EAAE,iBAAiB,QAAQ,CAAC,EAAE;AAAA,EACpE,CAAC;AACH;AAGA,eAAsB,oBAAoB;AAAA,EACxC;AAAA,EACA;AACF,GAG2B;AACzB,MAAI,CAAC,MAAM,IAAI;AACb,UAAM,IAAI,UAAU,oCAAoC;AAAA,EAC1D;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,MAAM;AAAA,IACf,aAAa,MAAM,aAAa,KAAK;AAAA,IACrC,SAAS,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF;AACF;AAaA,IAAM,MAAM,CAAC,WACX,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK;AAGlC,eAAsB,wBACpB,OAC4B;AAC5B,MAAI,CAAC,MAAM,aAAa;AACtB,UAAM,IAAI,UAAU,qDAAqD;AAAA,EAC3E;AACA,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,aAAa,MAAM;AAAA,IACnB,cAAc,IAAI,MAAM,YAAY;AAAA,IACpC,iBAAiB,IAAI,MAAM,eAAe;AAAA,IAC1C,gBAAgB,IAAI,MAAM,cAAc;AAAA,IACxC,GAAI,MAAM,gBAAgB,SACtB,CAAC,IACD,EAAE,aAAa,QAAQ,MAAM,WAAW,EAAE;AAAA,EAChD;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,MAAM,aAAa,OAAO;AAAA,IACnC,WAAW,QAAQ,MAAM,SAAS;AAAA,EACpC;AACF;AAOO,SAAS,kBACd,UACA,OACM;AACN,MAAI,CAAC,SAAS,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,OAAO,GAAG;AACxE,WAAO,SAAS;AAAA,EAClB;AACA,QAAM,QAAQ,IAAI,IAAI,MAAM,YAAY;AACxC,QAAM,WAAW,IAAI,IAAI,MAAM,eAAe;AAC9C,QAAM,UAAU,IAAI,IAAI,MAAM,cAAc;AAC5C,SAAO;AAAA,IACL,GAAG,SAAS;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,OAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,SAAS,KAAK,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM;AACtD,cAAM,UAAU,KAAK,SAAS;AAAA,UAAK,CAAC,YAClC,SAAS,IAAI,QAAQ,EAAE;AAAA,QACzB;AACA,cAAM,UAAU,KAAK,SAAS;AAAA,UAAI,CAAC,YACjC,SAAS,IAAI,QAAQ,EAAE,IACnB,EAAE,GAAG,SAAS,aAAa,MAAM,IACjC;AAAA,QACN;AACA,eAAO;AAAA,UACL;AAAA,UACA;AAAA,YACE,GAAG;AAAA;AAAA,YAEH,aACE,KAAK,eACL,CAAC,MAAM,IAAI,EAAE,MACZ,CAAC,WAAW,QAAQ,KAAK,CAAC,YAAY,QAAQ,WAAW;AAAA,YAC5D,UAAU;AAAA,YACV,cAAc,KAAK,aAAa,IAAI,CAAC,WAAW;AAAA,cAC9C,GAAG;AAAA,cACH,SAAS,MAAM,QAAQ;AAAA,gBAAI,CAAC,WAC1B,QAAQ,IAAI,OAAO,EAAE,IACjB,EAAE,GAAG,QAAQ,aAAa,MAAM,IAChC;AAAA,cACN;AAAA,YACF,EAAE;AAAA,UACJ;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAGO,SAAS,eAAe,OAAuC;AACpE,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,WAAO;AAAA,EACT;AACA,QAAM,WAAW;AACjB,QAAM,OAAO,SAAS;AACtB,SACE,SAAS,MAAM,KACf,OAAO,SAAS,YAAY,YAC5B,OAAO,SAAS,aAAa,YAC7B,OAAO,SAAS,YAAY,YAC5B,OAAO,SAAS,gBAAgB,YAChC,OAAO,SAAS,YAAY,YAC5B,CAAC,CAAC,QACF,OAAO,SAAS,YAChB,KAAK,aAAa,SAAS,YAC3B,KAAK,YAAY,SAAS,WAC1B,KAAK,iBAAiB,aACtB,MAAM,QAAQ,KAAK,UAAU,KAC7B,CAAC,CAAC,KAAK,SACP,OAAO,KAAK,UAAU;AAE1B;",
6
5
  "names": []
7
6
  }
@@ -1,5 +1,5 @@
1
1
  import { Menu, Store, Formatters } from '../index.js';
2
- import { C as Cache } from '../client-B377AzNC.js';
2
+ import { C as Cache } from '../client-DHxakZrv.js';
3
3
  import { ZappieError, ZappieConfig, Transport, StorageLike, StoreResource, MenuResource, ZappieEvents } from '../index.js';
4
4
  export { VERSION } from '../index.js';
5
5
  import { CartModule, CartLineInput } from '../index.js';
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/server/index.ts"],
4
- "sourcesContent": ["import { buildClient } from \"../client\";\nimport type { ZappieConfig } from \"../config\";\nimport type { Menu } from \"../models\";\nexport { VERSION } from \"../index\";\nexport {\n buildStorefrontUrl,\n parseStorefrontUrl,\n STOREFRONT_QUERY_PARAMETERS,\n STOREFRONT_V1_QUERY_PARAMETERS,\n storefrontRedirect,\n type StorefrontInstallation,\n type StorefrontPageState,\n type StorefrontQuery,\n type StorefrontRoute,\n type StorefrontUrlInput,\n type StorefrontUrlOptions,\n} from \"../url/index\";\nexport {\n createPkce,\n openSealedValue,\n randomToken,\n sealValue,\n type PkcePair,\n type SealOptions,\n type SealSecret,\n} from \"./auth/crypto\";\nexport {\n createTokenVerifier,\n ZappieTokenError,\n type IdTokenChecks,\n type IdTokenClaims,\n type TokenAlgorithm,\n type TokenClaims,\n type TokenErrorReason,\n type TokenVerifier,\n type TokenVerifierOptions,\n type VerifyTokenChecks,\n} from \"./auth/jwt\";\nexport {\n cookieSessionStore,\n type CookieSessionStoreOptions,\n type SessionData,\n type SessionStore,\n} from \"./auth/session-store\";\nexport {\n applyAvailability,\n canonicalJson,\n createAvailabilityPatch,\n createMenuSnapshot,\n createStoreSnapshot,\n isMenuSnapshot,\n menuSnapshotFromPayload,\n snapshotHash,\n snapshotKeys,\n type AvailabilityPatch,\n type AvailabilityPatchInput,\n type MenuSnapshot,\n type MenuSnapshotInput,\n type MenuSnapshotPayloadInput,\n type SnapshotVersionRecord,\n type StoreSnapshot,\n} from \"./edge-snapshot\";\nexport {\n createSecurityNonce,\n inlineScriptHashes,\n securityHeaders,\n type SecurityHeadersOptions,\n} from \"./security\";\nexport {\n verifyWebhook,\n ZappieWebhookSignatureError,\n type VerifyWebhookOptions,\n type WebhookEvent,\n type WebhookEventType,\n} from \"./webhook\";\n/** An edge reader that returns a Menu snapshot or null. */\nexport interface EdgeSnapshotReader {\n getMenu(storeId: string, branchId: string): Promise<Menu | null>;\n}\n/** Settings for your server client. Keep secret keys out of browser bundles. */\nexport type ZappieServerConfig = Omit<ZappieConfig, \"key\"> & {\n secretKey: string;\n};\n/** Create a server client with your secret key and configured transport. */\nexport function createZappieServer({\n secretKey,\n ...config\n}: ZappieServerConfig) {\n return buildClient({ ...config, key: secretKey }, true);\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,mBAAmB;AAG5B,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OASK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAUA,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,SAAO,YAAY,EAAE,GAAG,QAAQ,KAAK,UAAU,GAAG,IAAI;AACxD;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/server/security.ts"],
4
- "sourcesContent": ["import { hostname, record } from \"../site-config/helpers\";\nimport type { ZappieSiteConfig } from \"../site-config/types\";\n\nconst DEFAULT_SOURCES = {\n \"img-src\": [\"'self'\", \"data:\", \"https:\"],\n \"connect-src\": [\"'self'\"],\n \"font-src\": [\"'self'\", \"https://fonts.gstatic.com\"],\n \"style-src\": [\"'self'\", \"'unsafe-inline'\", \"https://fonts.googleapis.com\"],\n \"frame-src\": [\n \"https://checkout.zappie.ai\",\n \"https://elements.zappie.ai\",\n \"https://accounts.zappie.ai\",\n ],\n} as const;\n\n/** Inputs for a storefront document's report-only security headers. */\nexport type SecurityHeadersOptions = {\n /** A fresh value from createSecurityNonce for a server-rendered document. */\n nonce?: string;\n /** SHA-256 CSP source expressions for inline scripts in a prerendered document. */\n scriptHashes?: string[];\n /** The site configuration. Invalid CSP additions are ignored. */\n config?: ZappieSiteConfig;\n /** The configured ZAPPIE_API URL whose origin the browser may call. */\n apiUrl?: string;\n /** Existing response headers to extend. */\n headers?: HeadersInit;\n /** Hosted responses leave framing to Zappie's dispatch Worker. */\n hosted?: boolean;\n};\n\nconst sourceNames = {\n imgSrc: \"img-src\",\n connectSrc: \"connect-src\",\n fontSrc: \"font-src\",\n styleSrc: \"style-src\",\n frameSrc: \"frame-src\",\n} as const;\n\nconst base64 = (bytes: Uint8Array) => btoa(String.fromCharCode(...bytes));\n\n/** Create an unpredictable CSP nonce with Web Crypto in Node or a Worker. */\nexport function createSecurityNonce(): string {\n return base64(crypto.getRandomValues(new Uint8Array(18)))\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n}\n\n/** Compute CSP SHA-256 source expressions for inline scripts in prerendered HTML. */\nexport async function inlineScriptHashes(html: string): Promise<string[]> {\n const hashes = await Promise.all(\n [...html.matchAll(/<script\\b([^>]*)>([\\s\\S]*?)<\\/script>/gi)]\n .filter(([, attributes = \"\"]) => !/(?:^|\\s)src\\s*=/i.test(attributes))\n .map(async ([, , source = \"\"]) => {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(source),\n );\n return `'sha256-${base64(new Uint8Array(digest))}'`;\n }),\n );\n return [...new Set(hashes)];\n}\n\nfunction apiOrigin(value: string | undefined) {\n if (value === undefined) {\n return \"https://storefront-api.zappie.ai\";\n }\n try {\n const url = new URL(value);\n if (\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" &&\n [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname))\n ) {\n return url.origin;\n }\n } catch {\n return undefined;\n }\n return undefined;\n}\n\nfunction configuredSources(config: ZappieSiteConfig | undefined) {\n const configured = new Map<string, string[]>();\n const csp = config?.security?.csp as unknown;\n if (!record(csp)) {\n return configured;\n }\n for (const [name, values] of Object.entries(csp)) {\n const directive = sourceNames[name as keyof typeof sourceNames];\n if (!directive || !Array.isArray(values)) {\n continue;\n }\n const valid = values.filter(\n (value): value is string =>\n typeof value === \"string\" && !/[;,\\s]/.test(value) && hostname(value),\n );\n configured.set(\n directive,\n valid.map((host) => `https://${host}`),\n );\n }\n return configured;\n}\n\n/** Build the report-only security headers for a storefront document response. */\nexport function securityHeaders(options: SecurityHeadersOptions = {}): Headers {\n if (!options.nonce && !options.scriptHashes?.length) {\n throw new TypeError(\n \"securityHeaders needs a nonce or an inline script hash.\",\n );\n }\n const sources = Object.fromEntries(\n Object.entries(DEFAULT_SOURCES).map(([name, values]) => [\n name,\n [...values],\n ]),\n ) as Record<(typeof sourceNames)[keyof typeof sourceNames], string[]>;\n const storefrontApiOrigin = apiOrigin(options.apiUrl);\n if (storefrontApiOrigin) {\n sources[\"connect-src\"].push(storefrontApiOrigin);\n }\n for (const [name, values] of configuredSources(options.config)) {\n sources[name as keyof typeof sources].push(...values);\n }\n const outbound = options.config?.outbound?.allow as unknown;\n if (Array.isArray(outbound)) {\n sources[\"connect-src\"].push(\n ...outbound\n .filter(\n (host): host is string =>\n typeof host === \"string\" && !/[;,\\s]/.test(host) && hostname(host),\n )\n .map((host) => `https://${host}`),\n );\n }\n for (const values of Object.values(sources)) {\n values.splice(0, values.length, ...new Set(values));\n }\n const scriptSources = [\n \"'self'\",\n ...(options.nonce ? [`'nonce-${options.nonce}'`] : []),\n ...(options.scriptHashes ?? []),\n \"'strict-dynamic'\",\n ];\n const directives = [\n \"default-src 'self'\",\n `script-src ${scriptSources.join(\" \")}`,\n ...Object.entries(sources).map(\n ([name, values]) => `${name} ${values.join(\" \")}`,\n ),\n \"form-action 'self' https://accounts.zappie.ai https://checkout.zappie.ai\",\n \"base-uri 'self'\",\n \"object-src 'none'\",\n ...(!options.hosted ? [\"frame-ancestors 'self'\"] : []),\n \"report-uri /_zappie/csp\",\n ];\n const headers = new Headers(options.headers);\n headers.set(\"content-security-policy-report-only\", directives.join(\"; \"));\n headers.set(\"strict-transport-security\", \"max-age=31536000\");\n headers.set(\"x-content-type-options\", \"nosniff\");\n headers.set(\"referrer-policy\", \"strict-origin-when-cross-origin\");\n headers.set(\n \"permissions-policy\",\n 'camera=(), microphone=(), usb=(), geolocation=(self), payment=(self \"https://checkout.zappie.ai\")',\n );\n return headers;\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/build-v1.ts"],
4
- "sourcesContent": ["import { menuRoute } from \"./contract\";\nimport { publicInstallationPath } from \"./installation\";\nimport { v1Params } from \"./params\";\nimport { type StorefrontRoute, type StorefrontUrlOptions } from \"./types\";\nimport { encode } from \"./url\";\n\nexport const routePathV1 = (\n route: StorefrontRoute,\n options: StorefrontUrlOptions = {},\n): string => {\n switch (route.type) {\n case \"order-menu\":\n return `/order/${encode(route.business)}${route.branch ? `/${encode(route.branch)}` : \"\"}${route.cataloguePath ? \"/products\" : \"\"}`;\n case \"item\": {\n const branch = route.branch ?? options.installation?.defaultBranch;\n return branch\n ? `/order/${encode(route.business)}/${encode(branch)}/products/${encode(route.item)}`\n : `/order/${encode(route.business)}`;\n }\n case \"cart\":\n case \"checkout\":\n case \"checkout-return\":\n case \"sign-in\":\n return routePathV1(menuRoute(route), options);\n case \"content-page\":\n return `/order/${encode(route.business)}${route.branch ? `/${encode(route.branch)}` : \"\"}/pages/${encode(route.slug)}`;\n case \"dine-in\":\n return `/dine-in/${encode(route.order)}`;\n case \"pay-point\":\n return `/pay-point/${encode(route.payPoint)}`;\n case \"beacon\":\n return `/beacons/${encode(route.beacon)}`;\n case \"link\":\n return `/links/${encode(route.link)}`;\n case \"pay-session\":\n return `/pay/${encode(route.session)}`;\n case \"payment-step\":\n return `/payment/${encode(route.payment)}${route.step === \"payment\" ? \"\" : `/${route.step}`}`;\n case \"payment-card\":\n return `/payment/card/${encode(route.transaction)}`;\n case \"profile\":\n return `/profile/${route.page}${route.order ? `/${encode(route.order)}` : \"\"}`;\n case \"explore\":\n return route.home ? \"/\" : \"/explore\";\n case \"unknown\":\n return route.pathname || \"/\";\n case \"dine-in-menus\":\n return \"/dine-in/menus\";\n case \"self-service-menus\":\n return \"/self-service/menus\";\n case \"collect-payment\":\n case \"reservation\":\n return `/${route.type}`;\n }\n};\n\nexport const buildV1 = (\n route: StorefrontRoute,\n options: StorefrontUrlOptions = {},\n) => {\n const internal = routePathV1(route, options);\n const publicRoute =\n route.type === \"item\" &&\n route.branch === undefined &&\n options.installation?.defaultBranch\n ? { ...route, branch: options.installation.defaultBranch }\n : route;\n const pathname = publicInstallationPath(\n internal,\n publicRoute,\n options.installation,\n );\n const query = v1Params(route).toString();\n return `${pathname}${query ? `?${query}` : \"\"}`;\n};\n"],
5
4
  "mappings": ";AAAA,SAAS,iBAAiB;AAC1B,SAAS,8BAA8B;AACvC,SAAS,gBAAgB;AACzB,OAAgE;AAChE,SAAS,cAAc;AAEhB,IAAM,cAAc,CACzB,OACA,UAAgC,CAAC,MACtB;AACX,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,UAAU,OAAO,MAAM,QAAQ,CAAC,GAAG,MAAM,SAAS,IAAI,OAAO,MAAM,MAAM,CAAC,KAAK,EAAE,GAAG,MAAM,gBAAgB,cAAc,EAAE;AAAA,IACnI,KAAK,QAAQ;AACX,YAAM,SAAS,MAAM,UAAU,QAAQ,cAAc;AACrD,aAAO,SACH,UAAU,OAAO,MAAM,QAAQ,CAAC,IAAI,OAAO,MAAM,CAAC,aAAa,OAAO,MAAM,IAAI,CAAC,KACjF,UAAU,OAAO,MAAM,QAAQ,CAAC;AAAA,IACtC;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,YAAY,UAAU,KAAK,GAAG,OAAO;AAAA,IAC9C,KAAK;AACH,aAAO,UAAU,OAAO,MAAM,QAAQ,CAAC,GAAG,MAAM,SAAS,IAAI,OAAO,MAAM,MAAM,CAAC,KAAK,EAAE,UAAU,OAAO,MAAM,IAAI,CAAC;AAAA,IACtH,KAAK;AACH,aAAO,YAAY,OAAO,MAAM,KAAK,CAAC;AAAA,IACxC,KAAK;AACH,aAAO,cAAc,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC7C,KAAK;AACH,aAAO,YAAY,OAAO,MAAM,MAAM,CAAC;AAAA,IACzC,KAAK;AACH,aAAO,UAAU,OAAO,MAAM,IAAI,CAAC;AAAA,IACrC,KAAK;AACH,aAAO,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACtC,KAAK;AACH,aAAO,YAAY,OAAO,MAAM,OAAO,CAAC,GAAG,MAAM,SAAS,YAAY,KAAK,IAAI,MAAM,IAAI,EAAE;AAAA,IAC7F,KAAK;AACH,aAAO,iBAAiB,OAAO,MAAM,WAAW,CAAC;AAAA,IACnD,KAAK;AACH,aAAO,YAAY,MAAM,IAAI,GAAG,MAAM,QAAQ,IAAI,OAAO,MAAM,KAAK,CAAC,KAAK,EAAE;AAAA,IAC9E,KAAK;AACH,aAAO,MAAM,OAAO,MAAM;AAAA,IAC5B,KAAK;AACH,aAAO,MAAM,YAAY;AAAA,IAC3B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO,IAAI,MAAM,IAAI;AAAA,EACzB;AACF;AAEO,IAAM,UAAU,CACrB,OACA,UAAgC,CAAC,MAC9B;AACH,QAAM,WAAW,YAAY,OAAO,OAAO;AAC3C,QAAM,cACJ,MAAM,SAAS,UACf,MAAM,WAAW,UACjB,QAAQ,cAAc,gBAClB,EAAE,GAAG,OAAO,QAAQ,QAAQ,aAAa,cAAc,IACvD;AACN,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV;AACA,QAAM,QAAQ,SAAS,KAAK,EAAE,SAAS;AACvC,SAAO,GAAG,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAC/C;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/build-v2.ts"],
4
- "sourcesContent": ["import { buildV1 } from \"./build-v1\";\nimport { canonicalRoute } from \"./canonical\";\nimport { v2Params } from \"./params\";\nimport { categoryHash, publicId } from \"./public-id\";\nimport type {\n StorefrontInstallation,\n StorefrontRoute,\n StorefrontUrlOptions,\n} from \"./types\";\nimport { encode } from \"./url\";\n\nconst profilePath = (route: Extract<StorefrontRoute, { type: \"profile\" }>) => {\n if (route.page === \"orders\") {\n return `/orders${route.order ? `/${encode(publicId(route.order, \"ord\"))}` : \"\"}`;\n }\n if (route.page === \"edit\") {\n return \"/account\";\n }\n const page = route.page === \"add-store-credit\" ? \"store-credit\" : route.page;\n return `/account/${page}`;\n};\n\nconst routePathV2 = (\n route: Extract<StorefrontRoute, { business: string } | { type: \"profile\" }>,\n) => {\n switch (route.type) {\n case \"order-menu\":\n return \"/\";\n case \"item\":\n return `/item/${encode(publicId(route.item, \"item\"))}`;\n case \"cart\":\n return \"/cart\";\n case \"checkout\":\n return \"/checkout\";\n case \"checkout-return\":\n return \"/checkout/return\";\n case \"sign-in\":\n return \"/sign-in\";\n case \"content-page\":\n return `/pages/${encode(route.slug)}`;\n case \"profile\":\n return profilePath(route);\n }\n};\n\nexport const buildV2 = (\n route: StorefrontRoute,\n options: StorefrontUrlOptions & { installation: StorefrontInstallation },\n) => {\n const installation = options.installation;\n const canonical = canonicalRoute(route, installation);\n if (!(\"business\" in canonical) && canonical.type !== \"profile\") {\n return buildV1(canonical, options);\n }\n const pathname = routePathV2(canonical);\n const query = v2Params(canonical, installation).toString();\n const hash = categoryHash(canonical.state.categoryId);\n return `${pathname}${query ? `?${query}` : \"\"}${hash}`;\n};\n"],
5
4
  "mappings": ";AAAA,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,cAAc,gBAAgB;AAMvC,SAAS,cAAc;AAEvB,IAAM,cAAc,CAAC,UAAyD;AAC5E,MAAI,MAAM,SAAS,UAAU;AAC3B,WAAO,UAAU,MAAM,QAAQ,IAAI,OAAO,SAAS,MAAM,OAAO,KAAK,CAAC,CAAC,KAAK,EAAE;AAAA,EAChF;AACA,MAAI,MAAM,SAAS,QAAQ;AACzB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,MAAM,SAAS,qBAAqB,iBAAiB,MAAM;AACxE,SAAO,YAAY,IAAI;AACzB;AAEA,IAAM,cAAc,CAClB,UACG;AACH,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,SAAS,OAAO,SAAS,MAAM,MAAM,MAAM,CAAC,CAAC;AAAA,IACtD,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,UAAU,OAAO,MAAM,IAAI,CAAC;AAAA,IACrC,KAAK;AACH,aAAO,YAAY,KAAK;AAAA,EAC5B;AACF;AAEO,IAAM,UAAU,CACrB,OACA,YACG;AACH,QAAM,eAAe,QAAQ;AAC7B,QAAM,YAAY,eAAe,OAAO,YAAY;AACpD,MAAI,EAAE,cAAc,cAAc,UAAU,SAAS,WAAW;AAC9D,WAAO,QAAQ,WAAW,OAAO;AAAA,EACnC;AACA,QAAM,WAAW,YAAY,SAAS;AACtC,QAAM,QAAQ,SAAS,WAAW,YAAY,EAAE,SAAS;AACzD,QAAM,OAAO,aAAa,UAAU,MAAM,UAAU;AACpD,SAAO,GAAG,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE,GAAG,IAAI;AACtD;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/build.ts"],
4
- "sourcesContent": ["import { buildV1 } from \"./build-v1\";\nimport { buildV2 } from \"./build-v2\";\nimport { usesV2 } from \"./contract\";\nimport type { StorefrontRoute, StorefrontUrlOptions } from \"./types\";\n\n/**\n * Build a storefront URL from a route and its page state.\n * Uses v2 when installation metadata is present unless you select v1 explicitly.\n *\n * @param route - The destination and UI state to encode.\n * @param options - Contract, installation and optional origin for an absolute URL.\n * @returns The built path, or an absolute URL when the origin resolves successfully.\n */\nexport const buildStorefrontUrl = (\n route: StorefrontRoute,\n options: StorefrontUrlOptions = {},\n) => {\n const value = usesV2(options)\n ? buildV2(route, options)\n : buildV1(route, options);\n if (!options.origin) {\n return value;\n }\n try {\n return new URL(value, options.origin).toString();\n } catch {\n return value;\n }\n};\n"],
5
4
  "mappings": ";AAAA,SAAS,eAAe;AACxB,SAAS,eAAe;AACxB,SAAS,cAAc;AAWhB,IAAM,qBAAqB,CAChC,OACA,UAAgC,CAAC,MAC9B;AACH,QAAM,QAAQ,OAAO,OAAO,IACxB,QAAQ,OAAO,OAAO,IACtB,QAAQ,OAAO,OAAO;AAC1B,MAAI,CAAC,QAAQ,QAAQ;AACnB,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,IAAI,IAAI,OAAO,QAAQ,MAAM,EAAE,SAAS;AAAA,EACjD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/canonical.ts"],
4
- "sourcesContent": ["import { CART_MODAL, CHECKOUT_MODAL, SIGN_IN_MODALS } from \"./contract\";\nimport { signInFromOverlay } from \"./sign-in\";\nimport type { StorefrontRoute, StorefrontUrlOptions } from \"./types\";\nimport { ITEM_MODAL_PREFIX } from \"./url\";\n\nexport const canonicalRoute = (\n route: StorefrontRoute,\n installation: NonNullable<StorefrontUrlOptions[\"installation\"]>,\n): StorefrontRoute => {\n if (route.type !== \"order-menu\") {\n return route;\n }\n const { authModalId, modalId } = route.state;\n if (authModalId && SIGN_IN_MODALS.includes(authModalId)) {\n return signInFromOverlay(route, installation);\n }\n const common = {\n business: route.business,\n ...(route.branch !== undefined ? { branch: route.branch } : {}),\n state: route.state,\n };\n if (modalId?.startsWith(ITEM_MODAL_PREFIX)) {\n return {\n type: \"item\",\n ...common,\n item: modalId.slice(ITEM_MODAL_PREFIX.length),\n };\n }\n if (modalId === CART_MODAL || modalId === CHECKOUT_MODAL) {\n return {\n type: modalId === CART_MODAL ? \"cart\" : \"checkout\",\n ...common,\n };\n }\n return route;\n};\n"],
5
4
  "mappings": ";AAAA,SAAS,YAAY,gBAAgB,sBAAsB;AAC3D,SAAS,yBAAyB;AAElC,SAAS,yBAAyB;AAE3B,IAAM,iBAAiB,CAC5B,OACA,iBACoB;AACpB,MAAI,MAAM,SAAS,cAAc;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,EAAE,aAAa,QAAQ,IAAI,MAAM;AACvC,MAAI,eAAe,eAAe,SAAS,WAAW,GAAG;AACvD,WAAO,kBAAkB,OAAO,YAAY;AAAA,EAC9C;AACA,QAAM,SAAS;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,IAC7D,OAAO,MAAM;AAAA,EACf;AACA,MAAI,SAAS,WAAW,iBAAiB,GAAG;AAC1C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,GAAG;AAAA,MACH,MAAM,QAAQ,MAAM,kBAAkB,MAAM;AAAA,IAC9C;AAAA,EACF;AACA,MAAI,YAAY,cAAc,YAAY,gBAAgB;AACxD,WAAO;AAAA,MACL,MAAM,YAAY,aAAa,SAAS;AAAA,MACxC,GAAG;AAAA,IACL;AAAA,EACF;AACA,SAAO;AACT;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/contract.ts"],
4
- "sourcesContent": ["import type {\n StorefrontInstallation,\n StorefrontRoute,\n StorefrontUrlOptions,\n} from \"./types\";\n\nexport const CART_MODAL = \"review-menu\";\nexport const CHECKOUT_MODAL = \"payment\";\nexport const SIGN_IN_MODAL = \"send-otp\";\nexport const SIGN_IN_MODALS: readonly string[] = [\n SIGN_IN_MODAL,\n \"verify-otp\",\n \"update-profile\",\n];\n\nexport const usesV2 = (\n options: StorefrontUrlOptions,\n): options is StorefrontUrlOptions & { installation: StorefrontInstallation } =>\n options.contract !== \"v1\" && options.installation !== undefined;\n\nexport const menuRoute = (\n route: Extract<\n StorefrontRoute,\n { type: \"cart\" | \"checkout\" | \"checkout-return\" | \"sign-in\" }\n >,\n): Extract<StorefrontRoute, { type: \"order-menu\" }> => ({\n type: \"order-menu\",\n business: route.business,\n ...(route.branch !== undefined ? { branch: route.branch } : {}),\n state: route.state,\n});\n"],
5
4
  "mappings": ";AAMO,IAAM,aAAa;AACnB,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AACtB,IAAM,iBAAoC;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,SAAS,CACpB,YAEA,QAAQ,aAAa,QAAQ,QAAQ,iBAAiB;AAEjD,IAAM,YAAY,CACvB,WAIsD;AAAA,EACtD,MAAM;AAAA,EACN,UAAU,MAAM;AAAA,EAChB,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,EAC7D,OAAO,MAAM;AACf;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/index.ts"],
4
- "sourcesContent": ["export { buildStorefrontUrl } from \"./build\";\nexport { parseStorefrontUrl } from \"./parse\";\nexport { storefrontRedirect } from \"./redirect\";\nexport {\n STOREFRONT_QUERY_PARAMETERS,\n STOREFRONT_V1_QUERY_PARAMETERS,\n type StorefrontInstallation,\n type StorefrontPageState,\n type StorefrontQuery,\n type StorefrontRoute,\n type StorefrontUrlInput,\n type StorefrontUrlOptions,\n} from \"./types\";\n"],
5
4
  "mappings": ";AAAA,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,OAOK;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/installation.ts"],
4
- "sourcesContent": ["import { type StorefrontInstallation, type StorefrontRoute } from \"./types\";\nimport { decode, encode } from \"./url\";\n\nexport const normalizedPath = (pathname: string) => {\n const trimmed = pathname.replace(/\\/+$/, \"\");\n return trimmed || \"/\";\n};\n\nexport const installationPath = (\n pathname: string,\n installation?: StorefrontInstallation,\n) => {\n if (!installation) {\n return pathname;\n }\n const business = `/order/${encode(installation.business)}`;\n if (pathname === \"/\") {\n return business;\n }\n if (pathname === \"/order\") {\n return installation.defaultBranch\n ? `${business}/${encode(installation.defaultBranch)}`\n : business;\n }\n if (pathname.startsWith(\"/pages/\")) {\n return `${business}${pathname}`;\n }\n if (\n !pathname.startsWith(\"/order/\") ||\n pathname === business ||\n pathname.startsWith(`${business}/`)\n ) {\n return pathname;\n }\n const suffix = pathname.slice(\"/order\".length);\n if (\n installation.defaultBranch &&\n (suffix === \"/products\" ||\n suffix.startsWith(\"/products/\") ||\n suffix.startsWith(\"/pages/\"))\n ) {\n return `${business}/${encode(installation.defaultBranch)}${suffix}`;\n }\n return `${business}${suffix}`;\n};\n\nexport const publicInstallationPath = (\n pathname: string,\n route: StorefrontRoute,\n installation?: StorefrontInstallation,\n) => {\n if (!installation || !(\"business\" in route)) {\n return pathname;\n }\n if (route.business !== installation.business) {\n return pathname;\n }\n if (route.type === \"content-page\" && !route.branch) {\n return `/pages/${encode(route.slug)}`;\n }\n if (route.type === \"order-menu\" && !route.branch) {\n return \"/\";\n }\n const branchPrefix = `/order/${encode(route.business)}/${encode(route.branch ?? \"\")}`;\n const suffix = pathname.slice(branchPrefix.length);\n if (route.branch === installation.defaultBranch) {\n return `/order${suffix}`;\n }\n return `/order/${encode(route.branch ?? \"\")}${suffix}`;\n};\n\nexport const segment = (parts: string[], index: number) =>\n decode(parts[index] ?? \"\");\n"],
5
4
  "mappings": ";AAAA,OAAkE;AAClE,SAAS,QAAQ,cAAc;AAExB,IAAM,iBAAiB,CAAC,aAAqB;AAClD,QAAM,UAAU,SAAS,QAAQ,QAAQ,EAAE;AAC3C,SAAO,WAAW;AACpB;AAEO,IAAM,mBAAmB,CAC9B,UACA,iBACG;AACH,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,UAAU,OAAO,aAAa,QAAQ,CAAC;AACxD,MAAI,aAAa,KAAK;AACpB,WAAO;AAAA,EACT;AACA,MAAI,aAAa,UAAU;AACzB,WAAO,aAAa,gBAChB,GAAG,QAAQ,IAAI,OAAO,aAAa,aAAa,CAAC,KACjD;AAAA,EACN;AACA,MAAI,SAAS,WAAW,SAAS,GAAG;AAClC,WAAO,GAAG,QAAQ,GAAG,QAAQ;AAAA,EAC/B;AACA,MACE,CAAC,SAAS,WAAW,SAAS,KAC9B,aAAa,YACb,SAAS,WAAW,GAAG,QAAQ,GAAG,GAClC;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,SAAS,MAAM,SAAS,MAAM;AAC7C,MACE,aAAa,kBACZ,WAAW,eACV,OAAO,WAAW,YAAY,KAC9B,OAAO,WAAW,SAAS,IAC7B;AACA,WAAO,GAAG,QAAQ,IAAI,OAAO,aAAa,aAAa,CAAC,GAAG,MAAM;AAAA,EACnE;AACA,SAAO,GAAG,QAAQ,GAAG,MAAM;AAC7B;AAEO,IAAM,yBAAyB,CACpC,UACA,OACA,iBACG;AACH,MAAI,CAAC,gBAAgB,EAAE,cAAc,QAAQ;AAC3C,WAAO;AAAA,EACT;AACA,MAAI,MAAM,aAAa,aAAa,UAAU;AAC5C,WAAO;AAAA,EACT;AACA,MAAI,MAAM,SAAS,kBAAkB,CAAC,MAAM,QAAQ;AAClD,WAAO,UAAU,OAAO,MAAM,IAAI,CAAC;AAAA,EACrC;AACA,MAAI,MAAM,SAAS,gBAAgB,CAAC,MAAM,QAAQ;AAChD,WAAO;AAAA,EACT;AACA,QAAM,eAAe,UAAU,OAAO,MAAM,QAAQ,CAAC,IAAI,OAAO,MAAM,UAAU,EAAE,CAAC;AACnF,QAAM,SAAS,SAAS,MAAM,aAAa,MAAM;AACjD,MAAI,MAAM,WAAW,aAAa,eAAe;AAC/C,WAAO,SAAS,MAAM;AAAA,EACxB;AACA,SAAO,UAAU,OAAO,MAAM,UAAU,EAAE,CAAC,GAAG,MAAM;AACtD;AAEO,IAAM,UAAU,CAAC,OAAiB,UACvC,OAAO,MAAM,KAAK,KAAK,EAAE;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/params.ts"],
4
- "sourcesContent": ["import { CART_MODAL, CHECKOUT_MODAL, SIGN_IN_MODAL } from \"./contract\";\nimport { publicId } from \"./public-id\";\nimport type { StorefrontInstallation, StorefrontRoute } from \"./types\";\nimport { STATE_FIELDS } from \"./url\";\n\nconst stateParams = (route: StorefrontRoute) => {\n const params = new URLSearchParams(route.state.query);\n const state = route.state;\n for (const [key, field] of Object.entries(STATE_FIELDS)) {\n const value = state[field];\n if (value !== undefined) {\n params.set(key, value);\n }\n }\n if (state.mode) {\n params.set(\"pickup\", String(state.mode === \"pickup\"));\n }\n return params;\n};\n\nexport const v1Params = (route: StorefrontRoute) => {\n const params = stateParams(route);\n if (route.type === \"item\") {\n params.delete(\"modal_id\");\n } else if (route.type === \"cart\") {\n params.set(\"modal_id\", CART_MODAL);\n } else if (route.type === \"checkout\") {\n params.set(\"modal_id\", CHECKOUT_MODAL);\n } else if (route.type === \"sign-in\") {\n params.set(\"auth_modal_id\", SIGN_IN_MODAL);\n if (route.returnTo !== undefined) {\n params.set(\"return\", route.returnTo);\n }\n } else if (route.type === \"checkout-return\" && route.session !== undefined) {\n params.set(\"session\", route.session);\n }\n params.sort();\n return params;\n};\n\nexport const v2Params = (\n route: StorefrontRoute,\n installation: StorefrontInstallation,\n) => {\n const params = stateParams(route);\n const menu = params.get(\"menu_id\");\n if (menu) {\n params.set(\"menu\", publicId(menu, \"menu\"));\n }\n for (const key of [\n \"menu_id\",\n \"category_id\",\n \"pickup\",\n \"menu_layout\",\n \"catalogue_search\",\n \"catalogue_categories\",\n \"template\",\n \"catalog_rendering\",\n \"prefetch\",\n \"ws\",\n \"env\",\n \"business\",\n \"business_tag\",\n \"switched_branch\",\n \"success\",\n \"auto_scroll\",\n \"preview\",\n \"modal_id\",\n ]) {\n params.delete(key);\n }\n\n if (\"business\" in route) {\n if (route.branch && route.branch !== installation.defaultBranch) {\n params.set(\"branch\", route.branch);\n } else {\n params.delete(\"branch\");\n }\n }\n if (route.type === \"sign-in\") {\n params.delete(\"auth_modal_id\");\n if (route.returnTo !== undefined) {\n params.set(\"return\", route.returnTo);\n }\n }\n if (route.type === \"checkout-return\" && route.session !== undefined) {\n params.set(\"session\", route.session);\n }\n params.sort();\n return params;\n};\n"],
5
4
  "mappings": ";AAAA,SAAS,YAAY,gBAAgB,qBAAqB;AAC1D,SAAS,gBAAgB;AAEzB,SAAS,oBAAoB;AAE7B,IAAM,cAAc,CAAC,UAA2B;AAC9C,QAAM,SAAS,IAAI,gBAAgB,MAAM,MAAM,KAAK;AACpD,QAAM,QAAQ,MAAM;AACpB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,UAAM,QAAQ,MAAM,KAAK;AACzB,QAAI,UAAU,QAAW;AACvB,aAAO,IAAI,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACA,MAAI,MAAM,MAAM;AACd,WAAO,IAAI,UAAU,OAAO,MAAM,SAAS,QAAQ,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAEO,IAAM,WAAW,CAAC,UAA2B;AAClD,QAAM,SAAS,YAAY,KAAK;AAChC,MAAI,MAAM,SAAS,QAAQ;AACzB,WAAO,OAAO,UAAU;AAAA,EAC1B,WAAW,MAAM,SAAS,QAAQ;AAChC,WAAO,IAAI,YAAY,UAAU;AAAA,EACnC,WAAW,MAAM,SAAS,YAAY;AACpC,WAAO,IAAI,YAAY,cAAc;AAAA,EACvC,WAAW,MAAM,SAAS,WAAW;AACnC,WAAO,IAAI,iBAAiB,aAAa;AACzC,QAAI,MAAM,aAAa,QAAW;AAChC,aAAO,IAAI,UAAU,MAAM,QAAQ;AAAA,IACrC;AAAA,EACF,WAAW,MAAM,SAAS,qBAAqB,MAAM,YAAY,QAAW;AAC1E,WAAO,IAAI,WAAW,MAAM,OAAO;AAAA,EACrC;AACA,SAAO,KAAK;AACZ,SAAO;AACT;AAEO,IAAM,WAAW,CACtB,OACA,iBACG;AACH,QAAM,SAAS,YAAY,KAAK;AAChC,QAAM,OAAO,OAAO,IAAI,SAAS;AACjC,MAAI,MAAM;AACR,WAAO,IAAI,QAAQ,SAAS,MAAM,MAAM,CAAC;AAAA,EAC3C;AACA,aAAW,OAAO;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAG;AACD,WAAO,OAAO,GAAG;AAAA,EACnB;AAEA,MAAI,cAAc,OAAO;AACvB,QAAI,MAAM,UAAU,MAAM,WAAW,aAAa,eAAe;AAC/D,aAAO,IAAI,UAAU,MAAM,MAAM;AAAA,IACnC,OAAO;AACL,aAAO,OAAO,QAAQ;AAAA,IACxB;AAAA,EACF;AACA,MAAI,MAAM,SAAS,WAAW;AAC5B,WAAO,OAAO,eAAe;AAC7B,QAAI,MAAM,aAAa,QAAW;AAChC,aAAO,IAAI,UAAU,MAAM,QAAQ;AAAA,IACrC;AAAA,EACF;AACA,MAAI,MAAM,SAAS,qBAAqB,MAAM,YAAY,QAAW;AACnE,WAAO,IAAI,WAAW,MAAM,OAAO;AAAA,EACrC;AACA,SAAO,KAAK;AACZ,SAAO;AACT;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/server/storefront-url/parse-v1.ts"],
4
- "sourcesContent": ["import { installationPath, normalizedPath, segment } from \"./installation\";\nimport {\n type StorefrontRoute,\n type StorefrontUrlInput,\n type StorefrontUrlOptions,\n} from \"./types\";\nimport { ITEM_MODAL_PREFIX, inputUrl, pageState } from \"./url\";\n\n/** Paths with no variable segment. Checked before ID_ROUTES: `/dine-in/menus` is not a dine-in order. */\nconst FIXED_ROUTES = {\n \"/dine-in/menus\": \"dine-in-menus\",\n \"/self-service/menus\": \"self-service-menus\",\n \"/collect-payment\": \"collect-payment\",\n \"/reservation\": \"reservation\",\n} as const;\n\n/** `/<prefix>/<id>` routes, where the one segment after the prefix names the resource. */\nconst ID_ROUTES = {\n \"dine-in\": [\"dine-in\", \"order\"],\n \"pay-point\": [\"pay-point\", \"payPoint\"],\n beacons: [\"beacon\", \"beacon\"],\n links: [\"link\", \"link\"],\n pay: [\"pay-session\", \"session\"],\n} as const;\n\nexport const parseV1 = (\n input: StorefrontUrlInput,\n options: StorefrontUrlOptions = {},\n): StorefrontRoute => {\n try {\n const url = inputUrl(input);\n const state = pageState(url, \"v1\");\n const pathname = installationPath(\n normalizedPath(url.pathname),\n options.installation,\n );\n const parts = pathname.split(\"/\").filter(Boolean);\n\n if (parts[0] === \"order\" && parts[1]) {\n const business = segment(parts, 1);\n if (parts[2] === \"pages\" && parts.length === 4) {\n return {\n type: \"content-page\",\n business,\n slug: segment(parts, 3),\n state,\n };\n }\n const branch = parts[2] ? segment(parts, 2) : undefined;\n if (branch && parts[3] === \"pages\" && parts.length === 5) {\n return {\n type: \"content-page\",\n business,\n branch,\n slug: segment(parts, 4),\n state,\n };\n }\n if (branch && parts[3] === \"products\" && parts[4] && parts.length === 5) {\n return {\n type: \"item\",\n business,\n branch,\n item: segment(parts, 4),\n state,\n };\n }\n if (\n parts.length <= 3 ||\n (parts.length === 4 && parts[3] === \"products\")\n ) {\n const modalItem = state.modalId?.startsWith(ITEM_MODAL_PREFIX)\n ? state.modalId.slice(ITEM_MODAL_PREFIX.length)\n : \"\";\n if (branch && modalItem) {\n return {\n type: \"item\",\n business,\n branch,\n item: modalItem,\n state,\n };\n }\n return {\n type: \"order-menu\",\n business,\n ...(branch ? { branch } : {}),\n ...(parts[3] === \"products\" ? { cataloguePath: true } : {}),\n state,\n };\n }\n }\n\n if (Object.hasOwn(FIXED_ROUTES, pathname)) {\n return {\n type: FIXED_ROUTES[pathname as keyof typeof FIXED_ROUTES],\n state,\n };\n }\n const [prefix = \"\", id] = parts;\n if (parts.length === 2 && id && Object.hasOwn(ID_ROUTES, prefix)) {\n const [type, field] = ID_ROUTES[prefix as keyof typeof ID_ROUTES];\n return { type, [field]: segment(parts, 1), state } as StorefrontRoute;\n }\n if (\n parts[0] === \"payment\" &&\n parts[1] === \"card\" &&\n parts[2] &&\n parts.length === 3\n ) {\n return { type: \"payment-card\", transaction: segment(parts, 2), state };\n }\n if (parts[0] === \"payment\" && parts[1] && parts.length <= 3) {\n const step = parts[2];\n if (!step || [\"successful\", \"failed\", \"refunded\"].includes(step)) {\n return {\n type: \"payment-step\",\n payment: segment(parts, 1),\n step: (step || \"payment\") as Extract<\n StorefrontRoute,\n { type: \"payment-step\" }\n >[\"step\"],\n state,\n };\n }\n }\n if (parts[0] === \"orders\" && parts.length <= 2) {\n return {\n type: \"profile\",\n page: \"orders\",\n ...(parts[1] ? { order: segment(parts, 1) } : {}),\n state,\n };\n }\n if (parts[0] === \"profile\" && parts[1] && parts.length <= 3) {\n const page = parts[1];\n if (\n page === \"add-store-credit\" ||\n page === \"edit\" ||\n page === \"payment-methods\" ||\n page === \"transactions\"\n ) {\n return { type: \"profile\", page, state };\n }\n if (page === \"orders\") {\n return {\n type: \"profile\",\n page,\n ...(parts[2] ? { order: segment(parts, 2) } : {}),\n state,\n };\n }\n }\n if (pathname === \"/\" || pathname === \"/explore\") {\n return {\n type: \"explore\",\n ...(pathname === \"/\" ? { home: true } : {}),\n state,\n };\n }\n return { type: \"unknown\", pathname, state };\n } catch {\n return { type: \"unknown\", pathname: \"\", state: { query: {} } };\n }\n};\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }