@shopify/hydrogen 2026.10.0-preview.5 → 2026.10.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.
@@ -73,8 +73,8 @@ declare const PREDICTIVE_SEARCH_QUERY: StorefrontQueryString<{
73
73
  term: string;
74
74
  limitScope?: "ALL" | "EACH" | null | undefined;
75
75
  limit?: number | null | undefined;
76
- language?: "ID" | "AF" | "AK" | "AM" | "AR" | "AS" | "AZ" | "BE" | "BG" | "BM" | "BN" | "BO" | "BR" | "BS" | "CA" | "CE" | "CKB" | "CS" | "CY" | "DA" | "DE" | "DZ" | "EE" | "EL" | "EN" | "EO" | "ES" | "ET" | "EU" | "FA" | "FF" | "FI" | "FIL" | "FO" | "FR" | "FY" | "GA" | "GD" | "GL" | "GU" | "GV" | "HA" | "HE" | "HI" | "HR" | "HU" | "HY" | "IA" | "IG" | "II" | "IS" | "IT" | "JA" | "JV" | "KA" | "KI" | "KK" | "KL" | "KM" | "KN" | "KO" | "KS" | "KU" | "KW" | "KY" | "LB" | "LG" | "LN" | "LO" | "LT" | "LU" | "LV" | "MG" | "MI" | "MK" | "ML" | "MN" | "MR" | "MS" | "MT" | "MY" | "NB" | "ND" | "NE" | "NL" | "NN" | "NO" | "OM" | "OR" | "OS" | "PA" | "PL" | "PS" | "PT_BR" | "PT_PT" | "QU" | "RM" | "RN" | "RO" | "RU" | "RW" | "SA" | "SC" | "SD" | "SE" | "SG" | "SI" | "SK" | "SL" | "SN" | "SO" | "SQ" | "SR" | "SU" | "SV" | "SW" | "TA" | "TE" | "TG" | "TH" | "TI" | "TK" | "TO" | "TR" | "TT" | "UG" | "UK" | "UR" | "UZ" | "VI" | "WO" | "XH" | "YI" | "YO" | "ZH_CN" | "ZH_TW" | "ZU" | "ZH" | "PT" | "CU" | "VO" | "LA" | "SH" | "MO" | null | undefined;
77
- country?: "ID" | "AF" | "AM" | "AR" | "AZ" | "BE" | "BG" | "BM" | "BN" | "BO" | "BR" | "BS" | "CA" | "CY" | "DE" | "DZ" | "EE" | "ES" | "ET" | "FI" | "FO" | "FR" | "GA" | "GD" | "GL" | "HR" | "HU" | "IS" | "IT" | "KI" | "KM" | "KN" | "KW" | "KY" | "LB" | "LT" | "LU" | "LV" | "MG" | "MK" | "ML" | "MN" | "MR" | "MS" | "MT" | "MY" | "NE" | "NL" | "NO" | "OM" | "PA" | "PL" | "PS" | "RO" | "RU" | "RW" | "SA" | "SC" | "SD" | "SE" | "SG" | "SI" | "SK" | "SL" | "SN" | "SO" | "SR" | "SV" | "TA" | "TG" | "TH" | "TK" | "TO" | "TR" | "TT" | "UG" | "UZ" | "PT" | "CU" | "LA" | "SH" | "MO" | "AX" | "AL" | "AD" | "AO" | "AI" | "AG" | "AW" | "AC" | "AU" | "AT" | "BH" | "BD" | "BB" | "BY" | "BZ" | "BJ" | "BT" | "BA" | "BW" | "BV" | "IO" | "BF" | "BI" | "KH" | "CV" | "BQ" | "CF" | "TD" | "CL" | "CN" | "CX" | "CC" | "CO" | "CG" | "CD" | "CK" | "CR" | "CW" | "CZ" | "CI" | "DK" | "DJ" | "DM" | "DO" | "EC" | "EG" | "GQ" | "ER" | "SZ" | "FK" | "FJ" | "GF" | "PF" | "TF" | "GM" | "GE" | "GH" | "GI" | "GR" | "GP" | "GT" | "GG" | "GN" | "GW" | "GY" | "HT" | "HM" | "VA" | "HN" | "HK" | "IN" | "IR" | "IQ" | "IE" | "IM" | "IL" | "JM" | "JP" | "JE" | "JO" | "KZ" | "KE" | "KP" | "XK" | "KG" | "LS" | "LR" | "LY" | "LI" | "MW" | "MV" | "MQ" | "MU" | "YT" | "MX" | "MD" | "MC" | "ME" | "MA" | "MZ" | "MM" | "NA" | "NR" | "NP" | "AN" | "NC" | "NZ" | "NI" | "NG" | "NU" | "NF" | "PK" | "PG" | "PY" | "PE" | "PH" | "PN" | "QA" | "CM" | "RE" | "BL" | "LC" | "MF" | "PM" | "WS" | "SM" | "ST" | "RS" | "SX" | "SB" | "ZA" | "GS" | "KR" | "SS" | "LK" | "VC" | "SJ" | "CH" | "SY" | "TW" | "TJ" | "TZ" | "TL" | "TN" | "TM" | "TC" | "TV" | "UA" | "AE" | "GB" | "US" | "UM" | "UY" | "VU" | "VE" | "VN" | "VG" | "WF" | "EH" | "YE" | "ZM" | "ZW" | "ZZ" | null | undefined;
76
+ language?: "ID" | "AF" | "DZ" | "AR" | "AM" | "AZ" | "BS" | "BE" | "BM" | "BO" | "BR" | "BN" | "BG" | "CA" | "KY" | "KM" | "HR" | "CU" | "CY" | "SV" | "EE" | "ET" | "FO" | "FI" | "FR" | "GA" | "DE" | "GL" | "GD" | "HU" | "IS" | "IT" | "KI" | "KW" | "LA" | "LV" | "LB" | "LT" | "LU" | "MO" | "MG" | "MY" | "ML" | "MT" | "MR" | "MN" | "MS" | "NL" | "NE" | "MK" | "NO" | "OM" | "PS" | "PA" | "PL" | "PT" | "RO" | "RU" | "RW" | "SH" | "KN" | "SA" | "SN" | "SC" | "SL" | "SG" | "SK" | "SI" | "SO" | "ES" | "SD" | "SR" | "SE" | "TH" | "TG" | "TK" | "TO" | "TT" | "TA" | "TR" | "UG" | "UZ" | "AK" | "AS" | "CE" | "CKB" | "CS" | "DA" | "EL" | "EN" | "EO" | "EU" | "FA" | "FF" | "FIL" | "FY" | "GU" | "GV" | "HA" | "HE" | "HI" | "HY" | "IA" | "IG" | "II" | "JA" | "JV" | "KA" | "KK" | "KL" | "KO" | "KS" | "KU" | "LG" | "LN" | "LO" | "MI" | "NB" | "ND" | "NN" | "OR" | "OS" | "PT_BR" | "PT_PT" | "QU" | "RM" | "RN" | "SQ" | "SU" | "SW" | "TE" | "TI" | "UK" | "UR" | "VI" | "WO" | "XH" | "YI" | "YO" | "ZH_CN" | "ZH_TW" | "ZU" | "ZH" | "VO" | null | undefined;
77
+ country?: "ID" | "AF" | "AX" | "AL" | "DZ" | "AD" | "AO" | "AI" | "AG" | "AR" | "AM" | "AW" | "AC" | "AU" | "AT" | "AZ" | "BS" | "BH" | "BD" | "BB" | "BY" | "BE" | "BZ" | "BJ" | "BM" | "BT" | "BO" | "BA" | "BW" | "BV" | "BR" | "IO" | "BN" | "BG" | "BF" | "BI" | "KH" | "CA" | "CV" | "BQ" | "KY" | "CF" | "TD" | "CL" | "CN" | "CX" | "CC" | "CO" | "KM" | "CG" | "CD" | "CK" | "CR" | "HR" | "CU" | "CW" | "CY" | "CZ" | "CI" | "DK" | "DJ" | "DM" | "DO" | "EC" | "EG" | "SV" | "GQ" | "ER" | "EE" | "SZ" | "ET" | "FK" | "FO" | "FJ" | "FI" | "FR" | "GF" | "PF" | "TF" | "GA" | "GM" | "GE" | "DE" | "GH" | "GI" | "GR" | "GL" | "GD" | "GP" | "GT" | "GG" | "GN" | "GW" | "GY" | "HT" | "HM" | "VA" | "HN" | "HK" | "HU" | "IS" | "IN" | "IR" | "IQ" | "IE" | "IM" | "IL" | "IT" | "JM" | "JP" | "JE" | "JO" | "KZ" | "KE" | "KI" | "KP" | "XK" | "KW" | "KG" | "LA" | "LV" | "LB" | "LS" | "LR" | "LY" | "LI" | "LT" | "LU" | "MO" | "MG" | "MW" | "MY" | "MV" | "ML" | "MT" | "MQ" | "MR" | "MU" | "YT" | "MX" | "MD" | "MC" | "MN" | "ME" | "MS" | "MA" | "MZ" | "MM" | "NA" | "NR" | "NP" | "NL" | "AN" | "NC" | "NZ" | "NI" | "NE" | "NG" | "NU" | "NF" | "MK" | "NO" | "OM" | "PK" | "PS" | "PA" | "PG" | "PY" | "PE" | "PH" | "PN" | "PL" | "PT" | "QA" | "CM" | "RE" | "RO" | "RU" | "RW" | "BL" | "SH" | "KN" | "LC" | "MF" | "PM" | "WS" | "SM" | "ST" | "SA" | "SN" | "RS" | "SC" | "SL" | "SG" | "SX" | "SK" | "SI" | "SB" | "SO" | "ZA" | "GS" | "KR" | "SS" | "ES" | "LK" | "VC" | "SD" | "SR" | "SJ" | "SE" | "CH" | "SY" | "TW" | "TJ" | "TZ" | "TH" | "TL" | "TG" | "TK" | "TO" | "TT" | "TA" | "TN" | "TR" | "TM" | "TC" | "TV" | "UG" | "UA" | "AE" | "GB" | "US" | "UM" | "UY" | "UZ" | "VU" | "VE" | "VN" | "VG" | "WF" | "EH" | "YE" | "ZM" | "ZW" | "ZZ" | null | undefined;
78
78
  }, "query PredictiveSearch(\n $country: CountryCode\n $language: LanguageCode\n $limit: Int\n $limitScope: PredictiveSearchLimitScope\n $term: String!\n $types: [PredictiveSearchType!]\n $searchableFields: [SearchableField!]\n $unavailableProducts: SearchUnavailableProductsType\n ) @inContext(country: $country, language: $language) {\n predictiveSearch(\n limit: $limit\n limitScope: $limitScope\n query: $term\n types: $types\n searchableFields: $searchableFields\n unavailableProducts: $unavailableProducts\n ) {\n articles {\n ...HydrogenPredictiveSearchArticleFragment\n ...PredictiveSearchArticleFragment\n }\n collections {\n ...HydrogenPredictiveSearchCollectionFragment\n ...PredictiveSearchCollectionFragment\n }\n pages {\n ...HydrogenPredictiveSearchPageFragment\n ...PredictiveSearchPageFragment\n }\n products {\n ...HydrogenPredictiveSearchProductFragment\n ...PredictiveSearchProductFragment\n }\n queries {\n ...HydrogenPredictiveSearchQueryFragment\n ...PredictiveSearchQueryFragment\n }\n }\n }\n\n fragment HydrogenPredictiveSearchProductFragment on Product {\n __typename\n id\n title\n handle\n trackingParameters\n selectedOrFirstAvailableVariant(\n selectedOptions: []\n ignoreUnknownOptions: true\n caseInsensitiveMatch: true\n ) {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n }\n }\n\n\n fragment HydrogenPredictiveSearchCollectionFragment on Collection {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n\n\n fragment HydrogenPredictiveSearchPageFragment on Page {\n __typename\n id\n title\n handle\n trackingParameters\n }\n\n\n fragment HydrogenPredictiveSearchArticleFragment on Article {\n __typename\n id\n title\n handle\n blog {\n handle\n }\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n\n\n fragment HydrogenPredictiveSearchQueryFragment on SearchQuerySuggestion {\n __typename\n text\n styledText\n trackingParameters\n }\n">;
