@kotao/storefront 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/cart-documents-CwwRTvm8.d.ts +21 -0
  4. package/dist/cart-documents.d.ts +2 -0
  5. package/dist/cart-documents.js +3 -0
  6. package/dist/cart-documents.js.map +1 -0
  7. package/dist/chunk-2IWNUDSZ.js +104 -0
  8. package/dist/chunk-2IWNUDSZ.js.map +1 -0
  9. package/dist/chunk-3YQXJNR7.js +70 -0
  10. package/dist/chunk-3YQXJNR7.js.map +1 -0
  11. package/dist/chunk-5QOFKGKD.js +51 -0
  12. package/dist/chunk-5QOFKGKD.js.map +1 -0
  13. package/dist/chunk-GCQI6OKH.js +239 -0
  14. package/dist/chunk-GCQI6OKH.js.map +1 -0
  15. package/dist/chunk-ISAFEOR2.js +214 -0
  16. package/dist/chunk-ISAFEOR2.js.map +1 -0
  17. package/dist/chunk-MYDKIJRH.js +81 -0
  18. package/dist/chunk-MYDKIJRH.js.map +1 -0
  19. package/dist/chunk-OGWYTYYS.js +72 -0
  20. package/dist/chunk-OGWYTYYS.js.map +1 -0
  21. package/dist/chunk-PXZ737N3.js +439 -0
  22. package/dist/chunk-PXZ737N3.js.map +1 -0
  23. package/dist/chunk-QOEEUTHQ.js +110 -0
  24. package/dist/chunk-QOEEUTHQ.js.map +1 -0
  25. package/dist/chunk-U5677AEA.js +1690 -0
  26. package/dist/chunk-U5677AEA.js.map +1 -0
  27. package/dist/chunk-XMTWFJCZ.js +138 -0
  28. package/dist/chunk-XMTWFJCZ.js.map +1 -0
  29. package/dist/client-D9YYA2w6.d.ts +159 -0
  30. package/dist/customer-account-api-types.d.ts +144 -0
  31. package/dist/customer-account-api-types.js +3 -0
  32. package/dist/customer-account-api-types.js.map +1 -0
  33. package/dist/editor.d.ts +112 -0
  34. package/dist/editor.js +3 -0
  35. package/dist/editor.js.map +1 -0
  36. package/dist/image.d.ts +89 -0
  37. package/dist/image.js +3 -0
  38. package/dist/image.js.map +1 -0
  39. package/dist/index.d.ts +183 -0
  40. package/dist/index.js +208 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/pagination-CPgH3fAa.d.ts +77 -0
  43. package/dist/product.d.ts +105 -0
  44. package/dist/product.js +4 -0
  45. package/dist/product.js.map +1 -0
  46. package/dist/react-router.d.ts +29 -0
  47. package/dist/react-router.js +33 -0
  48. package/dist/react-router.js.map +1 -0
  49. package/dist/react.d.ts +299 -0
  50. package/dist/react.js +1813 -0
  51. package/dist/react.js.map +1 -0
  52. package/dist/routing-BfUp49o8.d.ts +302 -0
  53. package/dist/seo.d.ts +155 -0
  54. package/dist/seo.js +4 -0
  55. package/dist/seo.js.map +1 -0
  56. package/dist/settings.d.ts +87 -0
  57. package/dist/settings.js +3 -0
  58. package/dist/settings.js.map +1 -0
  59. package/dist/shop-DkQmro-Y.d.ts +202 -0
  60. package/dist/store-DQK1nhob.d.ts +67 -0
  61. package/dist/storefront-api-types.d.ts +312 -0
  62. package/dist/storefront-api-types.js +3 -0
  63. package/dist/storefront-api-types.js.map +1 -0
  64. package/dist/types-BDhHWZsP.d.ts +77 -0
  65. package/dist/types-Df3bGbKU.d.ts +167 -0
  66. package/package.json +82 -0
