@kotao/storefront 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/chunk-4OQYNUGM.js +29 -0
  2. package/dist/chunk-4OQYNUGM.js.map +1 -0
  3. package/dist/{chunk-U5677AEA.js → chunk-4TEFGD42.js} +343 -40
  4. package/dist/chunk-4TEFGD42.js.map +1 -0
  5. package/dist/chunk-7MTCGXIG.js +144 -0
  6. package/dist/chunk-7MTCGXIG.js.map +1 -0
  7. package/dist/{chunk-GCQI6OKH.js → chunk-H3D7BAOL.js} +3 -26
  8. package/dist/chunk-H3D7BAOL.js.map +1 -0
  9. package/dist/chunk-IIOHENCG.js +239 -0
  10. package/dist/chunk-IIOHENCG.js.map +1 -0
  11. package/dist/chunk-M7BPQ54D.js +26 -0
  12. package/dist/chunk-M7BPQ54D.js.map +1 -0
  13. package/dist/{chunk-ISAFEOR2.js → chunk-PXVDVUFX.js} +3 -2
  14. package/dist/chunk-PXVDVUFX.js.map +1 -0
  15. package/dist/chunk-VV653S52.js +246 -0
  16. package/dist/chunk-VV653S52.js.map +1 -0
  17. package/dist/chunk-WG7HYVHJ.js +38 -0
  18. package/dist/chunk-WG7HYVHJ.js.map +1 -0
  19. package/dist/{client-D9YYA2w6.d.ts → client-D51WdvYR.d.ts} +55 -8
  20. package/dist/client-bsiNPHBi.d.ts +159 -0
  21. package/dist/createCartHandler-C4RrPwSG.d.ts +58 -0
  22. package/dist/editor.d.ts +1 -1
  23. package/dist/events-react.d.ts +68 -0
  24. package/dist/events-react.js +557 -0
  25. package/dist/events-react.js.map +1 -0
  26. package/dist/events.d.ts +18 -0
  27. package/dist/events.js +5 -0
  28. package/dist/events.js.map +1 -0
  29. package/dist/fetch-M3Y-fQrW.d.ts +47 -0
  30. package/dist/forms.d.ts +58 -0
  31. package/dist/forms.js +3 -0
  32. package/dist/forms.js.map +1 -0
  33. package/dist/http-types-CGh8WGHB.d.ts +11 -0
  34. package/dist/index.d.ts +75 -12
  35. package/dist/index.js +55 -8
  36. package/dist/index.js.map +1 -1
  37. package/dist/offer-token-C-nk_nj8.d.ts +16 -0
  38. package/dist/{pagination-CPgH3fAa.d.ts → pagination-DCrQk02Q.d.ts} +1 -1
  39. package/dist/product.d.ts +1 -1
  40. package/dist/react-router.d.ts +12 -6
  41. package/dist/react-router.js +4 -2
  42. package/dist/react-router.js.map +1 -1
  43. package/dist/react.d.ts +30 -7
  44. package/dist/react.js +275 -156
  45. package/dist/react.js.map +1 -1
  46. package/dist/{routing-BfUp49o8.d.ts → routing-sEonP93J.d.ts} +26 -59
  47. package/dist/seo.d.ts +4 -1
  48. package/dist/seo.js +1 -1
  49. package/dist/settings.d.ts +6 -39
  50. package/dist/settings.js +2 -1
  51. package/dist/{shop-DkQmro-Y.d.ts → shop-BJSvtmnV.d.ts} +2 -2
  52. package/dist/{store-DQK1nhob.d.ts → store-CtCWHI8H.d.ts} +1 -1
  53. package/dist/types-CSNXbGFW.d.ts +108 -0
  54. package/dist/{types-Df3bGbKU.d.ts → types-bcbQzx87.d.ts} +1 -1
  55. package/package.json +13 -1
  56. package/dist/chunk-GCQI6OKH.js.map +0 -1
  57. package/dist/chunk-ISAFEOR2.js.map +0 -1
  58. package/dist/chunk-U5677AEA.js.map +0 -1
@@ -1,59 +1,8 @@
1
- import { S as StorefrontClient, C as CacheStrategy, K as KotaoCache } from './client-D9YYA2w6.js';
2
- import { C as CartFormAction, a as Cart, b as CartLineInput, c as CartAttribute, d as CartBuyerIdentityInput, e as CartMutationResult, f as CartLineUpdateInput, g as CartMetafield, h as CartMetafieldIdentifier, i as CartDeliveryAddress, j as CartSelectedDeliveryOption, H as HydrogenSession } from './types-Df3bGbKU.js';
3
-
4
- interface CartCreateInput {
5
- readonly lines?: readonly CartLineInput[];
6
- readonly note?: string | null;
7
- readonly attributes?: readonly CartAttribute[];
8
- readonly buyerIdentity?: CartBuyerIdentityInput;
9
- }
10
- interface CartDispatchAction {
11
- readonly action: CartFormAction;
12
- readonly inputs: Record<string, unknown>;
13
- }
14
- interface CartHandler {
15
- get(): Promise<Cart | null>;
16
- getCart(): Promise<Cart | null>;
17
- create(input?: CartCreateInput): Promise<CartMutationResult>;
18
- addLines(lines: readonly CartLineInput[]): Promise<CartMutationResult>;
19
- updateLines(lines: readonly CartLineUpdateInput[]): Promise<CartMutationResult>;
20
- removeLines(lineIds: readonly string[]): Promise<CartMutationResult>;
21
- updateBuyerIdentity(buyerIdentity: CartBuyerIdentityInput): Promise<CartMutationResult>;
22
- updateDiscountCodes(discountCodes: readonly string[]): Promise<CartMutationResult>;
23
- setCartId(cartId: string): Headers;
24
- cartCreate(input?: CartCreateInput): Promise<CartMutationResult>;
25
- linesAdd(lines: readonly CartLineInput[]): Promise<CartMutationResult>;
26
- linesUpdate(lines: readonly CartLineUpdateInput[]): Promise<CartMutationResult>;
27
- linesRemove(lineIds: readonly string[]): Promise<CartMutationResult>;
28
- buyerIdentityUpdate(buyerIdentity: CartBuyerIdentityInput): Promise<CartMutationResult>;
29
- discountCodesUpdate(discountCodes: readonly string[]): Promise<CartMutationResult>;
30
- giftCardCodesAdd(giftCardCodes: readonly string[]): Promise<CartMutationResult>;
31
- giftCardCodesRemove(giftCardCodes: readonly string[]): Promise<CartMutationResult>;
32
- noteUpdate(note: string | null): Promise<CartMutationResult>;
33
- attributesUpdate(attributes: readonly CartAttribute[]): Promise<CartMutationResult>;
34
- metafieldsSet(metafields: readonly CartMetafield[]): Promise<CartMutationResult>;
35
- metafieldsDelete(metafields: readonly CartMetafieldIdentifier[]): Promise<CartMutationResult>;
36
- deliveryAddressesReplace(addresses: readonly CartDeliveryAddress[]): Promise<CartMutationResult>;
37
- selectedDeliveryOptionsUpdate(selectedDeliveryOptions: readonly CartSelectedDeliveryOption[]): Promise<CartMutationResult>;
38
- dispatch(action: CartDispatchAction): Promise<CartMutationResult>;
39
- }
40
- type MaybePromise<T> = T | Promise<T>;
41
- type CartGetId = () => MaybePromise<string | null | undefined>;
42
- type CartSetId = (cartId: string) => MaybePromise<string | Headers | null | undefined | void>;
43
- interface RequestCartHandlerOptions {
44
- request: Request;
45
- storefront: StorefrontClient;
46
- }
47
- interface HydrogenCartHandlerOptions {
48
- storefront: StorefrontClient;
49
- customerAccount?: unknown;
50
- getCartId: CartGetId;
51
- setCartId: CartSetId;
52
- }
53
- type CreateCartHandlerOptions = RequestCartHandlerOptions | HydrogenCartHandlerOptions;
54
- declare function cartGetIdDefault(headers: Headers): CartGetId;
55
- declare function cartSetIdDefault(): CartSetId;
56
- declare function createCartHandler(opts: CreateCartHandlerOptions): CartHandler;
1
+ import { S as StoredThemeSettings } from './fetch-M3Y-fQrW.js';
2
+ import { C as CacheStrategy, K as KotaoCache, S as StorefrontClient, b as StorefrontCacheInput } from './client-D51WdvYR.js';
3
+ import { C as CartHandler } from './createCartHandler-C4RrPwSG.js';
4
+ import { E as EventsClient } from './client-bsiNPHBi.js';
5
+ import { H as HydrogenSession } from './types-bcbQzx87.js';
57
6
 
