@kotao/storefront 0.2.9 → 0.2.11

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 (36) hide show
  1. package/README.md +20 -2
  2. package/dist/{chunk-PHEKJDFW.js → chunk-4OELOXCN.js} +17 -4
  3. package/dist/chunk-4OELOXCN.js.map +1 -0
  4. package/dist/{chunk-OGWYTYYS.js → chunk-GR4QJ7EC.js} +14 -22
  5. package/dist/chunk-GR4QJ7EC.js.map +1 -0
  6. package/dist/{chunk-S4HW5NH7.js → chunk-NRWS7HJ2.js} +65 -18
  7. package/dist/chunk-NRWS7HJ2.js.map +1 -0
  8. package/dist/{chunk-3YQXJNR7.js → chunk-PWVXOMDE.js} +18 -9
  9. package/dist/chunk-PWVXOMDE.js.map +1 -0
  10. package/dist/{client-D-M3Y1ul.d.ts → client-Bt8EEr-K.d.ts} +1 -1
  11. package/dist/editor.d.ts +1 -1
  12. package/dist/editor.js +1 -1
  13. package/dist/events-react.d.ts +2 -2
  14. package/dist/events.d.ts +2 -2
  15. package/dist/forms.d.ts +2 -2
  16. package/dist/image.d.ts +1 -1
  17. package/dist/image.js +1 -1
  18. package/dist/index.d.ts +19 -31
  19. package/dist/index.js +29 -86
  20. package/dist/index.js.map +1 -1
  21. package/dist/{pagination-DCrQk02Q.d.ts → pagination-CPgH3fAa.d.ts} +1 -1
  22. package/dist/product.d.ts +1 -1
  23. package/dist/react-router.d.ts +18 -6
  24. package/dist/react-router.js +43 -4
  25. package/dist/react-router.js.map +1 -1
  26. package/dist/react.d.ts +17 -7
  27. package/dist/react.js +50 -21
  28. package/dist/react.js.map +1 -1
  29. package/dist/{routing-BYc6JReb.d.ts → routing-hrvYWYEV.d.ts} +3 -1
  30. package/dist/{shop-keSWbFKV.d.ts → shop-wJdcWcVz.d.ts} +19 -9
  31. package/dist/{types-CSNXbGFW.d.ts → types-BTUTZXxJ.d.ts} +4 -3
  32. package/package.json +2 -2
  33. package/dist/chunk-3YQXJNR7.js.map +0 -1
  34. package/dist/chunk-OGWYTYYS.js.map +0 -1
  35. package/dist/chunk-PHEKJDFW.js.map +0 -1
  36. package/dist/chunk-S4HW5NH7.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { S as StoredThemeSettings } from './fetch-Bq_cc8_G.js';
2
2
  import { C as CacheStrategy, K as KotaoCache, S as StorefrontClient, b as StorefrontCacheInput } from './client-D51WdvYR.js';
3
3
  import { C as CartHandler } from './createCartHandler-DJEZW4o2.js';
4
- import { E as EventsClient } from './client-D-M3Y1ul.js';
4
+ import { E as EventsClient } from './client-Bt8EEr-K.js';
5
5
  import { H as HydrogenSession } from './types-C4yXcret.js';
6
6
 
7
7
  /**
@@ -182,6 +182,8 @@ type WithCache = <T>(key: string, strategy: CacheStrategy, fn: () => Promise<T>)
182
182
  declare const createWithCache: (cache: KotaoCache) => WithCache;
183
183
 
184
184
  interface KotaoEnv {
185
+ /** Content-addressed static asset tree supplied by the build service. */
186
+ KOTAO_ASSET_BASE?: string;
185
187
  STOREFRONT_API_ENDPOINT: string;
186
188
  STOREFRONT_API_TOKEN: string;
187
189
  STOREFRONT_API_TOKEN_HEADER?: string;