79
79
  declare const DEFAULT_PRODUCT_FRAGMENT: StorefrontQueryString<{
80
80
  id: string;
@@ -84,7 +84,7 @@ function applyStorefrontRequestHeaders(context, headers) {
84
84
  headers.set(SDK_VARIANT_HEADER, "hydrogen");
85
85
  headers.set(SDK_VARIANT_SOURCE_HEADER, "kit");
86
86
  headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);
87
- headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0-preview.5");
87
+ headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0");
88
88
  headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);
89
89
  if (context.cookie) headers.set("cookie", context.cookie);
90
90
  else headers.delete("cookie");
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.mjs","names":[],"sources":["../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request-context.mjs","names":[],"sources":["../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
@@ -15,7 +15,7 @@ const LOCAL_ORIGIN_HOSTS = new Set([
15
15
  "::1",
16
16
  "[::1]"
17
17
  ]);
18
- const USER_AGENT = `Hydrogen 2026.10.0-preview.5`;
18
+ const USER_AGENT = `Hydrogen 2026.10.0`;
19
19
  const CUSTOMER_ACCOUNT_GRAPHQL_PERSONALIZATION_REASON = "customer-account-graphql";
20
20
  /**
21
21
  * Creates a client that sends Customer Account API requests from your server. Create the client with the current request's context.
@@ -44,7 +44,7 @@ const AUTHORIZATION_CODE_GRANT_TYPE = "authorization_code";
44
44
  const REFRESH_TOKEN_GRANT_TYPE = "refresh_token";
45
45
  const CUSTOMER_ACCOUNT_SCOPE = "openid email customer-account-api:full";
46
46
  const CODE_CHALLENGE_METHOD = "S256";
47
- const USER_AGENT = `Hydrogen 2026.10.0-preview.5`;
47
+ const USER_AGENT = `Hydrogen 2026.10.0`;
48
48
  const EXPIRY_BUFFER_IN_SECONDS = 120;
49
49
  const PENDING_LOGIN_TTL_IN_MINUTES = 10;
50
50
  const SECONDS_PER_MINUTE = 60;
@@ -84,7 +84,7 @@ function applyStorefrontRequestHeaders(context, headers) {
84
84
  headers.set(SDK_VARIANT_HEADER, "hydrogen");
85
85
  headers.set(SDK_VARIANT_SOURCE_HEADER, "kit");
86
86
  headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);
87
- headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0-preview.5");
87
+ headers.set(HYDROGEN_VERSION_HEADER, "2026.10.0");
88
88
  headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);
89
89
  if (context.cookie) headers.set("cookie", context.cookie);
90
90
  else headers.delete("cookie");
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/core/request-context.ts"],"sourcesContent":["import type {\n CountryCode as CustomerAccountCountryCode,\n LanguageCode as CustomerAccountLanguageCode,\n} from \"../graphql/generated/customer-account-api-types\";\nimport type {\n CountryCode as StorefrontCountryCode,\n LanguageCode as StorefrontLanguageCode,\n} from \"../graphql/generated/storefront-api-types\";\nimport { STOREFRONT_API_VERSION } from \"./constants\";\nimport {\n applyPrivateResponseCacheHeaders,\n CONSENT_MANAGEMENT_HEADER,\n HYDROGEN_VERSION_HEADER,\n REQUEST_GROUP_ID_HEADER,\n SDK_VARIANT_HEADER,\n SDK_VARIANT_SOURCE_HEADER,\n SDK_VERSION_HEADER,\n SEC_GPC_HEADER,\n SHOPIFY_STOREFRONT_ORIGIN_HEADER,\n STOREFRONT_URL_HEADER,\n} from \"./headers\";\nimport { normalizePathPrefix } from \"./standard-routes/path\";\n\nconst SHOPIFY_ESSENTIAL_COOKIE = \"_shopify_essential\";\n\n/** The incoming request's headers, plus its method, abort signal, and URL when the framework provides them. */\ntype StorefrontRequest = Pick<Request, \"headers\"> &\n Partial<Pick<Request, \"method\" | \"signal\" | \"url\">>;\n\n/** A language code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyLanguageCode = Extract<StorefrontLanguageCode, CustomerAccountLanguageCode>;\n/** A country code that both the Storefront API and the Customer Account API accept. */\nexport type ShopifyCountryCode = Extract<StorefrontCountryCode, CustomerAccountCountryCode>;\n\n/** The language, country, and optional localized path prefix for a request. */\nexport type I18nConfig = {\n /** The request's language. */\n language: ShopifyLanguageCode;\n /** The request's country. */\n country: ShopifyCountryCode;\n /**\n * The URL prefix for localized routes, such as `/es-es`. Hydrogen adds the prefix to standard route redirects and product variant URLs.\n */\n pathPrefix?: string;\n};\n\n/** The request's locale, with a path prefix that starts with `/` or is an empty string. */\ntype NormalizedI18nConfig<I18n extends I18nConfig = I18nConfig> = Omit<I18n, \"pathPrefix\"> & {\n pathPrefix: string;\n};\n\n/** The incoming request and its locale. */\ntype ShopifyRequestContextInputBase<I18n extends I18nConfig = I18nConfig> = {\n /**\n * The incoming request from your framework. When the framework exposes no request, such as during prerendering, pass `{ headers: new Headers() }`.\n */\n request: StorefrontRequest;\n /** The request's language, country, and optional localized path prefix. */\n i18n: I18n;\n};\n\ntype ShopifyRequestContextInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp?: never };\n\ntype ShopifyRequestContextWithBuyerIpInput<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextInputBase<I18n> & { buyerIp: string };\n\n/** The fields and methods on every request context. */\ntype ShopifyRequestContextBase = {\n // -- Private fields --\n /**\n * A type-only marker. Create request contexts only with `createShopifyRequestContext`.\n * @internal\n */\n readonly __hydrogenShopifyRequestContextBrand: never;\n /**\n * The incoming request's `Cookie` header. Hydrogen forwards the header to the Storefront API.\n * @internal\n */\n cookie?: string;\n /**\n * The customer's trusted IP address. Private Storefront API clients require it.\n * @internal\n */\n readonly buyerIp?: string;\n /**\n * The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one.\n * @internal\n */\n requestGroupId: string;\n /**\n * The incoming request's abort signal. Hydrogen's API clients stop their requests when the signal aborts.\n * @internal\n */\n signal?: AbortSignal;\n /**\n * The incoming request's URL.\n * @internal\n */\n url?: string;\n /**\n * The origin of the incoming request's URL. Hydrogen forwards the origin to the Storefront API.\n * @internal\n */\n storefrontOrigin?: string;\n /**\n * Adds the Hydrogen SDK headers and the customer's cookie, privacy, and origin headers to a\n * Storefront API request. The Storefront API client and the API proxies call the method for you.\n * @internal\n */\n applyStorefrontRequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from the first Storefront API response that sets cookies. The Storefront API\n * client calls the method for you, and `applyResponseHeaders()` can add the cookies to your response.\n * @internal\n */\n captureSubrequestHeaders(headers: Headers): void;\n /**\n * Saves the cookies from a proxied Storefront API response and removes the `set-cookie` header\n * from that response. The API proxies in `handleShopifyRoutes` call the method for you.\n * @internal\n */\n consumeStorefrontResponseHeaders(headers: Headers): void;\n /**\n * Marks the final response as private and uncacheable because the response holds customer data.\n * The Customer Account API client and the customer session call the method for you.\n * @internal\n */\n markResponseAsPersonalized(reason: string): void;\n /**\n * Lets the final response set Shopify cookies before the customer has a Shopify session, and\n * marks the response as private and uncacheable. The UCP MCP proxy calls the method for you.\n * @internal\n */\n markResponseAsSessionEstablishing(reason: string): void;\n\n // -- Public fields --\n /** The request's language, country, and path prefix. The path prefix starts with `/` and has no trailing slash, or is an empty string when you don't set one. */\n i18n: NormalizedI18nConfig;\n /** Returns the incoming request's headers with Shopify's request headers added. Pass the headers when you forward the request to a proxy or another origin. */\n getForwardedRequestHeaders(): Headers;\n /**\n * Adds the headers that a Hydrogen storefront needs to the final response. Call the method on each response that your app builds. Responses from `handleShopifyRoutes` and `handleShopifyRedirects` already carry these headers.\n *\n * Append your committed session headers before you call the method. The method makes any response that sets a cookie private and uncacheable. The method also sets the `powered-by` header, makes responses that hold customer data private and uncacheable, and adds eligible Shopify cookies only to non-document responses for methods other than `GET` and `HEAD`.\n */\n applyResponseHeaders(headers: Headers): void;\n};\n\n/** The context for one request. Pass the context to Hydrogen's API clients and route handlers for that request. */\nexport type ShopifyRequestContext<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContextBase & {\n i18n: NormalizedI18nConfig<I18n>;\n };\n\n/** A request context that includes the customer's IP address. Private Storefront API clients require this context. */\nexport type ShopifyRequestContextWithBuyerIp<I18n extends I18nConfig = I18nConfig> =\n ShopifyRequestContext<I18n> & { readonly buyerIp: string };\n\n/** The values that the request context reads from the incoming request. */\ntype Context<I18n extends I18nConfig = I18nConfig> = {\n /** The incoming request's `Cookie` header. */\n cookie?: string;\n /** The incoming request's `Sec-GPC` header value, which carries the customer's Global Privacy Control signal. */\n globalPrivacyControl?: string;\n /** The customer's trusted IP address. */\n buyerIp?: string;\n /** The ID that groups this request's Shopify API calls. Hydrogen reads the ID from the request headers or generates one. */\n requestGroupId: string;\n /** The incoming request's abort signal. */\n signal?: AbortSignal;\n /** The incoming request's URL. */\n url?: string;\n /** The origin of the incoming request's URL. */\n storefrontOrigin?: string;\n /** The request's language, country, and path prefix. */\n i18n: NormalizedI18nConfig<I18n>;\n /** Whether the incoming request asks for an HTML document. */\n documentRequest?: boolean;\n};\n\n/**\n * Creates the context for one incoming request. Pass the same context to the Storefront API\n * client, the Customer Account API client, and `handleShopifyRoutes`. The context forwards the\n * customer's cookies, locale, and privacy signal to Shopify, and adds Shopify's cookies and cache\n * headers to your response.\n *\n * Call `applyResponseHeaders()` on each response that your app builds. Pass `buyerIp` with the customer's trusted IP address when you use a private Storefront API client. The function throws when `i18n` lacks a country or language, and when `buyerIp` is an empty string.\n *\n * @publicDocs\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextWithBuyerIpInput<I18n>,\n): ShopifyRequestContextWithBuyerIp<I18n>;\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInput<I18n>,\n): ShopifyRequestContext<I18n>;\n/**\n * @param input The incoming request, its locale, and the customer's IP address for private Storefront API clients.\n * @returns The context to pass to Hydrogen's API clients and route handlers for this request.\n */\nexport function createShopifyRequestContext<const I18n extends I18nConfig>(\n input: ShopifyRequestContextInputBase<I18n> & { buyerIp?: string },\n): ShopifyRequestContext<I18n> {\n const { request } = input;\n\n if (!input.i18n?.country || !input.i18n?.language) {\n throw new Error(\"i18n with country and language is required for Shopify request contexts.\");\n }\n\n if (input.buyerIp !== undefined && !input.buyerIp) {\n throw new Error(\"buyerIp must be non-empty when provided\");\n }\n\n const requestMethod = request.method?.toUpperCase();\n const i18n = normalizeI18n(input.i18n);\n const cookieHeader = request.headers.get(\"cookie\") || undefined;\n const inboundCookies = parseCookieHeader(cookieHeader);\n const hasEssentialCookie = inboundCookies.has(SHOPIFY_ESSENTIAL_COOKIE);\n const isConsentManagementRequest = request.headers.get(CONSENT_MANAGEMENT_HEADER) === \"1\";\n const url = request.url ?? request.headers.get(STOREFRONT_URL_HEADER) ?? undefined;\n const storefrontOrigin = getUrlOrigin(url);\n const context = {\n ...(cookieHeader && { cookie: cookieHeader }),\n globalPrivacyControl: request.headers.get(SEC_GPC_HEADER) ?? undefined,\n i18n,\n ...(url && { url }),\n ...(storefrontOrigin && { storefrontOrigin }),\n ...(input.buyerIp && { buyerIp: input.buyerIp }),\n requestGroupId:\n request.headers.get(REQUEST_GROUP_ID_HEADER) ??\n request.headers.get(\"x-request-id\") ??\n request.headers.get(\"request-id\") ??\n crypto.randomUUID(),\n ...(isDocumentRequest(request) && { documentRequest: true }),\n ...(request.signal && { signal: request.signal }),\n } as Context<I18n>;\n\n let capturedCookies: string[] | undefined;\n let personalizedResponseReason: string | undefined;\n let sessionEstablishingReason: string | undefined;\n\n const captureSubrequestHeaders = (headers: Headers): void => {\n // Capture the first fresh response cookies to increase the\n // chance of returning them from the main server response. The main response\n // needs headers set at send time, while the body can stream later, so this\n // may not be used if subrequests finish after the main response is sent.\n const cookies = headers.getSetCookie();\n if (cookies.length > 0) capturedCookies ??= cookies;\n };\n\n return {\n ...context,\n getForwardedRequestHeaders() {\n const headers = new Headers(request.headers);\n applyStorefrontRequestHeaders(context, headers);\n if (context.url) headers.set(STOREFRONT_URL_HEADER, context.url);\n return headers;\n },\n applyStorefrontRequestHeaders(headers) {\n applyStorefrontRequestHeaders(context, headers);\n },\n captureSubrequestHeaders,\n consumeStorefrontResponseHeaders(headers) {\n captureSubrequestHeaders(headers);\n headers.delete(\"set-cookie\");\n },\n markResponseAsPersonalized(reason) {\n personalizedResponseReason ??= reason;\n },\n markResponseAsSessionEstablishing(reason) {\n sessionEstablishingReason ??= reason;\n },\n applyResponseHeaders(headers) {\n headers.set(\"powered-by\", \"Shopify, Hydrogen\");\n\n // Documents may be shared or streamed, so they must not carry buyer-specific state.\n const isDocumentResponse =\n context.documentRequest || (headers.get(\"content-type\")?.startsWith(\"text/html\") ?? false);\n\n // Keep GET and HEAD responses cacheable, and fail closed when the request method is unknown.\n // Cold sessions may only be established by the explicitly marked consent request.\n const mayReturnShopifyState =\n requestMethod !== undefined &&\n requestMethod !== \"GET\" &&\n requestMethod !== \"HEAD\" &&\n !isDocumentResponse &&\n (hasEssentialCookie ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined);\n\n // Replay cookies captured from fresh SFAPI and proxy responses when allowed.\n if (capturedCookies && mayReturnShopifyState) {\n const existingSetCookies = new Set(headers.getSetCookie());\n for (const value of capturedCookies) {\n if (existingSetCookies.has(value)) continue;\n headers.append(\"set-cookie\", value);\n existingSetCookies.add(value);\n }\n }\n\n // Consent/session responses can contain private state in the body without setting cookies.\n if (\n personalizedResponseReason ||\n isConsentManagementRequest ||\n sessionEstablishingReason !== undefined ||\n headers.has(\"set-cookie\")\n ) {\n applyPrivateResponseCacheHeaders(headers);\n }\n },\n } as ShopifyRequestContext<I18n>;\n}\n\nfunction normalizeI18n<I18n extends I18nConfig>(i18n: I18n): NormalizedI18nConfig<I18n> {\n return {\n ...i18n,\n pathPrefix: normalizePathPrefix(i18n.pathPrefix),\n } as NormalizedI18nConfig<I18n>;\n}\n\nfunction applyStorefrontRequestHeaders(context: Context, headers: Headers): void {\n headers.set(SDK_VARIANT_HEADER, \"hydrogen\");\n headers.set(SDK_VARIANT_SOURCE_HEADER, \"kit\");\n headers.set(SDK_VERSION_HEADER, STOREFRONT_API_VERSION);\n headers.set(HYDROGEN_VERSION_HEADER, __HYDROGEN_VERSION__);\n headers.set(REQUEST_GROUP_ID_HEADER, context.requestGroupId);\n\n if (context.cookie) headers.set(\"cookie\", context.cookie);\n else headers.delete(\"cookie\");\n if (context.globalPrivacyControl !== undefined) {\n headers.set(SEC_GPC_HEADER, context.globalPrivacyControl);\n } else headers.delete(SEC_GPC_HEADER);\n if (context.storefrontOrigin) {\n headers.set(SHOPIFY_STOREFRONT_ORIGIN_HEADER, context.storefrontOrigin);\n } else headers.delete(SHOPIFY_STOREFRONT_ORIGIN_HEADER);\n}\n\nfunction getUrlOrigin(url: string | undefined): string | undefined {\n if (!url) return undefined;\n try {\n return new URL(url).origin;\n } catch {\n return undefined;\n }\n}\n\nfunction isDocumentRequest(request: StorefrontRequest): boolean {\n if (request.method && request.method !== \"GET\" && request.method !== \"HEAD\") return false;\n\n const destination = request.headers.get(\"sec-fetch-dest\");\n if (destination === \"document\") return true;\n\n return request.headers.get(\"accept\")?.includes(\"text/html\") ?? false;\n}\n\nfunction parseCookieHeader(cookieHeader: string | undefined): Map<string, string> {\n const cookies = new Map<string, string>();\n if (!cookieHeader) return cookies;\n\n for (const cookie of cookieHeader.split(\";\")) {\n const separator = cookie.indexOf(\"=\");\n if (separator < 0) continue;\n\n const name = cookie.slice(0, separator).trim();\n if (!cookies.has(name)) cookies.set(name, cookie.slice(separator + 1));\n }\n\n return cookies;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopify/hydrogen",
3
- "version": "2026.10.0-preview.5",
3
+ "version": "2026.10.0",
4
4
  "description": "Framework-agnostic Shopify storefront SDK and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "Shopify",
@@ -6,12 +6,12 @@ Read `package.json` in the current directory.
6
6
 
7
7
  If it does not exist, stop and tell the user this skill must run in a javascript application with a `package.json` at its root.
8
8
 
9
- If it does not contain `@shopify/hydrogen@preview` (or a more specific hash) in its dependencies, ask the user to install it. Do not assume package managers or install for the user.
9
+ If it does not contain `@shopify/hydrogen` 2026.10.0 or later in its dependencies, ask the user to install it. Do not assume package managers or install for the user.
10
10
 
11
11
  ### Continue when
12
12
 
13
13
  - [ ] Current directory has `package.json`
14
- - [ ] Current project includes `@shopify/hydrogen@preview` or similar in its dependencies
14
+ - [ ] Current project includes `@shopify/hydrogen` 2026.10.0 or later in its dependencies
15
15
 
16
16
  ## Styling
17
17