58
7
  /**
59
8
  * Typed customer-account shapes for the `@kotao/storefront` SDK surface (#91). The names/signatures
@@ -95,6 +44,8 @@ interface RequestLoginCodeInput {
95
44
  }
96
45
  interface RequestLoginCodeResult {
97
46
  readonly status: 'sent' | 'rate_limited' | 'error';
47
+ /** Raw proxy response carrying CSRF rotation cookies/headers to the browser action. */
48
+ readonly response: Response;
98
49
  }
99
50
  interface VerifyLoginCodeInput {
100
51
  readonly email: string;
@@ -246,6 +197,8 @@ interface KotaoEnv {
246
197
  * Must match the api proxy's STOREFRONT_SHOP_CONTEXT_KEY.
247
198
  */
248
199
  STOREFRONT_SHOP_CONTEXT_KEY?: string;
200
+ /** Explicit local-dev origin; production receives the gateway-overwritten header. */
201
+ STOREFRONT_SHOP_ORIGIN?: string;
249
202
  SESSION_SECRET?: string;
250
203
  PUBLIC_STOREFRONT_API_TOKEN?: string;
251
204
  PRIVATE_STOREFRONT_API_TOKEN?: string;
@@ -256,11 +209,15 @@ interface KotaoEnv {
256
209
  PUBLIC_CHECKOUT_DOMAIN?: string;
257
210
  SHOP_ID?: string;
258
211
  }
212
+ interface StorefrontExecutionContext {
213
+ waitUntil(promise: Promise<unknown>): void;
214
+ }
259
215
  interface KotaoSession extends HydrogenSession {
260
216
  readonly isLoggedIn: boolean;
261
217
  }
262
218
  interface KotaoContext {
263
219
  readonly storefront: StorefrontClient;
220
+ readonly events: EventsClient;
264
221
  readonly cart: CartHandler;
265
222
  readonly customerAccount: CustomerAccountClient;
266
223
  readonly geo: Geo | null;
@@ -271,13 +228,23 @@ interface KotaoContext {
271
228
  readonly session: KotaoSession;
272
229
  readonly env: any;
273
230
  readonly waitUntil: (p: Promise<unknown>) => void;
231
+ /**
232
+ * Fetch this shop's stored theme settings (ordered section instances + global tokens) for SSR.
233
+ * The Gateway-signed shop context from the inbound request is forwarded automatically and the call
234
+ * goes cross-origin to the API — mirroring how `storefront` authenticates, because a Worker
235
+ * subrequest to its own zone bypasses the Gateway carve-out.
236
+ */
237
+ readonly loadThemeSettings: (opts?: {
238
+ readonly fetch?: typeof fetch;
239
+ }) => Promise<StoredThemeSettings>;
274
240
  }
241
+ declare function trustedShopOriginFromHeaders(headers: Headers): string | undefined;
275
242
  /**
276
243
  * Framework-agnostic per-request context, mirroring Hydrogen's `createHydrogenContext`:
277
244
  * storefront client + verified geo + SWR cache + locale + session seam.
278
245
  */
279
- declare function createKotaoContext(request: Request, env: KotaoEnv, executionCtx: ExecutionContext, deps?: {
280
- openCache?: (name: string) => Promise<Cache>;
246
+ declare function createKotaoContext(request: Request, env: KotaoEnv, executionCtx: StorefrontExecutionContext, deps?: {
247
+ openCache?: (name: string) => Promise<StorefrontCacheInput>;
281
248
  session?: HydrogenSession;
282
249
  }): Promise<KotaoContext>;
283
250
 
@@ -299,4 +266,4 @@ declare function stripLocalePrefixFromPathname(pathname: string): StrippedLocale
299
266
  declare function stripLocalePrefixFromRequest(request: Request): Request;
300
267
  declare function localizePath(path: string, locale: string | null | undefined, options?: LocalizePathOptions): string;
301
268
 
302
- export { type Address as A, isLocalePathSegment as B, type CartDispatchAction as C, localeFromAcceptLanguage as D, localeFromPathname as E, localeFromRequest as F, GEO_HEADER as G, type HydrogenCartHandlerOptions as H, localizePath as I, originalPathnameFromRequest as J, type KotaoEnv as K, type LocalizePathOptions as L, type Money as M, signGeo as N, type Order as O, stripLocalePrefixFromPathname as P, verifyGeo as Q, ROUTING_LOCALE_HEADER as R, type StrippedLocalePath as S, type VerifyLoginCodeInput as V, type WithCache as W, type KotaoContext as a, type KotaoSession as b, type CustomerAccountClient as c, type AddressConnection as d, type CartGetId as e, type CartHandler as f, type CartSetId as g, type CreateCartHandlerOptions as h, type CustomerAccount as i, CustomerAccountError as j, type CustomerAccountErrorCode as k, GEO_SIGNATURE_HEADER as l, type Geo as m, type OrderConnection as n, type OrderPageInfo as o, type OrdersOptions as p, ROUTING_ORIGINAL_PATHNAME_HEADER as q, type RequestCartHandlerOptions as r, stripLocalePrefixFromRequest as s, type RequestLoginCodeInput as t, type RequestLoginCodeResult as u, cartGetIdDefault as v, cartSetIdDefault as w, createCartHandler as x, createKotaoContext as y, createWithCache as z };
269
+ export { type Address as A, trustedShopOriginFromHeaders as B, type CustomerAccountClient as C, verifyGeo as D, GEO_HEADER as G, type KotaoEnv as K, type LocalizePathOptions as L, type Money as M, type Order as O, ROUTING_LOCALE_HEADER as R, type StorefrontExecutionContext as S, type VerifyLoginCodeInput as V, type WithCache as W, type KotaoContext as a, type KotaoSession as b, type AddressConnection as c, type CustomerAccount as d, CustomerAccountError as e, type CustomerAccountErrorCode as f, GEO_SIGNATURE_HEADER as g, type Geo as h, type OrderConnection as i, type OrderPageInfo as j, type OrdersOptions as k, ROUTING_ORIGINAL_PATHNAME_HEADER as l, type RequestLoginCodeInput as m, type RequestLoginCodeResult as n, type StrippedLocalePath as o, createKotaoContext as p, createWithCache as q, isLocalePathSegment as r, stripLocalePrefixFromRequest as s, localeFromAcceptLanguage as t, localeFromPathname as u, localeFromRequest as v, localizePath as w, originalPathnameFromRequest as x, signGeo as y, stripLocalePrefixFromPathname as z };
package/dist/seo.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { S as SeoConfig, b as SeoDescriptor, J as JsonLd } from './types-BDhHWZsP.js';
2
2
  export { A as Alternate, O as OpenGraph, a as OpenGraphImage, T as TwitterCard } from './types-BDhHWZsP.js';
3
- import { S as StorefrontClient } from './client-D9YYA2w6.js';
3
+ import { S as StorefrontClient } from './client-D51WdvYR.js';
4
+ import './http-types-CGh8WGHB.js';
4
5
 
5
6
  /**
6
7
  * Merge SEO config across levels and emit a flat, deduplicated descriptor list (RR7 `meta`-compatible):
@@ -119,6 +120,8 @@ declare function sitemapIndexResponse(sitemaps: readonly SitemapIndexEntry[]): R
119
120
  interface RobotsOptions {
120
121
  readonly production: boolean;
121
122
  readonly sitemapUrl?: string;
123
+ /** Path prefixes to Disallow in production (e.g. /cart, /checkout — Shopify parity). */
124
+ readonly disallow?: readonly string[];
122
125
  }
123
126
  declare function buildRobotsTxt(options: RobotsOptions): string;
124
127
  declare function robotsResponse(options: RobotsOptions): Response;
package/dist/seo.js CHANGED
@@ -1,4 +1,4 @@
1
- export { URL_REDIRECT_QUERY, breadcrumbListJsonLd, buildRobotsTxt, buildSitemapIndexXml, buildSitemapXml, localBusinessJsonLd, organizationJsonLd, productJsonLd, resolveRedirect, resolveStorefrontRedirect, restaurantJsonLd, robotsResponse, sitemapIndexResponse, sitemapResponse, storefrontRedirect, websiteJsonLd } from './chunk-ISAFEOR2.js';
1
+ export { URL_REDIRECT_QUERY, breadcrumbListJsonLd, buildRobotsTxt, buildSitemapIndexXml, buildSitemapXml, localBusinessJsonLd, organizationJsonLd, productJsonLd, resolveRedirect, resolveStorefrontRedirect, restaurantJsonLd, robotsResponse, sitemapIndexResponse, sitemapResponse, storefrontRedirect, websiteJsonLd } from './chunk-PXVDVUFX.js';
2
2
  export { getSeoMeta, serializeJsonLd } from './chunk-MYDKIJRH.js';
3
3
  //# sourceMappingURL=seo.js.map
4
4
  //# sourceMappingURL=seo.js.map
@@ -1,7 +1,9 @@
1
1
  import { SectionSchema, Preset, ValuesDocument, SettingInput, SectionInstance } from '@kotao/storefront-contracts/theme';
2
- import { a as ResolvedSection, b as ResolvedSettings, T as ThemeSettingsSnapshot } from './store-DQK1nhob.js';
3
- export { R as ResolvedBlock, c as ThemeSettingsStore, d as createThemeSettingsStore } from './store-DQK1nhob.js';
4
- import { f as StorefrontAuthHeader } from './client-D9YYA2w6.js';
2
+ import { c as ResolvedSection, R as ResolvedSettings, a as ThemeSettingsSnapshot } from './store-CtCWHI8H.js';
3
+ export { b as ResolvedBlock, T as ThemeSettingsStore, d as createThemeSettingsStore } from './store-CtCWHI8H.js';
4
+ export { F as FetchThemeSettingsOptions, S as StoredThemeSettings, f as fetchThemeSettings } from './fetch-M3Y-fQrW.js';
5
+ import './client-D51WdvYR.js';
6
+ import './http-types-CGh8WGHB.js';
5
7
 
6
8
  /**
7
9
  * The settings-as-data merge engine (ADR-0003 §"Settings-as-data customizer", #93). Pure: it resolves
@@ -49,39 +51,4 @@ interface ResolveThemeSettingsArgs {
49
51
  /** Compose the merge over an entire values document → the snapshot a theme renders from. */
50
52
  declare function resolveThemeSettings(args: ResolveThemeSettingsArgs): ThemeSettingsSnapshot;
51
53
 
52
- /**
53
- * The stored settings a theme hydrates from (#93): the layout document's ordered section instances +
54
- * the global theme tokens. Pass to {@link resolveThemeSettings} together with the theme's bundled
55
- * section schemas + presets to produce the merged {@link ThemeSettingsSnapshot}.
56
- */
57
- interface StoredThemeSettings {
58
- readonly shopId: string;
59
- readonly sections: readonly SectionInstance[];
60
- readonly global: Record<string, unknown>;
61
- readonly templates?: Readonly<Partial<Record<string, ValuesDocument>>>;
62
- readonly sectionGroups?: Readonly<Partial<Record<string, ValuesDocument>>>;
63
- }
64
- interface FetchThemeSettingsOptions {
65
- /** Injectable for tests; defaults to the global `fetch`. */
66
- readonly fetch?: typeof fetch;
67
- /** Same-origin settings endpoint (default `/api/storefront/settings`). */
68
- readonly endpoint?: string;
69
- /**
70
- * Explicit local-preview escape hatch for server-side theme workers that call the Kotao API directly.
71
- * Production themes should keep the default same-origin path.
72
- */
73
- readonly allowCrossOrigin?: boolean;
74
- /** Optional server-side auth headers for local previews. Never pass browser-visible tokens here. */
75
- readonly auth?: {
76
- readonly token: string;
77
- readonly tokenHeader?: StorefrontAuthHeader;
78
- readonly siteId?: string;
79
- };
80
- }
81
- /**
82
- * Fetch the shop's stored settings document via the same-origin `/api/storefront/settings` carve-out.
83
- * The shop is resolved server-side from the Gateway-signed context — the theme sends no id and no token.
84
- */
85
- declare function fetchThemeSettings(options?: FetchThemeSettingsOptions): Promise<StoredThemeSettings>;
86
-
87
- export { type FetchThemeSettingsOptions, type ResolveThemeSettingsArgs, ResolvedSection, ResolvedSettings, type StoredThemeSettings, ThemeSettingsSnapshot, fetchThemeSettings, mergeSection, mergeSettings, resolveThemeSettings };
54
+ export { type ResolveThemeSettingsArgs, ResolvedSection, ResolvedSettings, ThemeSettingsSnapshot, mergeSection, mergeSettings, resolveThemeSettings };
package/dist/settings.js CHANGED
@@ -1,3 +1,4 @@
1
- export { createThemeSettingsStore, fetchThemeSettings, mergeSection, mergeSettings, resolveThemeSettings } from './chunk-GCQI6OKH.js';
1
+ export { createThemeSettingsStore, mergeSection, mergeSettings, resolveThemeSettings } from './chunk-H3D7BAOL.js';
2
+ export { fetchThemeSettings } from './chunk-M7BPQ54D.js';
2
3
  //# sourceMappingURL=settings.js.map
3
4
  //# sourceMappingURL=settings.js.map
@@ -1,4 +1,4 @@
1
- import { C as CartFormAction } from './types-Df3bGbKU.js';
1
+ import { j as CartFormAction } from './types-bcbQzx87.js';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  interface CartFormProps {
@@ -199,4 +199,4 @@ interface GetShopAnalyticsOptions {
199
199
  }
200
200
  declare function getShopAnalytics(options: GetShopAnalyticsOptions): ShopAnalytics;
201
201
 
202
- export { ANALYTICS_EVENTS as A, type BoundNonceProvider as B, type ConsentBucket as C, DENIED_CONSENT as D, type EventHandler as E, getShopAnalytics as F, GRANTED_CONSENT as G, isGranted as H, mergeConsent as I, parseConsent as J, readConsentCookie as K, serializeConsent as L, getCartFormInput as M, useNonce as N, type PageViewedPayload as P, type SearchViewedPayload as S, type ConsentState as a, type AnalyticsBus as b, type AnalyticsBusOptions as c, type AnalyticsContext as d, type AnalyticsEmitter as e, type AnalyticsEventMap as f, type AnalyticsEventName as g, type AnalyticsGeo as h, CONSENT_BUCKETS as i, CONSENT_COOKIE as j, CartForm as k, type CartFormProps as l, type CartViewedPayload as m, type CollectionViewedPayload as n, type GetShopAnalyticsOptions as o, type PixelEvent as p, type PixelSend as q, type PixelTransport as r, type PixelTransportOptions as s, type ProductAddedToCartPayload as t, type ProductViewedPayload as u, type ShopAnalytics as v, buildConsentCookie as w, createAnalyticsBus as x, createAnalyticsEmitter as y, createPixelTransport as z };
202
+ export { type AnalyticsContext as A, type BoundNonceProvider as B, type ConsentState as C, DENIED_CONSENT as D, type EventHandler as E, createAnalyticsEmitter as F, GRANTED_CONSENT as G, createPixelTransport as H, getShopAnalytics as I, isGranted as J, mergeConsent as K, parseConsent as L, readConsentCookie as M, serializeConsent as N, type PageViewedPayload as P, type ShopAnalytics as S, type AnalyticsEmitter as a, type AnalyticsBus as b, type ConsentBucket as c, ANALYTICS_EVENTS as d, type AnalyticsBusOptions as e, type AnalyticsEventMap as f, getCartFormInput as g, type AnalyticsEventName as h, type AnalyticsGeo as i, CONSENT_BUCKETS as j, CONSENT_COOKIE as k, CartForm as l, type CartFormProps as m, type CartViewedPayload as n, type CollectionViewedPayload as o, type GetShopAnalyticsOptions as p, type PixelEvent as q, type PixelSend as r, type PixelTransport as s, type PixelTransportOptions as t, useNonce as u, type ProductAddedToCartPayload as v, type ProductViewedPayload as w, type SearchViewedPayload as x, buildConsentCookie as y, createAnalyticsBus as z };
@@ -64,4 +64,4 @@ interface ThemeSettingsStore {
64
64
  }
65
65
  declare function createThemeSettingsStore(initial: ThemeSettingsSnapshot): ThemeSettingsStore;
66
66
 
67
- export { type ResolvedBlock as R, type ThemeSettingsSnapshot as T, type ResolvedSection as a, type ResolvedSettings as b, type ThemeSettingsStore as c, createThemeSettingsStore as d };
67
+ export { type ResolvedSettings as R, type ThemeSettingsStore as T, type ThemeSettingsSnapshot as a, type ResolvedBlock as b, type ResolvedSection as c, createThemeSettingsStore as d };
@@ -0,0 +1,108 @@
1
+ type FormLocalizedText = Record<string, string>;
2
+ type FormAnswer = string | number | boolean | string[] | Record<string, string>;
3
+ type FormAnswers = Record<string, FormAnswer>;
4
+ interface FormVisibilityRule {
5
+ fieldKey: string;
6
+ operator: 'equals' | 'not_equals' | 'includes' | 'is_empty' | 'is_not_empty';
7
+ value?: string | number | boolean;
8
+ }
9
+ interface FormField {
10
+ key: string;
11
+ label: FormLocalizedText;
12
+ description?: FormLocalizedText;
13
+ kind: 'text' | 'textarea' | 'email' | 'phone' | 'number' | 'date' | 'address' | 'select' | 'multi_select' | 'radio' | 'checkbox' | 'consent';
14
+ classification: 'public' | 'internal' | 'sensitive';
15
+ required: boolean;
16
+ searchMode: 'none' | 'exact' | 'prefix';
17
+ reportMode: 'none' | 'aggregate';
18
+ validation?: {
19
+ minLength?: number;
20
+ maxLength?: number;
21
+ min?: number;
22
+ max?: number;
23
+ };
24
+ choices?: {
25
+ value: string;
26
+ label: FormLocalizedText;
27
+ }[];
28
+ consentText?: FormLocalizedText;
29
+ visibleWhen?: {
30
+ all: FormVisibilityRule[];
31
+ };
32
+ }
33
+ type FormBlock = {
34
+ type: 'section';
35
+ key: string;
36
+ title: FormLocalizedText;
37
+ description?: FormLocalizedText;
38
+ } | {
39
+ type: 'content';
40
+ key: string;
41
+ content: FormLocalizedText;
42
+ } | {
43
+ type: 'field';
44
+ field: FormField;
45
+ };
46
+ interface FormVersionSchema {
47
+ schemaVersion: 1;
48
+ pages: {
49
+ key: string;
50
+ title?: FormLocalizedText;
51
+ description?: FormLocalizedText;
52
+ blocks: FormBlock[];
53
+ }[];
54
+ submitLabel?: FormLocalizedText;
55
+ successMessage?: FormLocalizedText;
56
+ }
57
+ interface PublicFormDefinition {
58
+ id: string;
59
+ ownerNamespace: string;
60
+ handle: string;
61
+ title: string;
62
+ purpose: string;
63
+ status: 'draft' | 'published' | 'archived';
64
+ currentPublishedVersionId: string | null;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ }
68
+ interface PublicFormVersion {
69
+ id: string;
70
+ definitionId: string;
71
+ versionNumber: number;
72
+ state: 'draft' | 'published' | 'retired';
73
+ schema: FormVersionSchema;
74
+ schemaHash: string | null;
75
+ publishedAt: string | null;
76
+ retiredAt: string | null;
77
+ createdAt: string;
78
+ }
79
+ interface PublicForm {
80
+ definition: PublicFormDefinition;
81
+ version: PublicFormVersion;
82
+ }
83
+ interface FormFieldErrors {
84
+ [fieldKey: string]: string[];
85
+ }
86
+ interface FormRenderState {
87
+ answers?: FormAnswers;
88
+ errors?: FormFieldErrors;
89
+ submitted?: boolean;
90
+ }
91
+ interface FormSubmission {
92
+ id: string;
93
+ definitionId: string;
94
+ versionId: string;
95
+ state: 'draft' | 'submitted' | 'linked' | 'anonymized';
96
+ locale: string;
97
+ source: string;
98
+ ownerType: string | null;
99
+ ownerId: string | null;
100
+ hasAnswers: boolean;
101
+ submittedAt: string | null;
102
+ expiresAt: string | null;
103
+ anonymizedAt: string | null;
104
+ createdAt: string;
105
+ updatedAt: string;
106
+ }
107
+
108
+ export type { FormVersionSchema as F, PublicForm as P, FormAnswers as a, FormFieldErrors as b, FormAnswer as c, FormBlock as d, FormField as e, FormLocalizedText as f, FormRenderState as g, FormSubmission as h, FormVisibilityRule as i, PublicFormDefinition as j, PublicFormVersion as k };
@@ -164,4 +164,4 @@ interface HydrogenSession {
164
164
  commit(): string | Promise<string>;
165
165
  }
166
166
 
167
- export { type CartFormAction as C, type HydrogenSession as H, type Cart as a, type CartLineInput as b, type CartAttribute as c, type CartBuyerIdentityInput as d, type CartMutationResult as e, type CartLineUpdateInput as f, type CartMetafield as g, type CartMetafieldIdentifier as h, type CartDeliveryAddress as i, type CartSelectedDeliveryOption as j, CART_FORM_ACTIONS as k, type CartBuyerIdentity as l, type CartLine as m, type CartUserError as n, type HydrogenCart as o, type HydrogenCartCreateInput as p, type HydrogenCartMutationResult as q, type HydrogenSessionData as r };
167
+ export { type Cart as C, type HydrogenSession as H, type CartLineInput as a, type CartAttribute as b, type CartBuyerIdentityInput as c, type CartMutationResult as d, type CartLineUpdateInput as e, type CartMetafield as f, type CartMetafieldIdentifier as g, type CartDeliveryAddress as h, type CartSelectedDeliveryOption as i, type CartFormAction as j, CART_FORM_ACTIONS as k, type CartBuyerIdentity as l, type CartLine as m, type CartUserError as n, type HydrogenCart as o, type HydrogenCartCreateInput as p, type HydrogenCartMutationResult as q, type HydrogenSessionData as r };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kotao/storefront",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Kotao Storefronts theme SDK — framework-agnostic server runtime (storefront client, SWR isolated cache, signed geo) + React Router 7 binding. Mirrors Hydrogen's API (ADR-0003). Leaf package (ADR-0006).",
@@ -26,6 +26,18 @@
26
26
  "types": "./dist/editor.d.ts",
27
27
  "import": "./dist/editor.js"
28
28
  },
29
+ "./events": {
30
+ "types": "./dist/events.d.ts",
31
+ "import": "./dist/events.js"
32
+ },
33
+ "./events/react": {
34
+ "types": "./dist/events-react.d.ts",
35
+ "import": "./dist/events-react.js"
36
+ },
37
+ "./forms": {
38
+ "types": "./dist/forms.d.ts",
39
+ "import": "./dist/forms.js"
40
+ },
29
41
  "./image": {
30
42
  "types": "./dist/image.d.ts",
31
43
  "import": "./dist/image.js"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/settings/merge.ts","../src/settings/store.ts","../src/settings/fetch.ts"],"names":["section","block"],"mappings":"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","file":"chunk-GCQI6OKH.js","sourcesContent":["/**\n * The settings-as-data merge engine (ADR-0003 §\"Settings-as-data customizer\", #93). Pure: it resolves\n * a section's effective settings with the universal three-layer order\n *\n * stored value → preset value → input `defaultValue`\n *\n * and composes that over a whole values document (`resolveThemeSettings`). It lives in the SDK (not a\n * commerce module) because it consumes `@kotao/storefront-contracts` types — the storefront layer that\n * may import them (turbo boundaries forbids a module from doing so). The per-shop stored values come from\n * the web module's settings store, read server-side and handed to the SDK as a snapshot.\n */\nimport type {\n Preset,\n SectionInstance,\n SectionSchema,\n SettingInput,\n ValuesDocument,\n} from '@kotao/storefront-contracts/theme'\n\nimport type {\n ResolvedSection,\n ResolvedSettings,\n ResolvedValuesDocument,\n ThemeSettingsSnapshot,\n} from './types'\n\nexport type {\n ResolvedBlock,\n ResolvedSection,\n ResolvedSettings,\n ResolvedValuesDocument,\n ThemeSettingsSnapshot,\n} from './types'\n\n/**\n * Resolve one settings layer: for every input the schema declares, take the stored value, else the\n * preset value, else the input's `defaultValue`. Only declared inputs appear (stray stored keys for\n * removed settings are dropped).\n */\nexport function mergeSettings(\n inputs: readonly SettingInput[],\n stored: Readonly<Record<string, unknown>> | undefined,\n preset: Readonly<Record<string, unknown>> | undefined,\n): ResolvedSettings {\n const out: ResolvedSettings = {}\n for (const input of inputs) {\n const storedValue = stored?.[input.name]\n const presetValue = preset?.[input.name]\n out[input.name] =\n storedValue !== undefined\n ? storedValue\n : presetValue !== undefined\n ? presetValue\n : input.defaultValue\n }\n return out\n}\n\n/**\n * Resolve a full section instance: its settings plus each contained block's settings, all three-layer\n * merged against the section's schema (block instances come from the stored values).\n */\nexport function mergeSection(args: {\n readonly schema: SectionSchema\n readonly preset?: Preset\n readonly instance?: SectionInstance\n}): ResolvedSection {\n const settings = mergeSettings(\n args.schema.settings,\n args.instance?.settings,\n args.preset?.settings,\n )\n if (args.instance?.settings.disabled === true) settings.disabled = true\n const blockDefs = new Map((args.schema.blocks ?? []).map((b) => [b.type, b] as const))\n const blocks = (args.instance?.blocks ?? []).map((b) => ({\n ...(b.id !== undefined ? { id: b.id } : {}),\n type: b.type,\n settings: mergeSettings(blockDefs.get(b.type)?.settings ?? [], b.settings, undefined),\n }))\n return { id: args.instance?.id ?? '', type: args.schema.type, settings, blocks }\n}\n\nexport interface ResolveThemeSettingsArgs {\n /** Section schemas keyed by section `type` (from the deployment manifest). */\n readonly schemas: Readonly<Record<string, SectionSchema>>\n /** Optional default preset per section `type`. */\n readonly presets?: Readonly<Record<string, Preset>>\n /** The per-shop stored values document (from the web module's settings store). */\n readonly values: ValuesDocument\n /** Template-specific values documents, keyed by handles like `index`, `product`, or `cart`. */\n readonly templates?: Partial<Record<string, ValuesDocument>>\n /** Layout section-group values documents, keyed by handles like `header-group`. */\n readonly sectionGroups?: Partial<Record<string, ValuesDocument>>\n /** Global theme-token inputs (logo/colors/fonts), merged the same way. */\n readonly globalInputs?: readonly SettingInput[]\n readonly globalStored?: Readonly<Record<string, unknown>>\n readonly globalPreset?: Readonly<Record<string, unknown>>\n}\n\nfunction resolveValuesDocument(\n args: Pick<ResolveThemeSettingsArgs, 'presets' | 'schemas'>,\n values: ValuesDocument,\n): ResolvedValuesDocument {\n const sections = values.sections.map((instance): ResolvedSection => {\n const schema = args.schemas[instance.type]\n if (schema === undefined) {\n // Unknown section type (theme dropped it) — pass stored values through untouched; nothing to merge.\n return {\n id: instance.id,\n type: instance.type,\n settings: instance.settings,\n blocks: instance.blocks.map((b) => ({\n ...(b.id !== undefined ? { id: b.id } : {}),\n type: b.type,\n settings: b.settings,\n })),\n }\n }\n const preset = args.presets?.[instance.type]\n return mergeSection({ schema, instance, ...(preset !== undefined ? { preset } : {}) })\n })\n return { shopId: values.shopId, sections }\n}\n\nfunction resolveDocumentRecord(\n args: Pick<ResolveThemeSettingsArgs, 'presets' | 'schemas'>,\n documents: Partial<Record<string, ValuesDocument>> | undefined,\n): Readonly<Record<string, ResolvedValuesDocument>> | undefined {\n if (documents === undefined) return undefined\n const resolved: Record<string, ResolvedValuesDocument> = {}\n for (const [handle, document] of Object.entries(documents)) {\n if (document !== undefined) resolved[handle] = resolveValuesDocument(args, document)\n }\n return resolved\n}\n\n/** Compose the merge over an entire values document → the snapshot a theme renders from. */\nexport function resolveThemeSettings(args: ResolveThemeSettingsArgs): ThemeSettingsSnapshot {\n const root = resolveValuesDocument(args, args.values)\n const global = mergeSettings(args.globalInputs ?? [], args.globalStored, args.globalPreset)\n const templates = resolveDocumentRecord(args, args.templates)\n const sectionGroups = resolveDocumentRecord(args, args.sectionGroups)\n return {\n sections: root.sections,\n global,\n ...(templates !== undefined ? { templates } : {}),\n ...(sectionGroups !== undefined ? { sectionGroups } : {}),\n }\n}\n","/**\n * Framework-agnostic theme-settings store (#93). Holds the merged {@link ThemeSettingsSnapshot} and lets\n * subscribers react to live edits. In plain runtime the snapshot is the stable server-provided value and\n * never changes; in the visual editor, postMessage-driven setting patches call `patchSection`/`patchGlobal`\n * so the React binding (useSyncExternalStore) re-renders without a reload. `getSnapshot` returns a stable\n * reference between patches (a new one only on change), as useSyncExternalStore requires.\n */\nimport type {\n ResolvedBlock,\n ResolvedSection,\n ResolvedSettings,\n ResolvedValuesDocument,\n ThemeSettingsSnapshot,\n} from './types'\n\nexport interface ThemeSettingsStore {\n getSnapshot(): ThemeSettingsSnapshot\n subscribe(listener: () => void): () => void\n /** Replace a section instance's resolved settings (editor live-patch); no-op if the id is unknown. */\n patchSection(instanceId: string, settings: ResolvedSettings): void\n /** Insert a fully-resolved section instance (editor live-add); reserved ids are ignored. */\n insertSection(section: ResolvedSection, index?: number): void\n /** Remove one section instance (editor live-delete); no-op if unknown. */\n removeSection(instanceId: string): void\n /** Move one section instance to a new position (editor live-reorder); no-op if unknown. */\n moveSection(instanceId: string, index: number): void\n /** Replace the global theme tokens (editor live-patch). */\n patchGlobal(settings: ResolvedSettings): void\n /** Replace a block instance's resolved settings; no-op if section/block is unknown. */\n patchBlock(sectionId: string, blockId: string, settings: ResolvedSettings): void\n /** Insert a fully-resolved block instance into a section. */\n insertBlock(sectionId: string, block: ResolvedBlock, index?: number): void\n /** Remove one block instance from a section. */\n removeBlock(sectionId: string, blockId: string): void\n /** Move one block instance to a new position inside a section. */\n moveBlock(sectionId: string, blockId: string, index: number): void\n /** Replace all preview sections from a template values document. */\n replaceSections(sections: readonly ResolvedSection[]): void\n /** Replace one layout section group document, such as header or footer. */\n replaceSectionGroup(handle: string, document: ResolvedValuesDocument): void\n}\n\nexport function createThemeSettingsStore(initial: ThemeSettingsSnapshot): ThemeSettingsStore {\n let snapshot = initial\n const listeners = new Set<() => void>()\n const clampIndex = (index: number | undefined, length: number): number => {\n if (index === undefined || Number.isNaN(index)) return length\n return Math.max(0, Math.min(Math.trunc(index), length))\n }\n const emit = (): void => {\n for (const listener of listeners) listener()\n }\n const updateSection = (\n sectionId: string,\n update: (section: ResolvedSection) => ResolvedSection | null,\n ): boolean => {\n if (sectionId.startsWith('theme:')) return false\n let changed = false\n const sections = snapshot.sections.map((section) => {\n if (section.id !== sectionId) return section\n const next = update(section)\n if (next === null || next === section) return section\n changed = true\n return next\n })\n if (!changed) return false\n snapshot = { ...snapshot, sections }\n emit()\n return true\n }\n return {\n getSnapshot: () => snapshot,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n },\n patchSection: (instanceId, settings) => {\n if (instanceId.startsWith('theme:')) return\n let changed = false\n const sections = snapshot.sections.map((section) => {\n if (section.id !== instanceId) return section\n changed = true\n return { ...section, settings }\n })\n if (!changed) return // unknown instance → no new snapshot, no notify\n snapshot = { ...snapshot, sections }\n emit()\n },\n insertSection: (section, index) => {\n if (section.id.startsWith('theme:')) return\n if (snapshot.sections.some((existing) => existing.id === section.id)) return\n const next = [...snapshot.sections]\n next.splice(clampIndex(index, next.length), 0, section)\n snapshot = { ...snapshot, sections: next }\n emit()\n },\n removeSection: (instanceId) => {\n if (instanceId.startsWith('theme:')) return\n const sections = snapshot.sections.filter((section) => section.id !== instanceId)\n if (sections.length === snapshot.sections.length) return\n snapshot = { ...snapshot, sections }\n emit()\n },\n moveSection: (instanceId, index) => {\n if (instanceId.startsWith('theme:')) return\n const fromIndex = snapshot.sections.findIndex((section) => section.id === instanceId)\n if (fromIndex === -1) return\n const next = [...snapshot.sections]\n const [section] = next.splice(fromIndex, 1)\n if (section === undefined) return\n const toIndex = clampIndex(index, next.length)\n if (fromIndex === toIndex) return\n next.splice(toIndex, 0, section)\n snapshot = { ...snapshot, sections: next }\n emit()\n },\n patchGlobal: (settings) => {\n if (settings === snapshot.global) return // same ref → no-op (symmetric with patchSection)\n snapshot = { ...snapshot, global: settings }\n emit()\n },\n patchBlock: (sectionId, blockId, settings) => {\n updateSection(sectionId, (section) => {\n let changed = false\n const blocks = section.blocks.map((block) => {\n if (block.id !== blockId) return block\n changed = true\n return { ...block, settings }\n })\n return changed ? { ...section, blocks } : null\n })\n },\n insertBlock: (sectionId, block, index) => {\n updateSection(sectionId, (section) => {\n if (block.id !== undefined && section.blocks.some((existing) => existing.id === block.id)) {\n return null\n }\n const blocks = [...section.blocks]\n blocks.splice(clampIndex(index, blocks.length), 0, block)\n return { ...section, blocks }\n })\n },\n removeBlock: (sectionId, blockId) => {\n updateSection(sectionId, (section) => {\n const blocks = section.blocks.filter((block) => block.id !== blockId)\n return blocks.length === section.blocks.length ? null : { ...section, blocks }\n })\n },\n moveBlock: (sectionId, blockId, index) => {\n updateSection(sectionId, (section) => {\n const fromIndex = section.blocks.findIndex((block) => block.id === blockId)\n if (fromIndex === -1) return null\n const blocks = [...section.blocks]\n const [block] = blocks.splice(fromIndex, 1)\n if (block === undefined) return null\n const toIndex = clampIndex(index, blocks.length)\n if (fromIndex === toIndex) return null\n blocks.splice(toIndex, 0, block)\n return { ...section, blocks }\n })\n },\n replaceSections: (sections) => {\n const next = sections.filter((section) => !section.id.startsWith('theme:'))\n if (\n next.length === snapshot.sections.length &&\n next.every((section, index) => section === snapshot.sections[index])\n ) {\n return\n }\n snapshot = { ...snapshot, sections: next }\n emit()\n },\n replaceSectionGroup: (handle, document) => {\n if (handle.trim() === '') return\n const sectionGroups = snapshot.sectionGroups ?? {}\n const nextDocument: ResolvedValuesDocument = {\n ...document,\n sections: document.sections.filter((section) => !section.id.startsWith('theme:')),\n }\n if (sectionGroups[handle] === nextDocument) return\n snapshot = {\n ...snapshot,\n sectionGroups: {\n ...sectionGroups,\n [handle]: nextDocument,\n },\n }\n emit()\n },\n }\n}\n","import type { SectionInstance, ValuesDocument } from '@kotao/storefront-contracts/theme'\n\nimport type { StorefrontAuthHeader } from '../storefront-client/client'\n\n/** A single leading slash, not protocol-relative (`//`/`\\`) — keeps the fetch same-origin (no exfil). */\nconst SAME_ORIGIN_PATH = /^\\/(?![/\\\\])/\nconst HTTPS_ENDPOINT = /^https:\\/\\//\nconst DEFAULT_ENDPOINT = '/api/storefront/settings'\n\n/**\n * The stored settings a theme hydrates from (#93): the layout document's ordered section instances +\n * the global theme tokens. Pass to {@link resolveThemeSettings} together with the theme's bundled\n * section schemas + presets to produce the merged {@link ThemeSettingsSnapshot}.\n */\nexport interface StoredThemeSettings {\n readonly shopId: string\n readonly sections: readonly SectionInstance[]\n readonly global: Record<string, unknown>\n readonly templates?: Readonly<Partial<Record<string, ValuesDocument>>>\n readonly sectionGroups?: Readonly<Partial<Record<string, ValuesDocument>>>\n}\n\nexport interface FetchThemeSettingsOptions {\n /** Injectable for tests; defaults to the global `fetch`. */\n readonly fetch?: typeof fetch\n /** Same-origin settings endpoint (default `/api/storefront/settings`). */\n readonly endpoint?: string\n /**\n * Explicit local-preview escape hatch for server-side theme workers that call the Kotao API directly.\n * Production themes should keep the default same-origin path.\n */\n readonly allowCrossOrigin?: boolean\n /** Optional server-side auth headers for local previews. Never pass browser-visible tokens here. */\n readonly auth?: {\n readonly token: string\n readonly tokenHeader?: StorefrontAuthHeader\n readonly siteId?: string\n }\n}\n\n/**\n * Fetch the shop's stored settings document via the same-origin `/api/storefront/settings` carve-out.\n * The shop is resolved server-side from the Gateway-signed context — the theme sends no id and no token.\n */\nexport async function fetchThemeSettings(\n options: FetchThemeSettingsOptions = {},\n): Promise<StoredThemeSettings> {\n const endpoint = options.endpoint ?? DEFAULT_ENDPOINT\n if (\n !SAME_ORIGIN_PATH.test(endpoint) &&\n !(options.allowCrossOrigin === true && HTTPS_ENDPOINT.test(endpoint))\n ) {\n throw new Error(`theme-settings endpoint must be a same-origin path, got '${endpoint}'`)\n }\n const doFetch = options.fetch ?? fetch\n const headers: Record<string, string> = {}\n if (options.auth !== undefined) {\n headers[options.auth.tokenHeader ?? 'x-kotao-storefront-token'] = options.auth.token\n if (options.auth.siteId !== undefined && options.auth.siteId.trim().length > 0) {\n headers['x-kotao-site-id'] = options.auth.siteId\n }\n }\n const init = Object.keys(headers).length > 0 ? { headers } : undefined\n const res = await doFetch(endpoint, init)\n if (!res.ok) throw new Error(`theme settings fetch failed: HTTP ${res.status}`)\n return (await res.json()) as StoredThemeSettings\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/seo/json-ld/common.ts","../src/seo/json-ld/product.ts","../src/seo/json-ld/local-business.ts","../src/seo/sitemap.ts","../src/seo/robots.ts","../src/seo/redirects.ts"],"names":[],"mappings":"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","file":"chunk-ISAFEOR2.js","sourcesContent":["import type { JsonLd } from '../types'\n\n/** Drop undefined / null / '' / empty-array fields so JSON-LD never renders empty or null values. */\nexport function compact(obj: Record<string, unknown>): JsonLd {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(obj)) {\n if (value === undefined || value === null || value === '') continue\n if (Array.isArray(value) && value.length === 0) continue\n out[key] = value\n }\n return out\n}\n\nconst SCHEMA = 'https://schema.org'\n\nexport interface BreadcrumbItem {\n readonly name: string\n readonly url: string\n}\n\nexport function breadcrumbListJsonLd(items: readonly BreadcrumbItem[]): JsonLd {\n return {\n '@context': SCHEMA,\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: item.name,\n item: item.url,\n })),\n }\n}\n\nexport interface OrganizationInput {\n readonly name: string\n readonly url?: string\n readonly logo?: string\n readonly sameAs?: readonly string[]\n}\n\nexport function organizationJsonLd(input: OrganizationInput): JsonLd {\n return compact({\n '@context': SCHEMA,\n '@type': 'Organization',\n name: input.name,\n url: input.url,\n logo: input.logo,\n sameAs: input.sameAs,\n })\n}\n\nexport interface WebSiteInput {\n readonly name: string\n readonly url: string\n /** A URL template with `{search_term_string}` to enable the sitelinks search box. */\n readonly searchUrlTemplate?: string\n}\n\nexport function websiteJsonLd(input: WebSiteInput): JsonLd {\n return compact({\n '@context': SCHEMA,\n '@type': 'WebSite',\n name: input.name,\n url: input.url,\n potentialAction:\n input.searchUrlTemplate !== undefined\n ? {\n '@type': 'SearchAction',\n target: { '@type': 'EntryPoint', urlTemplate: input.searchUrlTemplate },\n 'query-input': 'required name=search_term_string',\n }\n : undefined,\n })\n}\n","import type { JsonLd } from '../types'\nimport { compact } from './common'\n\nexport interface OfferInput {\n readonly price: string | number\n readonly currency: string\n /** schema.org ItemAvailability short name, e.g. 'InStock' | 'OutOfStock' | 'PreOrder'. */\n readonly availability?: string\n readonly url?: string\n readonly sku?: string\n}\n\nexport interface ProductJsonLdInput {\n readonly name: string\n readonly description?: string\n readonly sku?: string\n readonly image?: string | readonly string[]\n readonly brand?: string\n readonly url?: string\n readonly offer?: OfferInput\n}\n\nfunction offerJsonLd(offer: OfferInput): JsonLd {\n return compact({\n '@type': 'Offer',\n price: String(offer.price),\n priceCurrency: offer.currency,\n availability:\n offer.availability !== undefined ? `https://schema.org/${offer.availability}` : undefined,\n url: offer.url,\n sku: offer.sku,\n })\n}\n\nexport function productJsonLd(input: ProductJsonLdInput): JsonLd {\n return compact({\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: input.name,\n description: input.description,\n sku: input.sku,\n image: input.image,\n brand: input.brand !== undefined ? { '@type': 'Brand', name: input.brand } : undefined,\n url: input.url,\n offers: input.offer !== undefined ? offerJsonLd(input.offer) : undefined,\n })\n}\n","import type { JsonLd } from '../types'\nimport { compact } from './common'\n\nexport interface AddressInput {\n readonly streetAddress?: string\n readonly locality?: string\n readonly region?: string\n readonly postalCode?: string\n readonly country?: string\n}\n\nexport interface GeoInput {\n readonly latitude: number\n readonly longitude: number\n}\n\nexport interface LocalBusinessInput {\n readonly name: string\n /** schema.org type, e.g. 'LocalBusiness' | 'Restaurant' | 'Store'. Defaults to 'LocalBusiness'. */\n readonly type?: string\n readonly url?: string\n readonly telephone?: string\n readonly priceRange?: string\n readonly image?: string | readonly string[]\n readonly address?: AddressInput\n readonly geo?: GeoInput\n /** schema.org openingHours strings, e.g. ['Mo-Fr 09:00-17:00', 'Sa 10:00-14:00']. */\n readonly openingHours?: readonly string[]\n /** Restaurant fields (used when type is 'Restaurant'). */\n readonly servesCuisine?: string | readonly string[]\n readonly menu?: string\n readonly acceptsReservations?: boolean\n}\n\nfunction addressJsonLd(address: AddressInput): JsonLd {\n return compact({\n '@type': 'PostalAddress',\n streetAddress: address.streetAddress,\n addressLocality: address.locality,\n addressRegion: address.region,\n postalCode: address.postalCode,\n addressCountry: address.country,\n })\n}\n\n/**\n * LocalBusiness JSON-LD — the brick-and-mortar / restaurant differentiator beyond Shopify's defaults\n * (the SDK table calls this out). `restaurantJsonLd` is the same builder pinned to `Restaurant`.\n */\nexport function localBusinessJsonLd(input: LocalBusinessInput): JsonLd {\n return compact({\n '@context': 'https://schema.org',\n '@type': input.type ?? 'LocalBusiness',\n name: input.name,\n url: input.url,\n telephone: input.telephone,\n priceRange: input.priceRange,\n image: input.image,\n address: input.address !== undefined ? addressJsonLd(input.address) : undefined,\n geo:\n input.geo !== undefined\n ? {\n '@type': 'GeoCoordinates',\n latitude: input.geo.latitude,\n longitude: input.geo.longitude,\n }\n : undefined,\n openingHours: input.openingHours,\n servesCuisine: input.servesCuisine,\n hasMenu: input.menu,\n acceptsReservations: input.acceptsReservations,\n })\n}\n\nexport function restaurantJsonLd(input: Omit<LocalBusinessInput, 'type'>): JsonLd {\n return localBusinessJsonLd({ ...input, type: 'Restaurant' })\n}\n","/**\n * sitemap.xml + sitemap-index factories. The entry source is injected (the caller resolves it from the\n * Storefront API or a search index — an open question, NOT decided here): build the entry list, then call\n * these. The index shape exists so large catalogs split into child sitemaps rather than one document.\n */\n\nexport type ChangeFreq = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never'\n\nexport interface SitemapEntry {\n readonly loc: string\n readonly lastmod?: string\n readonly changefreq?: ChangeFreq\n /** 0.0–1.0 (clamped). */\n readonly priority?: number\n}\n\nexport interface SitemapIndexEntry {\n readonly loc: string\n readonly lastmod?: string\n}\n\nconst XML_CONTENT_TYPE = 'application/xml; charset=utf-8'\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;')\n}\n\nexport function buildSitemapXml(entries: readonly SitemapEntry[]): string {\n const urls = entries\n .map((entry) => {\n const parts = [` <loc>${escapeXml(entry.loc)}</loc>`]\n if (entry.lastmod !== undefined)\n parts.push(` <lastmod>${escapeXml(entry.lastmod)}</lastmod>`)\n if (entry.changefreq !== undefined)\n parts.push(` <changefreq>${entry.changefreq}</changefreq>`)\n if (entry.priority !== undefined) {\n parts.push(\n ` <priority>${Math.max(0, Math.min(1, entry.priority)).toFixed(1)}</priority>`,\n )\n }\n return ` <url>\\n${parts.join('\\n')}\\n </url>`\n })\n .join('\\n')\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${urls}\\n</urlset>\\n`\n}\n\nexport function buildSitemapIndexXml(sitemaps: readonly SitemapIndexEntry[]): string {\n const items = sitemaps\n .map((sitemap) => {\n const parts = [` <loc>${escapeXml(sitemap.loc)}</loc>`]\n if (sitemap.lastmod !== undefined)\n parts.push(` <lastmod>${escapeXml(sitemap.lastmod)}</lastmod>`)\n return ` <sitemap>\\n${parts.join('\\n')}\\n </sitemap>`\n })\n .join('\\n')\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${items}\\n</sitemapindex>\\n`\n}\n\nexport function sitemapResponse(entries: readonly SitemapEntry[]): Response {\n return new Response(buildSitemapXml(entries), { headers: { 'content-type': XML_CONTENT_TYPE } })\n}\n\nexport function sitemapIndexResponse(sitemaps: readonly SitemapIndexEntry[]): Response {\n return new Response(buildSitemapIndexXml(sitemaps), {\n headers: { 'content-type': XML_CONTENT_TYPE },\n })\n}\n","/**\n * robots.txt factory. Non-production / preview deployments auto-disallow ALL crawling; production allows\n * and references the sitemap. `production` comes from the SDK's stage signal (context.env), NEVER the\n * request Host/URL (stage-pinned-config rule, AGENTS.md §Environments).\n */\nexport interface RobotsOptions {\n readonly production: boolean\n readonly sitemapUrl?: string\n}\n\nexport function buildRobotsTxt(options: RobotsOptions): string {\n if (!options.production) {\n return 'User-agent: *\\nDisallow: /\\n'\n }\n const lines = ['User-agent: *', 'Allow: /']\n if (options.sitemapUrl !== undefined) lines.push(`Sitemap: ${options.sitemapUrl}`)\n return `${lines.join('\\n')}\\n`\n}\n\nexport function robotsResponse(options: RobotsOptions): Response {\n return new Response(buildRobotsTxt(options), {\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n })\n}\n","import type { StorefrontClient } from '../storefront-client/client'\n\n/**\n * Slug-change 301 handler. The redirect store is owned elsewhere (catalog/storefront-API work); this\n * owns only the request-handling shape. The lookup is injected: given the current path, it returns the\n * canonical target (or null). A self-redirect (target === path) returns null to avoid an immediate loop.\n */\nexport type RedirectLookup = (path: string) => Promise<string | null> | string | null\n\nexport interface RedirectOptions {\n /** 301 (permanent, default) or 302 (temporary). */\n readonly status?: 301 | 302\n}\n\nexport interface StorefrontRedirectOptions extends RedirectOptions {\n readonly request: Request\n readonly response: Response\n readonly storefront: StorefrontClient\n}\n\nexport async function resolveRedirect(\n lookup: RedirectLookup,\n path: string,\n options: RedirectOptions = {},\n): Promise<Response | null> {\n const target = await lookup(path)\n if (target === null || target === '' || target === path) return null\n return new Response(null, {\n status: options.status ?? 301,\n headers: { location: target },\n })\n}\n\nexport const URL_REDIRECT_QUERY = `#graphql\n query UrlRedirect($path: String!) {\n urlRedirect(path: $path) {\n target\n }\n }\n`\n\n/**\n * Look up `path` in the storefront-api's url_redirects and, if found, return a 301 Response.\n * Returns null when there is no redirect, a self-redirect, or the lookup query fails (so the caller\n * falls through to its normal 404 — a lookup failure must never become a 500).\n */\nexport async function resolveStorefrontRedirect(\n storefront: StorefrontClient,\n path: string,\n options: RedirectOptions = {},\n): Promise<Response | null> {\n let target: string | null = null\n try {\n const data = await storefront.query<{ urlRedirect: { target: string } | null }>(\n URL_REDIRECT_QUERY,\n { variables: { path } },\n )\n target = data.urlRedirect?.target ?? null\n } catch {\n return null\n }\n return resolveRedirect(() => target, path, options)\n}\n\n/**\n * Hydrogen-compatible 404 redirect helper. It preserves the original response when there is no\n * matching URL redirect, mirroring the server.ts pattern used by Hydrogen demo stores.\n */\nexport async function storefrontRedirect(options: StorefrontRedirectOptions): Promise<Response> {\n if (options.response.status !== 404) return options.response\n const path = new URL(options.request.url).pathname\n const redirectOptions = options.status === undefined ? {} : { status: options.status }\n return (\n (await resolveStorefrontRedirect(options.storefront, path, redirectOptions)) ?? options.response\n )\n}\n"]}