@@ -1,5 +1,4 @@
1
1
  import { j as CartFormAction } from './types-C4yXcret.js';
2
- import { ReactNode } from 'react';
3
2
 
4
3
  interface CartFormProps {
5
4
  readonly action: CartFormAction;
@@ -33,8 +32,8 @@ declare const CartForm: ((_props: CartFormProps) => null) & {
33
32
  };
34
33
 
35
34
  /**
36
- * Legacy consent state. The visible Storefront consent UI is disabled until the replacement flow ships,
37
- * so runtime analytics treats every bucket as effectively granted and ignores stale consent cookies.
35
+ * Shared native-CMP consent state. Unknown choices deny optional categories; the versioned cookie
36
+ * is the same contract used by dispatch and checkout.
38
37
  */
39
38
  type ConsentBucket = 'analytics' | 'marketing' | 'preferences';
40
39
  type ConsentState = Readonly<Record<ConsentBucket, boolean>>;
@@ -51,11 +50,22 @@ declare function buildConsentCookie(state: ConsentState): string;
51
50
  /** Read the consent cookie out of a `document.cookie`-style string. */
52
51
  declare function readConsentCookie(cookieHeader: string | null | undefined): ConsentState;
53
52
 
54
- type BoundNonceProvider = (props: {
55
- readonly children?: ReactNode;
56
- }) => ReactNode;
57
- /** Hydrogen-compatible nonce hook. */
58
- declare function useNonce(): string | undefined;
53
+ interface ContentSecurityPolicyShop {
54
+ readonly checkoutDomain?: string;
55
+ readonly storeDomain?: string;
56
+ }
57
+ interface CreateContentSecurityPolicyOptions {
58
+ readonly shop?: ContentSecurityPolicyShop;
59
+ readonly scriptSrc?: readonly string[];
60
+ readonly connectSrc?: readonly string[];
61
+ readonly imgSrc?: readonly string[];
62
+ readonly styleSrc?: readonly string[];
63
+ }
64
+ interface ContentSecurityPolicyResult {
65
+ readonly nonce: string;
66
+ readonly header: string;
67
+ }
68
+ declare function createContentSecurityPolicy(options?: CreateContentSecurityPolicyOptions): ContentSecurityPolicyResult;
59
69
 
60
70
  /**
61
71
  * Coarse visitor geo attached to pixel events — structurally the SDK's `context.geo` (a `Geo` value is
@@ -227,4 +237,4 @@ interface GetShopAnalyticsOptions {
227
237
  }
228
238
  declare function getShopAnalytics(options: GetShopAnalyticsOptions): ShopAnalytics;
229
239
 
230
- export { type AnalyticsContext as A, type BoundNonceProvider as B, type ConsentState as C, DENIED_CONSENT as D, type EventHandler as E, createAnalyticsBus as F, GRANTED_CONSENT as G, createAnalyticsEmitter as H, createPixelTransport as I, getShopAnalytics as J, isGranted as K, mergeConsent as L, parseConsent as M, readConsentCookie as N, serializeConsent as O, 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 AnalyticsDeviceClass as f, getCartFormInput as g, type AnalyticsEventMap as h, type AnalyticsEventName as i, type AnalyticsGeo as j, CONSENT_BUCKETS as k, CONSENT_COOKIE as l, CartForm as m, type CartFormProps as n, type CartViewedPayload as o, type CollectionViewedPayload as p, type GetShopAnalyticsOptions as q, type PixelEvent as r, type PixelSend as s, type PixelTransport as t, useNonce as u, type PixelTransportOptions as v, type ProductAddedToCartPayload as w, type ProductViewedPayload as x, type SearchViewedPayload as y, buildConsentCookie as z };
240
+ export { type AnalyticsContext as A, buildConsentCookie as B, type ConsentBucket as C, DENIED_CONSENT as D, type EventHandler as E, createAnalyticsBus as F, GRANTED_CONSENT as G, createAnalyticsEmitter as H, createContentSecurityPolicy as I, createPixelTransport as J, getShopAnalytics as K, isGranted as L, mergeConsent as M, parseConsent as N, readConsentCookie as O, type PageViewedPayload as P, serializeConsent as Q, getCartFormInput as R, type SearchViewedPayload as S, type ConsentState as a, ANALYTICS_EVENTS as b, type AnalyticsBus as c, type AnalyticsBusOptions as d, type AnalyticsDeviceClass as e, type AnalyticsEmitter as f, type AnalyticsEventMap 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 ContentSecurityPolicyResult as p, type ContentSecurityPolicyShop as q, type CreateContentSecurityPolicyOptions as r, type GetShopAnalyticsOptions as s, type PixelEvent as t, type PixelSend as u, type PixelTransport as v, type PixelTransportOptions as w, type ProductAddedToCartPayload as x, type ProductViewedPayload as y, type ShopAnalytics as z };
@@ -18,8 +18,9 @@ interface FormField {
18
18
  validation?: {
19
19
  minLength?: number;
20
20
  maxLength?: number;
21
- min?: number;
22
- max?: number;
21
+ /** Inclusive number or ISO calendar date (YYYY-MM-DD), matching the field kind. */
22
+ min?: number | string;
23
+ max?: number | string;
23
24
  };