@@ -0,0 +1,183 @@
1
+ export { a as CacheCustom, b as CacheLong, c as CacheNone, d as CacheShort, C as CacheStrategy, H as HydrogenStorefrontClientOptions, K as KotaoCache, e as Storefront, f as StorefrontAuthHeader, S as StorefrontClient, g as StorefrontClientOptions, h as StorefrontClientResult, i as StorefrontDefaultCache, j as StorefrontQueryOptions, k as createCache, l as createStorefrontClient, m as generateCacheControlHeader } from './client-D9YYA2w6.js';
2
+ import { a as Cart } from './types-Df3bGbKU.js';
3
+ export { k as CART_FORM_ACTIONS, c as CartAttribute, l as CartBuyerIdentity, d as CartBuyerIdentityInput, i as CartDeliveryAddress, C as CartFormAction, m as CartLine, b as CartLineInput, f as CartLineUpdateInput, g as CartMetafield, h as CartMetafieldIdentifier, e as CartMutationResult, j as CartSelectedDeliveryOption, n as CartUserError, o as HydrogenCart, p as HydrogenCartCreateInput, q as HydrogenCartMutationResult, H as HydrogenSession, r as HydrogenSessionData } from './types-Df3bGbKU.js';
4
+ import { C as ConsentBucket, a as ConsentState, B as BoundNonceProvider } from './shop-DkQmro-Y.js';
5
+ export { A as ANALYTICS_EVENTS, b as AnalyticsBus, c as AnalyticsBusOptions, d as AnalyticsContext, e as AnalyticsEmitter, f as AnalyticsEventMap, g as AnalyticsEventName, h as AnalyticsGeo, i as CONSENT_BUCKETS, j as CONSENT_COOKIE, k as CartForm, l as CartFormProps, m as CartViewedPayload, n as CollectionViewedPayload, D as DENIED_CONSENT, E as EventHandler, G as GRANTED_CONSENT, o as GetShopAnalyticsOptions, P as PageViewedPayload, p as PixelEvent, q as PixelSend, r as PixelTransport, s as PixelTransportOptions, t as ProductAddedToCartPayload, u as ProductViewedPayload, S as SearchViewedPayload, v as ShopAnalytics, w as buildConsentCookie, x as createAnalyticsBus, y as createAnalyticsEmitter, z as createPixelTransport, F as getShopAnalytics, H as isGranted, I as mergeConsent, J as parseConsent, K as readConsentCookie, L as serializeConsent } from './shop-DkQmro-Y.js';
6
+ import { C as CartDispatchAction, c as CustomerAccountClient } from './routing-BfUp49o8.js';
7
+ export { A as Address, d as AddressConnection, e as CartGetId, f as CartHandler, g as CartSetId, h as CreateCartHandlerOptions, i as CustomerAccount, j as CustomerAccountError, k as CustomerAccountErrorCode, G as GEO_HEADER, l as GEO_SIGNATURE_HEADER, m as Geo, H as HydrogenCartHandlerOptions, a as KotaoContext, K as KotaoEnv, b as KotaoSession, L as LocalizePathOptions, M as Money, O as Order, n as OrderConnection, o as OrderPageInfo, p as OrdersOptions, R as ROUTING_LOCALE_HEADER, q as ROUTING_ORIGINAL_PATHNAME_HEADER, r as RequestCartHandlerOptions, t as RequestLoginCodeInput, u as RequestLoginCodeResult, S as StrippedLocalePath, V as VerifyLoginCodeInput, W as WithCache, v as cartGetIdDefault, w as cartSetIdDefault, x as createCartHandler, y as createKotaoContext, z as createWithCache, B as isLocalePathSegment, D as localeFromAcceptLanguage, E as localeFromPathname, F as localeFromRequest, I as localizePath, J as originalPathnameFromRequest, N as signGeo, P as stripLocalePrefixFromPathname, s as stripLocalePrefixFromRequest, Q as verifyGeo } from './routing-BfUp49o8.js';
8
+ import { T as ThemeSettingsSnapshot } from './store-DQK1nhob.js';
9
+ export { R as ResolvedBlock, a as ResolvedSection, b as ResolvedSettings, c as ThemeSettingsStore, d as createThemeSettingsStore } from './store-DQK1nhob.js';
10
+ export { ImageAttrs, ImageAttrsOptions, ImageCrop, ImageData, ImageFit, ImageFormat, ImageSrcSetOptions, ImageUrlOptions, buildImageAttrs, imageSrcSet, imageUrl } from './image.js';
11
+ export { C as Connection, a as ConnectionEdge, F as FlattenableConnection, P as PageInfo, b as PaginationInfo, c as PaginationOptions, d as PaginationVariables, f as flattenConnection, g as getPaginationInfo, e as getPaginationVariables } from './pagination-CPgH3fAa.js';
12
+ export { MappedProductOptions, ProductLike, ProductOptionDef, ProductOptionState, ProductOptionValueState, SelectedOption, SelectedOptionsMap, VariantLike, VariantSelectionLike, getAdjacentAndFirstAvailableVariants, getProductOptions, getSelectedProductOptions, getSelectedVariant, mapSelectedProductOptionToObject, useOptimisticVariant, useSelectedOptionInUrlParam } from './product.js';
13
+ export { FetchThemeSettingsOptions, ResolveThemeSettingsArgs, StoredThemeSettings, fetchThemeSettings, mergeSection, mergeSettings, resolveThemeSettings } from './settings.js';
14
+ export { AddressInput, BreadcrumbItem, ChangeFreq, GeoInput, LocalBusinessInput, OfferInput, OrganizationInput, ProductJsonLdInput, RedirectLookup, RedirectOptions, RobotsOptions, SitemapEntry, SitemapIndexEntry, StorefrontRedirectOptions, URL_REDIRECT_QUERY, WebSiteInput, breadcrumbListJsonLd, buildRobotsTxt, buildSitemapIndexXml, buildSitemapXml, getSeoMeta, localBusinessJsonLd, organizationJsonLd, productJsonLd, resolveRedirect, resolveStorefrontRedirect, restaurantJsonLd, robotsResponse, serializeJsonLd, sitemapIndexResponse, sitemapResponse, storefrontRedirect, websiteJsonLd } from './seo.js';
15
+ export { A as Alternate, J as JsonLd, O as OpenGraph, a as OpenGraphImage, S as SeoConfig, b as SeoDescriptor, T as TwitterCard } from './types-BDhHWZsP.js';
16
+ export { CreateStorefrontEditorBridgeArgs, CreateStorefrontEditorHostBridgeArgs, StorefrontEditorBlockSchema, StorefrontEditorBridge, StorefrontEditorHostBridge, StorefrontEditorMessageEvent, StorefrontEditorMessageSource, StorefrontEditorMessageTarget, StorefrontEditorReady, StorefrontEditorSectionPreset, StorefrontEditorSectionPresetBlock, StorefrontEditorSectionSchema, StorefrontEditorSelection, StorefrontEditorSettingInput, StorefrontEditorSettingOption, createStorefrontEditorBridge, createStorefrontEditorHostBridge } from './editor.js';
17
+ import 'react';
18
+ import './storefront-api-types.js';
19
+ import '@kotao/storefront-contracts/theme';
20
+
21
+ declare const CART_ID_COOKIE = "kotao_cart";
22
+ declare function readCartIdCookie(cookieHeader: string | null | undefined): string | null;
23
+ declare function buildCartIdCookie(cartId: string): string;
24
+
25
+ declare function applyOptimisticCartAction(cart: Cart, action: CartDispatchAction): Cart;
26
+ /**
27
+ * Pure optimistic helper for React integrations. A React wrapper can call this from `useOptimistic`;
28
+ * keeping the reducer pure lets non-React tests exercise the exact overlay behavior.
29
+ */
30
+ declare function useOptimisticCart(cart: Cart, action: CartDispatchAction | null): Cart;
31
+
32
+ declare const CHECKOUT_CONSENT_PARAM_PREFIX = "consent_";
33
+ declare const CHECKOUT_CONSENT_CATEGORIES: readonly ConsentBucket[];
34
+ declare function encodeCheckoutConsent(state: ConsentState): URLSearchParams;
35
+ declare function decodeCheckoutConsent(input: URLSearchParams | string | URL): ConsentState;
36
+
37
+ interface ResolveCheckoutUrlOptions {
38
+ readonly checkoutUrl: string;
39
+ readonly consent: ConsentState;
40
+ readonly shopDomain?: string;
41
+ readonly requestUrl?: string | URL | Request;
42
+ }
43
+ declare function resolveCheckoutUrl(options: ResolveCheckoutUrlOptions): string;
44
+
45
+ interface CheckoutDesignTokens {
46
+ readonly logo: {
47
+ readonly src: string;
48
+ readonly altText: string | null;
49
+ } | null;
50
+ readonly colors: {
51
+ readonly accent: string;
52
+ readonly background: string;
53
+ readonly foreground: string;
54
+ };
55
+ readonly fonts: {
56
+ readonly body: string;
57
+ readonly heading: string;
58
+ };
59
+ }
60
+ declare const CHECKOUT_DESIGN_TOKEN_INPUTS: readonly [{
61
+ readonly type: "url";
62
+ readonly name: "checkoutLogoUrl";
63
+ readonly label: "Checkout logo URL";
64
+ }, {
65
+ readonly type: "text";
66
+ readonly name: "checkoutLogoAlt";
67
+ readonly label: "Checkout logo alt text";
68
+ readonly defaultValue: "";
69
+ }, {
70
+ readonly type: "color";
71
+ readonly name: "checkoutColorAccent";
72
+ readonly label: "Checkout accent color";
73
+ readonly defaultValue: "#005bd3";
74
+ }, {
75
+ readonly type: "color";
76
+ readonly name: "checkoutColorBackground";
77
+ readonly label: "Checkout background color";
78
+ readonly defaultValue: "#ffffff";
79
+ }, {
80
+ readonly type: "color";
81
+ readonly name: "checkoutColorForeground";
82
+ readonly label: "Checkout foreground color";
83
+ readonly defaultValue: "#111827";
84
+ }, {
85
+ readonly type: "font";
86
+ readonly name: "checkoutFontBody";
87
+ readonly label: "Checkout body font";
88
+ readonly defaultValue: "Inter";
89
+ }, {
90
+ readonly type: "font";
91
+ readonly name: "checkoutFontHeading";
92
+ readonly label: "Checkout heading font";
93
+ readonly defaultValue: "Inter";
94
+ }];
95
+ declare function extractCheckoutDesignTokens(snapshot: Pick<ThemeSettingsSnapshot, 'global'>): CheckoutDesignTokens;
96
+
97
+ interface ContentSecurityPolicyShop {
98
+ readonly checkoutDomain?: string;
99
+ readonly storeDomain?: string;
100
+ }
101
+ interface CreateContentSecurityPolicyOptions {
102
+ readonly shop?: ContentSecurityPolicyShop;
103
+ readonly scriptSrc?: readonly string[];
104
+ readonly connectSrc?: readonly string[];
105
+ readonly imgSrc?: readonly string[];
106
+ readonly styleSrc?: readonly string[];
107
+ }
108
+ interface ContentSecurityPolicyResult {
109
+ readonly nonce: string;
110
+ readonly header: string;
111
+ readonly NonceProvider: BoundNonceProvider;
112
+ }
113
+ declare function createContentSecurityPolicy(options?: CreateContentSecurityPolicyOptions): ContentSecurityPolicyResult;
114
+
115
+ declare const LOCALIZATION_COOKIE = "kotao_localization";
116
+ declare function readLocalizationCookie(cookieHeader: string | null | undefined): {
117
+ country: string | null;
118
+ locale: string | null;
119
+ };
120
+ declare function buildLocalizationCookie(sel: {
121
+ country?: string | null;
122
+ locale?: string | null;
123
+ }): string;
124
+
125
+ declare function handleLocalizationPost(request: Request): Promise<Response>;
126
+
127
+ declare function encodeConsent(state: ConsentState): URLSearchParams;
128
+ declare function decodeConsent(params: URLSearchParams): ConsentState;
129
+ declare function appendConsentToCheckoutUrl(checkoutUrl: string, state: ConsentState): string;
130
+
131
+ interface CustomerAccountClientOptions {
132
+ /** The inbound theme request — supplies the session cookie to forward + the shop host. */
133
+ readonly request: Request;
134
+ /** Injectable for tests; defaults to the global `fetch`. */
135
+ readonly fetch?: typeof fetch | undefined;
136
+ /** Same-origin proxy base (#85); must be an absolute same-origin path. Defaults to `/api/storefront`. */
137
+ readonly proxyBase?: string | undefined;
138
+ /** Hydrogen server compatibility: accepted for original server.ts call sites; Kotao proxies do not need it. */
139
+ readonly waitUntil?: ((promise: Promise<unknown>) => void) | undefined;
140
+ /** Hydrogen server compatibility: accepted for original server.ts call sites; Kotao proxies do not need it. */
141
+ readonly session?: unknown;
142
+ /** Hydrogen server compatibility: accepted for original server.ts call sites; Kotao proxies do not need it. */
143
+ readonly customerAccountId?: string | undefined;
144
+ /** Hydrogen server compatibility: accepted for original server.ts call sites; Kotao proxies do not need it. */
145
+ readonly shopId?: string | undefined;
146
+ }
147
+ /**
148
+ * Theme-facing customer-account client (#91), mirroring Hydrogen's `createCustomerAccount`. Kotao
149
+ * sessions go through the same-origin `/api/storefront` proxy, while original Hydrogen contexts can
150
+ * keep using Shopify Customer Account OAuth/session data when `shopId`, `customerAccountId`, and a
151
+ * Hydrogen AppSession are supplied. Untrusted-mode safe: no `request.cf`, no `caches.default`.
152
+ */
153
+ declare function createCustomerAccountClient(opts: CustomerAccountClientOptions): CustomerAccountClient;
154
+
155
+ /**
156
+ * Per-shop customer session cookie (#91). Sessions are scoped to the shop's custom hostname and MUST
157
+ * NOT be shared across shops or up to `.kotao.com` (ADR-0004, design §"Checkout handoff") — cross-shop
158
+ * session bleed is the footgun this module exists to prevent. The cookie name + attributes are defined
159
+ * here once so every reader/writer (theme runtime, proxy callback) agrees on the shape.
160
+ */
161
+ declare const CUSTOMER_SESSION_COOKIE = "kotao_customer_session";
162
+ declare const DEFAULT_SESSION_MAX_AGE: number;
163
+ interface SessionCookieAttrs {
164
+ /**
165
+ * The shop's custom hostname. `Domain` is pinned to it host-scoped (no leading dot), so the cookie
166
+ * matches only this shop's host — never another shop and never the `.kotao.com` platform apex.
167
+ */
168
+ readonly hostname: string;
169
+ readonly maxAgeSeconds?: number;
170
+ }
171
+ /** The shop custom host of the current request — the session isolation boundary. */
172
+ declare function shopHostname(request: Request): string;
173
+ /**
174
+ * Build the session cookie: `HttpOnly` (theme JS can never read the token), `Secure`, `SameSite=Lax`
175
+ * (survives the IdP login-redirect round trip), `Domain` pinned host-scoped to the shop.
176
+ */
177
+ declare function buildCustomerSessionCookie(token: string, attrs: SessionCookieAttrs): string;
178
+ /** Expire the session cookie (logout) on the same host scope. */
179
+ declare function clearCustomerSessionCookie(attrs: SessionCookieAttrs): string;
180
+ /** Read the session token out of a server-side `Cookie` header; null if absent or empty. */
181
+ declare function readCustomerSessionToken(cookieHeader: string | null | undefined): string | null;
182
+
183
+ export { CART_ID_COOKIE, CHECKOUT_CONSENT_CATEGORIES, CHECKOUT_CONSENT_PARAM_PREFIX, CHECKOUT_DESIGN_TOKEN_INPUTS, CUSTOMER_SESSION_COOKIE, Cart, CartDispatchAction, type CheckoutDesignTokens, ConsentBucket, ConsentState, type ContentSecurityPolicyResult, type ContentSecurityPolicyShop, type CreateContentSecurityPolicyOptions, CustomerAccountClient, type CustomerAccountClientOptions, DEFAULT_SESSION_MAX_AGE, LOCALIZATION_COOKIE, type ResolveCheckoutUrlOptions, type SessionCookieAttrs, ThemeSettingsSnapshot, appendConsentToCheckoutUrl, applyOptimisticCartAction, buildCartIdCookie, buildCustomerSessionCookie, buildLocalizationCookie, clearCustomerSessionCookie, createContentSecurityPolicy, createCustomerAccountClient, decodeCheckoutConsent, decodeConsent, encodeCheckoutConsent, encodeConsent, extractCheckoutDesignTokens, handleLocalizationPost, readCartIdCookie, readCustomerSessionToken, readLocalizationCookie, resolveCheckoutUrl, shopHostname, useOptimisticCart };
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ export { URL_REDIRECT_QUERY, breadcrumbListJsonLd, buildRobotsTxt, buildSitemapIndexXml, buildSitemapXml, localBusinessJsonLd, organizationJsonLd, productJsonLd, resolveRedirect, resolveStorefrontRedirect, restaurantJsonLd, robotsResponse, sitemapIndexResponse, sitemapResponse, storefrontRedirect, websiteJsonLd } from './chunk-ISAFEOR2.js';
2
+ export { createThemeSettingsStore, fetchThemeSettings, mergeSection, mergeSettings, resolveThemeSettings } from './chunk-GCQI6OKH.js';
3
+ export { createStorefrontEditorBridge, createStorefrontEditorHostBridge } from './chunk-PXZ737N3.js';
4
+ export { getAdjacentAndFirstAvailableVariants, getProductOptions, getSelectedProductOptions, getSelectedVariant, mapSelectedProductOptionToObject, useOptimisticVariant, useSelectedOptionInUrlParam } from './chunk-QOEEUTHQ.js';
5
+ import { createNonceProvider } from './chunk-XMTWFJCZ.js';
6
+ export { CartForm, createAnalyticsBus, createAnalyticsEmitter, createPixelTransport } from './chunk-XMTWFJCZ.js';
7
+ export { getSeoMeta, serializeJsonLd } from './chunk-MYDKIJRH.js';
8
+ export { buildImageAttrs, imageSrcSet, imageUrl } from './chunk-3YQXJNR7.js';
9
+ export { flattenConnection, getPaginationInfo, getPaginationVariables } from './chunk-5QOFKGKD.js';
10
+ import { localizePath, buildLocalizationCookie } from './chunk-U5677AEA.js';
11
+ export { CART_ID_COOKIE, CHECKOUT_CONSENT_CATEGORIES, CHECKOUT_CONSENT_PARAM_PREFIX, CHECKOUT_DESIGN_TOKEN_INPUTS, CUSTOMER_SESSION_COOKIE, CacheCustom, CacheLong, CacheNone, CacheShort, CustomerAccountError, DEFAULT_SESSION_MAX_AGE, GEO_HEADER, GEO_SIGNATURE_HEADER, LOCALIZATION_COOKIE, ROUTING_LOCALE_HEADER, ROUTING_ORIGINAL_PATHNAME_HEADER, StorefrontDefaultCache, buildCartIdCookie, buildCustomerSessionCookie, buildLocalizationCookie, cartGetIdDefault, cartSetIdDefault, clearCustomerSessionCookie, createCache, createCartHandler, createCustomerAccountClient, createKotaoContext, createStorefrontClient, createWithCache, decodeCheckoutConsent, encodeCheckoutConsent, extractCheckoutDesignTokens, generateCacheControlHeader, isLocalePathSegment, localeFromAcceptLanguage, localeFromPathname, localeFromRequest, localizePath, originalPathnameFromRequest, readCartIdCookie, readCustomerSessionToken, readLocalizationCookie, resolveCheckoutUrl, shopHostname, signGeo, stripLocalePrefixFromPathname, stripLocalePrefixFromRequest, verifyGeo } from './chunk-U5677AEA.js';
12
+ import { CART_FORM_ACTIONS, CONSENT_BUCKETS } from './chunk-OGWYTYYS.js';
13
+ export { CART_FORM_ACTIONS, CONSENT_BUCKETS, CONSENT_COOKIE, DENIED_CONSENT, GRANTED_CONSENT, buildConsentCookie, isGranted, mergeConsent, parseConsent, readConsentCookie, serializeConsent } from './chunk-OGWYTYYS.js';
14
+
15
+ // src/cart/useOptimisticCart.ts
16
+ var recalculate = (cart, lines) => ({
17
+ ...cart,
18
+ lines,
19
+ totalQuantity: lines.reduce((sum, line) => sum + line.quantity, 0)
20
+ });
21
+ function applyOptimisticCartAction(cart, action) {
22
+ switch (action.action) {
23
+ case CART_FORM_ACTIONS.LinesAdd: {
24
+ const inputLines = action.inputs.lines ?? [];
25
+ const additions = inputLines.map((line) => ({
26
+ id: `optimistic:${line.merchandiseId}`,
27
+ quantity: line.quantity ?? 1,
28
+ merchandise: { id: line.merchandiseId },
29
+ attributes: []
30
+ }));
31
+ return recalculate(cart, [...cart.lines, ...additions]);
32
+ }
33
+ case CART_FORM_ACTIONS.LinesUpdate: {
34
+ const updates = new Map(
35
+ (action.inputs.lines ?? []).map(
36
+ (line) => [line.id, line]
37
+ )
38
+ );
39
+ const lines = cart.lines.map((line) => {
40
+ const update = updates.get(line.id);
41
+ if (!update) return line;
42
+ if (update.quantity === 0) return null;
43
+ return {
44
+ ...line,
45
+ quantity: update.quantity ?? line.quantity
46
+ };
47
+ }).filter((line) => line !== null);
48
+ return recalculate(cart, lines);
49
+ }
50
+ case CART_FORM_ACTIONS.LinesRemove: {
51
+ const remove = new Set(action.inputs.lineIds ?? []);
52
+ return recalculate(
53
+ cart,
54
+ cart.lines.filter((line) => !remove.has(line.id))
55
+ );
56
+ }
57
+ default:
58
+ return cart;
59
+ }
60
+ }
61
+ function useOptimisticCart(cart, action) {
62
+ return action ? applyOptimisticCartAction(cart, action) : cart;
63
+ }
64
+
65
+ // src/content-security-policy.ts
66
+ var QUOTED_TOKENS = /* @__PURE__ */ new Set([
67
+ "self",
68
+ "none",
69
+ "unsafe-eval",
70
+ "unsafe-hashes",
71
+ "unsafe-inline",
72
+ "strict-dynamic",
73
+ "wasm-unsafe-eval"
74
+ ]);
75
+ function createContentSecurityPolicy(options = {}) {
76
+ const nonce = createNonce();
77
+ const shopDomains = shopSources(options.shop);
78
+ const scriptSrc = uniqueSources([
79
+ ...normalizeSources(options.scriptSrc ?? ["self"]),
80
+ `'nonce-${nonce}'`
81
+ ]);
82
+ const connectSrc = uniqueSources([
83
+ "self",
84
+ ...shopDomains,
85
+ ...normalizeSources(options.connectSrc)
86
+ ]);
87
+ const imgSrc = uniqueSources([
88
+ "self",
89
+ "data:",
90
+ "https:",
91
+ ...shopDomains,
92
+ ...normalizeSources(options.imgSrc)
93
+ ]);
94
+ const styleSrc = uniqueSources(["self", "unsafe-inline", ...normalizeSources(options.styleSrc)]);
95
+ const directives = [
96
+ ["base-uri", ["'self'"]],
97
+ ["default-src", ["'self'"]],
98
+ ["connect-src", connectSrc],
99
+ ["img-src", imgSrc],
100
+ ["script-src", scriptSrc],
101
+ ["style-src", styleSrc]
102
+ ];
103
+ return {
104
+ nonce,
105
+ header: directives.map(([name, values]) => `${name} ${values.join(" ")}`).join("; "),
106
+ NonceProvider: createNonceProvider(nonce)
107
+ };
108
+ }
109
+ function createNonce() {
110
+ const bytes = new Uint8Array(18);
111
+ const runtimeCrypto = globalThis.crypto;
112
+ if (runtimeCrypto?.getRandomValues !== void 0) {
113
+ runtimeCrypto.getRandomValues(bytes);
114
+ } else {
115
+ for (let i = 0; i < bytes.length; i += 1) {
116
+ bytes[i] = Math.floor(Math.random() * 256);
117
+ }
118
+ }
119
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
120
+ }
121
+ function shopSources(shop) {
122
+ if (shop === void 0) return [];
123
+ return [shop.storeDomain, shop.checkoutDomain].filter((domain) => domain !== void 0 && domain.trim() !== "").map((domain) => {
124
+ const trimmed = domain.trim();
125
+ return /^https?:\/\//i.test(trimmed) ? new URL(trimmed).origin : `https://${trimmed}`;
126
+ });
127
+ }
128
+ function normalizeSources(sources) {
129
+ return (sources ?? []).map((source) => {
130
+ const trimmed = source.trim();
131
+ return QUOTED_TOKENS.has(trimmed) ? `'${trimmed}'` : trimmed;
132
+ });
133
+ }
134
+ function uniqueSources(sources) {
135
+ return Array.from(new Set(sources.filter((source) => source.length > 0)));
136
+ }
137
+
138
+ // src/localization/handler.ts
139
+ function safeReturnTo(raw) {
140
+ if (raw === null || raw === "") return { path: "/", safe: true };
141
+ return /^\/(?![/\\])/.test(raw) ? { path: raw, safe: true } : { path: "/", safe: false };
142
+ }
143
+ async function handleLocalizationPost(request) {
144
+ const form = await request.formData();
145
+ const country = (form.get("country_code") ?? "").toString().trim().toUpperCase() || null;
146
+ const locale = (form.get("locale_code") ?? "").toString().trim() || null;
147
+ const primaryLocale = (form.get("primary_locale") ?? "").toString().trim() || null;
148
+ const returnTo = safeReturnTo((form.get("return_to") ?? "").toString());
149
+ const location = returnTo.safe && primaryLocale !== null ? localizePath(returnTo.path, locale, { primaryLocale }) : returnTo.path;
150
+ return new Response(null, {
151
+ status: 302,
152
+ headers: { location, "set-cookie": buildLocalizationCookie({ country, locale }) }
153
+ });
154
+ }
155
+
156
+ // src/analytics/events.ts
157
+ var ANALYTICS_EVENTS = {
158
+ pageViewed: "page_viewed",
159
+ productViewed: "product_viewed",
160
+ collectionViewed: "collection_viewed",
161
+ cartViewed: "cart_viewed",
162
+ searchViewed: "search_viewed",
163
+ productAddedToCart: "product_added_to_cart"
164
+ };
165
+
166
+ // src/analytics/shop.ts
167
+ function getShopAnalytics(options) {
168
+ const language = options.storefront?.i18n?.language ?? null;
169
+ const country = options.storefront?.i18n?.country ?? null;
170
+ const currency = options.storefront?.i18n?.currency ?? null;
171
+ const acceptedLanguage = language != null && country != null ? `${language}-${country}` : language ?? country;
172
+ return {
173
+ ...acceptedLanguage != null ? { acceptedLanguage } : {},
174
+ ...currency != null ? { currency } : {},
175
+ ...options.publicStorefrontId != null ? { shopId: options.publicStorefrontId } : {},
176
+ ...options.publicStorefrontId != null ? { storefrontId: options.publicStorefrontId } : {}
177
+ };
178
+ }
179
+
180
+ // src/analytics/consent-encoding.ts
181
+ var PARAM_PREFIX = "consent_";
182
+ var RELATIVE_BASE = "https://kotao.invalid";
183
+ function encodeConsent(state) {
184
+ const params = new URLSearchParams();
185
+ for (const bucket of CONSENT_BUCKETS)
186
+ params.set(`${PARAM_PREFIX}${bucket}`, state[bucket] ? "1" : "0");
187
+ return params;
188
+ }
189
+ function decodeConsent(params) {
190
+ const read = (bucket) => params.get(`${PARAM_PREFIX}${bucket}`) === "1";
191
+ return {
192
+ analytics: read("analytics"),
193
+ marketing: read("marketing"),
194
+ preferences: read("preferences")
195
+ };
196
+ }
197
+ function appendConsentToCheckoutUrl(checkoutUrl, state) {
198
+ if (!/^\/(?!\/)/.test(checkoutUrl)) {
199
+ throw new Error(`checkoutUrl must be a same-origin path starting with '/'; got: ${checkoutUrl}`);
200
+ }
201
+ const url = new URL(checkoutUrl, RELATIVE_BASE);
202
+ for (const [key, value] of encodeConsent(state)) url.searchParams.set(key, value);
203
+ return `${url.pathname}${url.search}${url.hash}`;
204
+ }
205
+
206
+ export { ANALYTICS_EVENTS, appendConsentToCheckoutUrl, applyOptimisticCartAction, createContentSecurityPolicy, decodeConsent, encodeConsent, getShopAnalytics, handleLocalizationPost, useOptimisticCart };
207
+ //# sourceMappingURL=index.js.map
208
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cart/useOptimisticCart.ts","../src/content-security-policy.ts","../src/localization/handler.ts","../src/analytics/events.ts","../src/analytics/shop.ts","../src/analytics/consent-encoding.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { CART_FORM_ACTIONS } from './actions'\nimport type { CartDispatchAction } from './createCartHandler'\nimport type { Cart, CartLine } from './types'\n\nconst recalculate = (cart: Cart, lines: readonly CartLine[]): Cart => ({\n ...cart,\n lines,\n totalQuantity: lines.reduce((sum, line) => sum + line.quantity, 0),\n})\n\nexport function applyOptimisticCartAction(cart: Cart, action: CartDispatchAction): Cart {\n switch (action.action) {\n case CART_FORM_ACTIONS.LinesAdd: {\n const inputLines = (action.inputs.lines ?? []) as Array<{\n merchandiseId: string\n quantity?: number | null\n }>\n const additions = inputLines.map<CartLine>((line) => ({\n id: `optimistic:${line.merchandiseId}`,\n quantity: line.quantity ?? 1,\n merchandise: { id: line.merchandiseId },\n attributes: [],\n }))\n return recalculate(cart, [...cart.lines, ...additions])\n }\n case CART_FORM_ACTIONS.LinesUpdate: {\n const updates = new Map(\n ((action.inputs.lines ?? []) as Array<{ id: string; quantity?: number | null }>).map(\n (line) => [line.id, line],\n ),\n )\n const lines = cart.lines\n .map((line) => {\n const update = updates.get(line.id)\n if (!update) return line\n if (update.quantity === 0) return null\n return {\n ...line,\n quantity: update.quantity ?? line.quantity,\n }\n })\n .filter((line): line is CartLine => line !== null)\n return recalculate(cart, lines)\n }\n case CART_FORM_ACTIONS.LinesRemove: {\n const remove = new Set((action.inputs.lineIds ?? []) as string[])\n return recalculate(\n cart,\n cart.lines.filter((line) => !remove.has(line.id)),\n )\n }\n default:\n return cart\n }\n}\n\n/**\n * Pure optimistic helper for React integrations. A React wrapper can call this from `useOptimistic`;\n * keeping the reducer pure lets non-React tests exercise the exact overlay behavior.\n */\nexport function useOptimisticCart(cart: Cart, action: CartDispatchAction | null): Cart {\n return action ? applyOptimisticCartAction(cart, action) : cart\n}\n","import { createNonceProvider, type BoundNonceProvider } from './react/nonce'\n\nexport interface ContentSecurityPolicyShop {\n readonly checkoutDomain?: string\n readonly storeDomain?: string\n}\n\nexport interface CreateContentSecurityPolicyOptions {\n readonly shop?: ContentSecurityPolicyShop\n readonly scriptSrc?: readonly string[]\n readonly connectSrc?: readonly string[]\n readonly imgSrc?: readonly string[]\n readonly styleSrc?: readonly string[]\n}\n\nexport interface ContentSecurityPolicyResult {\n readonly nonce: string\n readonly header: string\n readonly NonceProvider: BoundNonceProvider\n}\n\nconst QUOTED_TOKENS = new Set([\n 'self',\n 'none',\n 'unsafe-eval',\n 'unsafe-hashes',\n 'unsafe-inline',\n 'strict-dynamic',\n 'wasm-unsafe-eval',\n])\n\nexport function createContentSecurityPolicy(\n options: CreateContentSecurityPolicyOptions = {},\n): ContentSecurityPolicyResult {\n const nonce = createNonce()\n const shopDomains = shopSources(options.shop)\n const scriptSrc = uniqueSources([\n ...normalizeSources(options.scriptSrc ?? ['self']),\n `'nonce-${nonce}'`,\n ])\n const connectSrc = uniqueSources([\n 'self',\n ...shopDomains,\n ...normalizeSources(options.connectSrc),\n ])\n const imgSrc = uniqueSources([\n 'self',\n 'data:',\n 'https:',\n ...shopDomains,\n ...normalizeSources(options.imgSrc),\n ])\n const styleSrc = uniqueSources(['self', 'unsafe-inline', ...normalizeSources(options.styleSrc)])\n const directives = [\n ['base-uri', [\"'self'\"]],\n ['default-src', [\"'self'\"]],\n ['connect-src', connectSrc],\n ['img-src', imgSrc],\n ['script-src', scriptSrc],\n ['style-src', styleSrc],\n ]\n return {\n nonce,\n header: directives\n .map(([name, values]) => `${name} ${(values as readonly string[]).join(' ')}`)\n .join('; '),\n NonceProvider: createNonceProvider(nonce),\n }\n}\n\nfunction createNonce(): string {\n const bytes = new Uint8Array(18)\n const runtimeCrypto = (globalThis as typeof globalThis & { crypto?: Crypto }).crypto\n if (runtimeCrypto?.getRandomValues !== undefined) {\n runtimeCrypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < bytes.length; i += 1) {\n bytes[i] = Math.floor(Math.random() * 256)\n }\n }\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction shopSources(shop: ContentSecurityPolicyShop | undefined): readonly string[] {\n if (shop === undefined) return []\n return [shop.storeDomain, shop.checkoutDomain]\n .filter((domain): domain is string => domain !== undefined && domain.trim() !== '')\n .map((domain) => {\n const trimmed = domain.trim()\n return /^https?:\\/\\//i.test(trimmed) ? new URL(trimmed).origin : `https://${trimmed}`\n })\n}\n\nfunction normalizeSources(sources: readonly string[] | undefined): readonly string[] {\n return (sources ?? []).map((source) => {\n const trimmed = source.trim()\n return QUOTED_TOKENS.has(trimmed) ? `'${trimmed}'` : trimmed\n })\n}\n\nfunction uniqueSources(sources: readonly string[]): readonly string[] {\n return Array.from(new Set(sources.filter((source) => source.length > 0)))\n}\n","import { buildLocalizationCookie } from './cookie'\nimport { localizePath } from './routing'\n\n/** Same-origin path only: must start with a single '/', not '//' or '/\\'. */\nfunction safeReturnTo(raw: string | null): { path: string; safe: boolean } {\n if (raw === null || raw === '') return { path: '/', safe: true }\n return /^\\/(?![/\\\\])/.test(raw) ? { path: raw, safe: true } : { path: '/', safe: false }\n}\n\nexport async function handleLocalizationPost(request: Request): Promise<Response> {\n const form = await request.formData()\n const country = (form.get('country_code') ?? '').toString().trim().toUpperCase() || null\n const locale = (form.get('locale_code') ?? '').toString().trim() || null\n const primaryLocale = (form.get('primary_locale') ?? '').toString().trim() || null\n const returnTo = safeReturnTo((form.get('return_to') ?? '').toString())\n const location =\n returnTo.safe && primaryLocale !== null\n ? localizePath(returnTo.path, locale, { primaryLocale })\n : returnTo.path\n return new Response(null, {\n status: 302,\n headers: { location, 'set-cookie': buildLocalizationCookie({ country, locale }) },\n })\n}\n","/**\n * Coarse visitor geo attached to pixel events — structurally the SDK's `context.geo` (a `Geo` value is\n * assignable here). Declared locally so the analytics surface (and its DOM/React preset) need not pull\n * in the geo crypto module.\n */\nexport interface AnalyticsGeo {\n readonly country: string | null\n readonly city: string | null\n readonly colo: string | null\n readonly botScore: number | null\n readonly clientIp: string | null\n}\n\n/** The standard storefront analytics events (Hydrogen-shaped names). */\nexport const ANALYTICS_EVENTS = {\n pageViewed: 'page_viewed',\n productViewed: 'product_viewed',\n collectionViewed: 'collection_viewed',\n cartViewed: 'cart_viewed',\n searchViewed: 'search_viewed',\n productAddedToCart: 'product_added_to_cart',\n} as const\n\nexport type AnalyticsEventName = (typeof ANALYTICS_EVENTS)[keyof typeof ANALYTICS_EVENTS]\n\n/** Ambient context the provider attaches to every event before it reaches the pixel. */\nexport interface AnalyticsContext {\n readonly shopId?: string\n readonly url?: string\n readonly geo?: AnalyticsGeo\n}\n\nexport interface PageViewedPayload {\n readonly title?: string\n}\nexport interface ProductViewedPayload {\n readonly productId: string\n readonly handle?: string\n}\nexport interface CollectionViewedPayload {\n readonly collectionId: string\n readonly handle?: string\n}\nexport interface CartViewedPayload {\n readonly cartId?: string\n}\nexport interface SearchViewedPayload {\n readonly query: string\n readonly resultCount?: number\n}\nexport interface ProductAddedToCartPayload {\n readonly productId: string\n readonly variantId: string\n readonly quantity: number\n}\n\n/** Event name → payload (the typed pub/sub surface). */\nexport interface AnalyticsEventMap {\n page_viewed: PageViewedPayload\n product_viewed: ProductViewedPayload\n collection_viewed: CollectionViewedPayload\n cart_viewed: CartViewedPayload\n search_viewed: SearchViewedPayload\n product_added_to_cart: ProductAddedToCartPayload\n}\n\n/** A pixel-bound event: the name, its payload, the ambient context, a timestamp, and consent state. */\nexport interface PixelEvent<E extends AnalyticsEventName = AnalyticsEventName> {\n readonly event: E\n readonly payload: AnalyticsEventMap[E]\n readonly context: AnalyticsContext\n readonly timestamp: string\n /** Whether the analytics-consent bucket was granted when this event was recorded. */\n readonly consent: boolean\n}\n","export interface ShopAnalytics {\n readonly acceptedLanguage?: string\n readonly currency?: string\n readonly shopId?: string\n readonly storefrontId?: string\n}\n\nexport interface GetShopAnalyticsOptions {\n readonly publicStorefrontId?: string\n readonly storefront?: {\n readonly i18n?: {\n readonly country?: string | null\n readonly currency?: string | null\n readonly language?: string | null\n readonly pathPrefix?: string | null\n } | null\n } | null\n}\n\nexport function getShopAnalytics(options: GetShopAnalyticsOptions): ShopAnalytics {\n const language = options.storefront?.i18n?.language ?? null\n const country = options.storefront?.i18n?.country ?? null\n const currency = options.storefront?.i18n?.currency ?? null\n const acceptedLanguage =\n language != null && country != null ? `${language}-${country}` : (language ?? country)\n\n return {\n ...(acceptedLanguage != null ? { acceptedLanguage } : {}),\n ...(currency != null ? { currency } : {}),\n ...(options.publicStorefrontId != null ? { shopId: options.publicStorefrontId } : {}),\n ...(options.publicStorefrontId != null ? { storefrontId: options.publicStorefrontId } : {}),\n }\n}\n","import { CONSENT_BUCKETS, type ConsentBucket, type ConsentState } from './consent'\n\n/**\n * Encode/decode consent into the `cart.checkoutUrl` handoff (query params), mirroring Shopify's\n * server-set-consent direction (design §Checkout handoff). The handoff target is the Gateway-owned\n * same-origin `/checkout/*` path — `appendConsentToCheckoutUrl` preserves a relative URL as relative\n * so it never points at a separate subdomain.\n */\nconst PARAM_PREFIX = 'consent_'\nconst RELATIVE_BASE = 'https://kotao.invalid'\n\nexport function encodeConsent(state: ConsentState): URLSearchParams {\n const params = new URLSearchParams()\n for (const bucket of CONSENT_BUCKETS)\n params.set(`${PARAM_PREFIX}${bucket}`, state[bucket] ? '1' : '0')\n return params\n}\n\nexport function decodeConsent(params: URLSearchParams): ConsentState {\n const read = (bucket: ConsentBucket): boolean => params.get(`${PARAM_PREFIX}${bucket}`) === '1'\n return {\n analytics: read('analytics'),\n marketing: read('marketing'),\n preferences: read('preferences'),\n }\n}\n\nexport function appendConsentToCheckoutUrl(checkoutUrl: string, state: ConsentState): string {\n // The checkout handoff is the same-origin `/checkout/*` path (cart.checkoutUrl is relative). Reject\n // absolute or protocol-relative URLs so consent params never ride to another origin.\n if (!/^\\/(?!\\/)/.test(checkoutUrl)) {\n throw new Error(`checkoutUrl must be a same-origin path starting with '/'; got: ${checkoutUrl}`)\n }\n const url = new URL(checkoutUrl, RELATIVE_BASE)\n for (const [key, value] of encodeConsent(state)) url.searchParams.set(key, value)\n return `${url.pathname}${url.search}${url.hash}`\n}\n"]}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Framework-agnostic cursor pagination (ADR-0003 §SDK). Mirrors Shopify Hydrogen's
3
+ * `getPaginationVariables` + `<Pagination>` over a relay-style connection
4
+ * (`{ nodes, pageInfo { hasNextPage, hasPreviousPage, startCursor, endCursor } }`). Generic over the
5
+ * connection's node type, so it works with any Storefront API connection (today: `Cart.lines`).
6
+ *
7
+ * Pure logic, no React — the React `<Pagination>` render-props component is a thin wrapper.
8
+ */
9
+ /** Relay PageInfo as the Storefront API emits it. */
10
+ interface PageInfo {
11
+ readonly hasNextPage: boolean;
12
+ readonly hasPreviousPage: boolean;
13
+ readonly startCursor?: string | null;
14
+ readonly endCursor?: string | null;
15
+ }
16
+ interface Connection<T> {
17
+ readonly nodes: readonly T[];
18
+ readonly pageInfo: PageInfo;
19
+ }
20
+ interface ConnectionEdge<T> {
21
+ readonly node?: T | null;
22
+ }
23
+ interface FlattenableConnection<T> {
24
+ readonly nodes?: T[] | readonly T[] | null;
25
+ readonly edges?: readonly (ConnectionEdge<T> | null)[] | null;
26
+ }
27
+ type IsAny<T> = 0 extends 1 & T ? true : false;
28
+ type NodeFromConnection<C> = IsAny<C> extends true ? any : C extends {
29
+ readonly nodes?: readonly (infer Node)[] | null;
30
+ } ? Node : C extends {
31
+ readonly edges?: readonly (infer Edge | null)[] | null;
32
+ } ? Edge extends {
33
+ readonly node?: infer Node | null;
34
+ } ? NonNullable<Node> : unknown : unknown;
35
+ /**
36
+ * Hydrogen-compatible connection flattener. Shopify code often accepts either Storefront API
37
+ * `nodes` connections or Relay `edges[].node` connections; migrated routes can keep that shape.
38
+ */
39
+ declare function flattenConnection(connection: null | undefined): [];
40
+ declare function flattenConnection<T>(connection: FlattenableConnection<T>): T[];
41
+ declare function flattenConnection<C extends FlattenableConnection<unknown>>(connection: C | null | undefined): NodeFromConnection<C>[];
42
+ /** The query variables a connection field takes for forward/backward paging. */
43
+ interface PaginationVariables {
44
+ first?: number;
45
+ last?: number;
46
+ startCursor?: string;
47
+ endCursor?: string;
48
+ }
49
+ interface PaginationOptions {
50
+ pageSize?: number;
51
+ pageBy?: number;
52
+ /** Isolates the URL params when multiple paginations share a page (`{ns}_direction`/`{ns}_cursor`). */
53
+ namespace?: string;
54
+ }
55
+ /**
56
+ * Read `direction`/`cursor` from the request URL and return the connection variables for the page:
57
+ * forward → `{ first, endCursor }`, backward → `{ last, startCursor }`, no params → `{ first }`.
58
+ */
59
+ declare function getPaginationVariables(input: URL | Request | URLSearchParams | string, options: PaginationOptions): PaginationVariables;
60
+ interface PaginationInfo<T> {
61
+ readonly nodes: readonly T[];
62
+ readonly hasNextPage: boolean;
63
+ readonly hasPreviousPage: boolean;
64
+ /** Same-page URL with `direction=next` + the end cursor; absent when there is no next page. */
65
+ readonly nextPageUrl: string;
66
+ /** Same-page URL with `direction=previous` + the start cursor; absent when there is no previous page. */
67
+ readonly prevPageUrl: string;
68
+ }
69
+ /**
70
+ * Decorate a connection with the current page's nodes + the next/previous page URLs (built from the
71
+ * current URL + the connection's cursors). A theme renders links to `nextPageUrl`/`prevPageUrl`.
72
+ */
73
+ declare function getPaginationInfo<T>(connection: Connection<T>, currentUrl: URL | string, options?: {
74
+ namespace?: string;
75
+ }): PaginationInfo<T>;
76
+
77
+ export { type Connection as C, type FlattenableConnection as F, type PageInfo as P, type ConnectionEdge as a, type PaginationInfo as b, type PaginationOptions as c, type PaginationVariables as d, getPaginationVariables as e, flattenConnection as f, getPaginationInfo as g };
@@ -0,0 +1,105 @@
1
+ import { F as FlattenableConnection } from './pagination-CPgH3fAa.js';
2
+ import { ProductOptionValueSwatch, Image } from './storefront-api-types.js';
3
+
4
+ /**
5
+ * Framework-agnostic variant selection (ADR-0003 §SDK). Mirrors Shopify Hydrogen's MODERN approach —
6
+ * `getProductOptions` as a utility, not the deprecated `<VariantSelector>` component — adapted to the
7
+ * Kotao Storefront API `Product` shape (`options { name values }` + `variants { selectedOptions }`).
8
+ * A theme renders the returned grid however it likes and resolves the chosen variant.
9
+ *
10
+ * Pure logic, no React, so it stays in the SDK core and is reused by SSR and any framework preset.
11
+ */
12
+
13
+ /** One option name + its chosen value on a variant (Storefront API SelectedOption). */
14
+ interface SelectedOption {
15
+ readonly name: string;
16
+ readonly value: string;
17
+ }
18
+ /** A product option definition: a name and its distinct values, in display order. */
19
+ interface ProductOptionDef {
20
+ readonly name: string;
21
+ readonly optionValues?: readonly (string | {
22
+ readonly name?: string;
23
+ readonly value?: string;
24
+ readonly swatch?: ProductOptionValueSwatch | null;
25
+ })[];
26
+ readonly values?: readonly string[];
27
+ }
28
+ /** The minimal variant shape variant-selection needs. `availableForSale` is optional — the Storefront
29
+ * API does not expose stock yet (inventory deferred), so a missing value is treated as available. */
30
+ interface VariantSelectionLike {
31
+ readonly id: string;
32
+ readonly availableForSale?: boolean | null;
33
+ readonly selectedOptions: readonly SelectedOption[];
34
+ }
35
+ interface VariantLike extends VariantSelectionLike {
36
+ readonly compareAtPrice?: {
37
+ readonly amount: string;
38
+ readonly currencyCode: string;
39
+ } | null;
40
+ readonly image?: Pick<Image, 'altText' | 'height' | 'url' | 'width' | 'id'> | null;
41
+ readonly price: {
42
+ readonly amount: string;
43
+ readonly currencyCode: string;
44
+ };
45
+ readonly product: {
46
+ readonly handle: string;
47
+ readonly id?: string | null;
48
+ readonly title: string;
49
+ };
50
+ readonly selectedOptions: SelectedOption[];
51
+ readonly sku?: string | null;
52
+ readonly title?: string;
53
+ }
54
+ interface ProductLike {
55
+ readonly options: readonly ProductOptionDef[];
56
+ readonly handle?: string | null;
57
+ readonly variants: readonly VariantSelectionLike[] | FlattenableConnection<VariantSelectionLike>;
58
+ readonly selectedOrFirstAvailableVariant?: VariantSelectionLike | null;
59
+ }
60
+ /** A selection map: option name → chosen value. */
61
+ type SelectedOptionsMap = Readonly<Record<string, string>>;
62
+ /** One value of one option, decorated with selection/availability state for rendering. */
63
+ interface ProductOptionValueState {
64
+ readonly name: string;
65
+ readonly value: string;
66
+ /** This value is the current selection for its option. */
67
+ readonly selected: boolean;
68
+ /** A variant exists for this value combined with the other currently-selected options. */
69
+ readonly exists: boolean;
70
+ /** That variant is purchasable (availableForSale; true when availability is unknown). */
71
+ readonly available: boolean;
72
+ /** The variant choosing this value (holding the other selected options) would land on, if any. */
73
+ readonly variant: VariantSelectionLike | null;
74
+ readonly handle: string;
75
+ readonly isDifferentProduct: boolean;
76
+ readonly swatch?: ProductOptionValueSwatch | null;
77
+ readonly variantUriQuery: string;
78
+ }
79
+ interface ProductOptionState {
80
+ readonly name: string;
81
+ readonly optionValues: readonly ProductOptionValueState[];
82
+ readonly values: readonly ProductOptionValueState[];
83
+ }
84
+ type MappedProductOptions = ProductOptionState;
85
+ /** `[{ name: 'Size', value: 'M' }]` → `{ Size: 'M' }`. Mirrors Hydrogen mapSelectedProductOptionToObject. */
86
+ declare function mapSelectedProductOptionToObject(selectedOptions: readonly SelectedOption[]): SelectedOptionsMap;
87
+ /**
88
+ * The variant fully matching `selected` (every chosen option name → value), or null. Returns null
89
+ * unless the selection is unambiguous — a partial selection that matches several variants yields null.
90
+ */
91
+ declare function getSelectedVariant(variants: readonly VariantSelectionLike[], selected: SelectedOptionsMap): VariantSelectionLike | null;
92
+ /**
93
+ * Decorate each option value with `selected`/`exists`/`available` + the variant it resolves to, given
94
+ * the buyer's current `selected` map. `exists`/`available` hold the OTHER selected options fixed (so a
95
+ * swatch greys out when its combination is unbuyable) — Hydrogen's getProductOptions semantics.
96
+ */
97
+ declare function getProductOptions(product: ProductLike, selected?: SelectedOptionsMap): ProductOptionState[];
98
+ declare function getSelectedProductOptions(input: URL | Request | URLSearchParams | string): SelectedOption[];
99
+ declare function getAdjacentAndFirstAvailableVariants(product: {
100
+ readonly variants?: readonly VariantLike[] | FlattenableConnection<VariantLike> | null;
101
+ }): VariantLike[];
102
+ declare function useOptimisticVariant(selectedVariant: VariantLike | null | undefined, variants?: readonly VariantLike[] | FlattenableConnection<VariantLike>): VariantLike;
103
+ declare function useSelectedOptionInUrlParam(_selectedOptions: readonly SelectedOption[]): void;
104
+
105
+ export { type MappedProductOptions, type ProductLike, type ProductOptionDef, type ProductOptionState, type ProductOptionValueState, type SelectedOption, type SelectedOptionsMap, type VariantLike, type VariantSelectionLike, getAdjacentAndFirstAvailableVariants, getProductOptions, getSelectedProductOptions, getSelectedVariant, mapSelectedProductOptionToObject, useOptimisticVariant, useSelectedOptionInUrlParam };
@@ -0,0 +1,4 @@
1
+ export { getAdjacentAndFirstAvailableVariants, getProductOptions, getSelectedProductOptions, getSelectedVariant, mapSelectedProductOptionToObject, useOptimisticVariant, useSelectedOptionInUrlParam } from './chunk-QOEEUTHQ.js';
2
+ import './chunk-5QOFKGKD.js';
3
+ //# sourceMappingURL=product.js.map
4
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"product.js"}