24
25
  choices?: {
25
26
  value: string;
@@ -105,4 +106,4 @@ interface FormSubmission {
105
106
  updatedAt: string;
106
107
  }
107
108
 
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 };
109
+ export type { FormVersionSchema as F, PublicForm as P, FormAnswer as a, FormAnswers as b, FormBlock as c, FormField as d, FormFieldErrors as e, FormLocalizedText as f, FormRenderState as g, FormSubmission as h, FormVisibilityRule as i, PublicFormDefinition as j, PublicFormVersion as k };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kotao/storefront",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Kotao Storefronts theme SDK — framework-agnostic server runtime, React Router 8 binding, and authenticated source-authoritative React/Hydrogen editor bridge (ADR-0003/ADR-0047). Leaf package (ADR-0006).",
@@ -72,7 +72,7 @@
72
72
  "LICENSE"
73
73
  ],
74
74
  "dependencies": {
75
- "@kotao/storefront-contracts": "^0.2.4"
75
+ "@kotao/storefront-contracts": "^0.2.5"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "react": "^18.2.0 || ^19.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/image/image-url.ts","../src/image/image-attrs.ts"],"names":[],"mappings":"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","file":"chunk-3YQXJNR7.js","sourcesContent":["/**\n * Framework-agnostic image-URL builders for the storefront SDK (ADR-0003 §SDK image-URL contract).\n * They build Cloudflare `/cdn-cgi/image/<opts>/<path>` path-form transform URLs for `https://cdn.kotao.*`\n * bases, allowing the CDN to serve optimized renderings. Non-CDN URLs (external, data:, theme `/assets/...`)\n * pass through unchanged. Wire-native params — `width`/`height`/`fit`/`format`/`quality` — matching\n * Cloudflare's native image resizing; a Hydrogen-shaped React `<Image>` (with `crop`, `srcSet`/`sizes`\n * props) layers on top in the framework presets (#92). Pure string building, no framework imports, so it\n * stays in the SDK core.\n */\n\n/** Cloudflare-native resize mode (the Gateway's `fit` param). */\nexport type ImageFit = 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'\n/** Output format query value (the Gateway maps it to a content-type). */\nexport type ImageFormat = 'webp' | 'avif' | 'jpeg' | 'png'\n\nexport interface ImageUrlOptions {\n width?: number\n height?: number\n fit?: ImageFit\n format?: ImageFormat\n /** 1–100. */\n quality?: number\n}\n\nconst CDN_CGI = '/cdn-cgi/image/'\nconst isCdnUrl = (url: string): boolean => /^https?:\\/\\/cdn\\.kotao\\./.test(url)\n\n/** Strip a `/cdn-cgi/image/<opts>/` segment back to `{origin}{path}`, returning [bareUrl, existingOpts]. */\nfunction splitCdnCgi(url: string): { bare: string; opts: URLSearchParams } {\n const u = new URL(url)\n if (!u.pathname.startsWith(CDN_CGI)) return { bare: url, opts: new URLSearchParams() }\n const rest = u.pathname.slice(CDN_CGI.length)\n const slash = rest.indexOf('/')\n const optStr = slash === -1 ? rest : rest.slice(0, slash)\n const path = slash === -1 ? '' : rest.slice(slash) // includes leading '/'\n const opts = new URLSearchParams()\n for (const pair of optStr.split(',')) {\n const [k, v] = pair.split('=')\n if (k && v) opts.set(k, v)\n }\n return { bare: `${u.origin}${path}`, opts }\n}\n\nconst OPT_ORDER = ['width', 'height', 'fit', 'format', 'quality'] as const\n\n/**\n * Build a Cloudflare `/cdn-cgi/image/` path-form transform URL for a CDN image. For transformable\n * URLs (absolute `https://cdn.kotao.*`), emits `/cdn-cgi/image/{opts}/{path}` where `{opts}` is\n * the comma-joined key=value list (in stable order: width,height,fit,format,quality) and `{path}`\n * is the original pathname. Chaining replaces any existing `/cdn-cgi/image/` segment.\n * Non-CDN URLs pass through unchanged.\n */\nexport function imageUrl(url: string, options: ImageUrlOptions = {}): string {\n if (!isCdnUrl(url)) return url\n const { bare, opts } = splitCdnCgi(url)\n if (options.width != null) opts.set('width', String(options.width))\n if (options.height != null) opts.set('height', String(options.height))\n if (options.fit != null) opts.set('fit', options.fit)\n if (options.format != null) opts.set('format', options.format)\n if (options.quality != null) opts.set('quality', String(options.quality))\n const ordered = OPT_ORDER.filter((k) => opts.has(k)).map((k) => `${k}=${opts.get(k)}`)\n if (ordered.length === 0) return bare\n const u = new URL(bare)\n return `${u.origin}${CDN_CGI}${ordered.join(',')}${u.pathname}`\n}\n\nexport interface ImageSrcSetOptions extends Omit<ImageUrlOptions, 'width'> {\n /** Candidate widths (px) for the `srcset` descriptors. */\n widths: number[]\n}\n\n/**\n * Build a `srcset` string — one `url width` candidate per width — for responsive `<img srcset>` /\n * `<source srcset>`. Pair with a `sizes` attribute the theme controls.\n */\nexport function imageSrcSet(url: string, options: ImageSrcSetOptions): string {\n const { widths, ...rest } = options\n return widths.map((width) => `${imageUrl(url, { ...rest, width })} ${width}w`).join(', ')\n}\n","/**\n * Framework-agnostic `<img>` attribute builder (ADR-0003 §SDK image-URL contract). Turns the\n * Hydrogen-shaped `<Image>` surface (data/aspectRatio/crop/sizes/width/height) into concrete img\n * attributes (src, srcSet, sizes, width, height, style) over the `cdn.kotao.*` R2-native transform\n * contract, via the pure `imageUrl`/`imageSrcSet` builders. The React `<Image>` is a thin wrapper\n * that spreads these — so all logic lives here, in the core, unit-tested without a DOM.\n */\nimport { imageUrl, type ImageFormat, type ImageUrlOptions } from './image-url'\n\nexport interface ImageData {\n url: string\n altText?: string | null\n width?: number | null\n height?: number | null\n}\n\n/** Hydrogen crop region. v1 maps any crop to a fill+crop (Cloudflare `fit=cover`); positional gravity\n * (top/bottom/left/right) is not on the wire yet, so all values currently centre-crop. */\nexport type ImageCrop = 'center' | 'top' | 'bottom' | 'left' | 'right'\n\n// Optionals are `| undefined` on purpose: the React <Image> feeds them straight from optional props,\n// so explicit `undefined` must be assignable under exactOptionalPropertyTypes.\nexport interface ImageAttrsOptions {\n data: ImageData\n /** Override `data.url`. */\n src?: string | undefined\n /** \"width/height\" (e.g. \"4/5\") — sets the intrinsic box for CLS-free layout. */\n aspectRatio?: string | undefined\n crop?: ImageCrop | undefined\n /** The responsive `sizes` attribute; the theme owns it. */\n sizes?: string | undefined\n /** Fixed render width (px). With it, the srcSet becomes 1x/2x/3x instead of the responsive set. */\n width?: number | undefined\n height?: number | undefined\n loading?: ('eager' | 'lazy') | undefined\n /** srcSet candidate widths; defaults to a responsive set (or 1x/2x/3x when `width` is fixed). */\n srcSetWidths?: number[] | undefined\n format?: ImageFormat | undefined\n quality?: number | undefined\n}\n\nexport interface ImageAttrs {\n src: string\n srcSet: string\n sizes?: string\n width?: number\n height?: number\n alt: string\n loading: 'eager' | 'lazy'\n style?: { aspectRatio: string }\n}\n\n/** Responsive widths when no fixed `width` is given (mirrors Hydrogen's default interval set). */\nconst DEFAULT_WIDTHS = [320, 640, 768, 1024, 1280, 1536]\n/** The `src` fallback width for browsers that ignore srcSet. */\nconst FALLBACK_WIDTH = 1280\n\nexport function buildImageAttrs(options: ImageAttrsOptions): ImageAttrs {\n const url = options.src ?? options.data.url\n\n const widths =\n options.srcSetWidths ??\n (options.width ? [options.width, options.width * 2, options.width * 3] : DEFAULT_WIDTHS)\n\n const aspectRatio =\n options.aspectRatio ??\n (options.data.width != null && options.data.height != null\n ? `${options.data.width}/${options.data.height}`\n : undefined)\n\n // Shared transform params — set only when present (exactOptionalPropertyTypes forbids explicit\n // `undefined`). A crop maps to Cloudflare `fit=cover` (fill+crop).\n const transform: ImageUrlOptions = {}\n if (options.crop) transform.fit = 'cover'\n if (options.format != null) transform.format = options.format\n if (options.quality != null) transform.quality = options.quality\n\n // For a fixed box (width+height), the srcSet candidates must scale BOTH dimensions so every DPR\n // variant keeps the same crop aspect (a uniform height would distort the 2x/3x candidates).\n const heightPerWidth =\n options.width != null && options.height != null ? options.height / options.width : null\n const candidate = (w: number): ImageUrlOptions => {\n const opts: ImageUrlOptions = { ...transform, width: w }\n if (heightPerWidth != null) opts.height = Math.round(w * heightPerWidth)\n return opts\n }\n\n const srcOptions: ImageUrlOptions = { ...transform, width: options.width ?? FALLBACK_WIDTH }\n if (options.height != null) srcOptions.height = options.height\n\n return {\n src: imageUrl(url, srcOptions),\n srcSet: widths.map((w) => `${imageUrl(url, candidate(w))} ${w}w`).join(', '),\n ...(options.sizes != null ? { sizes: options.sizes } : {}),\n ...(options.width != null ? { width: options.width } : {}),\n ...(options.height != null ? { height: options.height } : {}),\n alt: options.data.altText ?? '',\n loading: options.loading ?? 'lazy',\n ...(aspectRatio != null ? { style: { aspectRatio } } : {}),\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cart/actions.ts","../src/analytics/consent.ts"],"names":[],"mappings":"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","file":"chunk-OGWYTYYS.js","sourcesContent":["export const CART_FORM_ACTIONS = Object.freeze({\n LinesAdd: 'LinesAdd',\n LinesUpdate: 'LinesUpdate',\n LinesRemove: 'LinesRemove',\n DiscountCodesUpdate: 'DiscountCodesUpdate',\n GiftCardCodesAdd: 'GiftCardCodesAdd',\n GiftCardCodesRemove: 'GiftCardCodesRemove',\n BuyerIdentityUpdate: 'BuyerIdentityUpdate',\n NoteUpdate: 'NoteUpdate',\n AttributesUpdate: 'AttributesUpdate',\n MetafieldsSet: 'MetafieldsSet',\n MetafieldsDelete: 'MetafieldsDelete',\n DeliveryAddressesReplace: 'DeliveryAddressesReplace',\n SelectedDeliveryOptionsUpdate: 'SelectedDeliveryOptionsUpdate',\n} as const)\n\nexport type CartFormAction = (typeof CART_FORM_ACTIONS)[keyof typeof CART_FORM_ACTIONS]\n","/**\n * Legacy consent state. The visible Storefront consent UI is disabled until the replacement flow ships,\n * so runtime analytics treats every bucket as effectively granted and ignores stale consent cookies.\n */\nexport type ConsentBucket = 'analytics' | 'marketing' | 'preferences'\nexport type ConsentState = Readonly<Record<ConsentBucket, boolean>>\n\nexport const CONSENT_BUCKETS: readonly ConsentBucket[] = ['analytics', 'marketing', 'preferences']\nexport const DENIED_CONSENT: ConsentState = Object.freeze({\n analytics: false,\n marketing: false,\n preferences: false,\n})\nexport const GRANTED_CONSENT: ConsentState = Object.freeze({\n analytics: true,\n marketing: true,\n preferences: true,\n})\n\nexport const CONSENT_COOKIE = 'kotao_consent'\nconst CONSENT_MAX_AGE = 60 * 60 * 24 * 180 // ~180 days\n\nexport function isGranted(state: ConsentState, bucket: ConsentBucket): boolean {\n void state\n void bucket\n return true\n}\n\nexport function mergeConsent(state: ConsentState, patch: Partial<ConsentState>): ConsentState {\n return { ...state, ...patch }\n}\n\nexport function serializeConsent(state: ConsentState): string {\n return JSON.stringify({\n analytics: state.analytics,\n marketing: state.marketing,\n preferences: state.preferences,\n })\n}\n\nexport function parseConsent(value: string | null | undefined): ConsentState {\n if (value === null || value === undefined || value === '') return GRANTED_CONSENT\n try {\n const parsed = JSON.parse(value) as Partial<Record<ConsentBucket, unknown>>\n return {\n analytics: parsed.analytics === true,\n marketing: parsed.marketing === true,\n preferences: parsed.preferences === true,\n }\n } catch {\n return GRANTED_CONSENT\n }\n}\n\n/** The `document.cookie` string to persist consent (Secure, SameSite=Lax, Path=/, ~180d). */\nexport function buildConsentCookie(state: ConsentState): string {\n const value = encodeURIComponent(serializeConsent(state))\n return `${CONSENT_COOKIE}=${value}; Path=/; Max-Age=${CONSENT_MAX_AGE}; SameSite=Lax; Secure`\n}\n\n/** Read the consent cookie out of a `document.cookie`-style string. */\nexport function readConsentCookie(cookieHeader: string | null | undefined): ConsentState {\n if (cookieHeader === null || cookieHeader === undefined || cookieHeader === '')\n return GRANTED_CONSENT\n const match = cookieHeader\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith(`${CONSENT_COOKIE}=`))\n if (match === undefined) return GRANTED_CONSENT\n void match\n return GRANTED_CONSENT\n}\n"]}