@shopify/hydrogen 0.14.0 → 0.16.1

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 (155) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/dist/esnext/client.d.ts +4 -0
  3. package/dist/esnext/client.js +4 -0
  4. package/dist/esnext/components/CartProvider/CartProvider.client.js +23 -0
  5. package/dist/esnext/components/DevTools.d.ts +1 -0
  6. package/dist/esnext/components/DevTools.js +128 -0
  7. package/dist/esnext/components/Link/Link.client.js +1 -1
  8. package/dist/esnext/constants.d.ts +6 -0
  9. package/dist/esnext/constants.js +6 -0
  10. package/dist/esnext/entry-client.js +7 -4
  11. package/dist/esnext/entry-server.d.ts +1 -1
  12. package/dist/esnext/entry-server.js +29 -15
  13. package/dist/esnext/foundation/Analytics/Analytics.client.d.ts +3 -0
  14. package/dist/esnext/foundation/Analytics/Analytics.client.js +28 -0
  15. package/dist/esnext/foundation/Analytics/Analytics.server.d.ts +1 -0
  16. package/dist/esnext/foundation/Analytics/Analytics.server.js +38 -0
  17. package/dist/esnext/foundation/Analytics/ClientAnalytics.d.ts +24 -0
  18. package/dist/esnext/foundation/Analytics/ClientAnalytics.js +91 -0
  19. package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
  20. package/dist/esnext/foundation/Analytics/ServerAnalyticsRoute.server.js +33 -0
  21. package/dist/esnext/foundation/Analytics/const.d.ts +8 -0
  22. package/dist/esnext/foundation/Analytics/const.js +8 -0
  23. package/dist/esnext/foundation/Analytics/hook.d.ts +1 -0
  24. package/dist/esnext/foundation/Analytics/hook.js +7 -0
  25. package/dist/esnext/foundation/Analytics/index.d.ts +2 -0
  26. package/dist/esnext/foundation/Analytics/index.js +2 -0
  27. package/dist/esnext/foundation/Analytics/types.d.ts +5 -0
  28. package/dist/esnext/foundation/Analytics/types.js +1 -0
  29. package/dist/esnext/foundation/Analytics/utils.d.ts +1 -0
  30. package/dist/esnext/foundation/Analytics/utils.js +8 -0
  31. package/dist/esnext/foundation/Boomerang/Boomerang.client.js +3 -1
  32. package/dist/esnext/foundation/Route/Route.server.js +4 -0
  33. package/dist/esnext/foundation/Router/BrowserRouter.client.js +68 -15
  34. package/dist/esnext/foundation/ServerRequestProvider/ServerRequestProvider.js +1 -1
  35. package/dist/esnext/foundation/ShopifyProvider/types.d.ts +2 -5
  36. package/dist/esnext/foundation/fetchSync/client/fetchSync.d.ts +10 -0
  37. package/dist/esnext/foundation/fetchSync/client/fetchSync.js +27 -0
  38. package/dist/esnext/foundation/fetchSync/server/fetchSync.d.ts +8 -0
  39. package/dist/esnext/foundation/fetchSync/server/fetchSync.js +27 -0
  40. package/dist/esnext/foundation/fetchSync/types.d.ts +5 -0
  41. package/dist/esnext/foundation/fetchSync/types.js +1 -0
  42. package/dist/esnext/foundation/useQuery/hooks.d.ts +4 -2
  43. package/dist/esnext/foundation/useQuery/hooks.js +10 -6
  44. package/dist/esnext/foundation/useUrl/useUrl.js +8 -1
  45. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  46. package/dist/esnext/framework/Hydration/ServerComponentRequest.server.js +11 -5
  47. package/dist/esnext/framework/cache/in-memory.js +5 -5
  48. package/dist/esnext/framework/cache.d.ts +1 -2
  49. package/dist/esnext/framework/cache.js +67 -22
  50. package/dist/esnext/framework/plugin.js +10 -0
  51. package/dist/esnext/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
  52. package/dist/esnext/framework/plugins/vite-plugin-hydrogen-config.js +11 -2
  53. package/dist/esnext/hooks/useShopQuery/hooks.js +32 -25
  54. package/dist/esnext/index.d.ts +2 -0
  55. package/dist/esnext/index.js +2 -0
  56. package/dist/esnext/types.d.ts +6 -1
  57. package/dist/esnext/utilities/apiRoutes.d.ts +2 -3
  58. package/dist/esnext/utilities/apiRoutes.js +14 -9
  59. package/dist/esnext/utilities/hash.d.ts +2 -0
  60. package/dist/esnext/utilities/hash.js +7 -0
  61. package/dist/esnext/utilities/log/log-cache-api-status.js +1 -1
  62. package/dist/esnext/utilities/log/log-cache-header.js +1 -1
  63. package/dist/esnext/utilities/log/log-query-timeline.js +1 -1
  64. package/dist/esnext/utilities/storefrontApi.d.ts +4 -0
  65. package/dist/esnext/utilities/storefrontApi.js +21 -0
  66. package/dist/esnext/utilities/suspense.d.ts +5 -0
  67. package/dist/esnext/utilities/suspense.js +32 -0
  68. package/dist/esnext/utilities/template.js +1 -1
  69. package/dist/esnext/version.d.ts +1 -1
  70. package/dist/esnext/version.js +1 -1
  71. package/dist/node/constants.d.ts +6 -0
  72. package/dist/node/constants.js +7 -1
  73. package/dist/node/entry-server.d.ts +1 -1
  74. package/dist/node/entry-server.js +28 -17
  75. package/dist/node/foundation/Analytics/Analytics.client.d.ts +3 -0
  76. package/dist/node/foundation/Analytics/Analytics.client.js +32 -0
  77. package/dist/node/foundation/Analytics/Analytics.server.d.ts +1 -0
  78. package/dist/node/foundation/Analytics/Analytics.server.js +45 -0
  79. package/dist/node/foundation/Analytics/ClientAnalytics.d.ts +24 -0
  80. package/dist/node/foundation/Analytics/ClientAnalytics.js +94 -0
  81. package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.d.ts +2 -0
  82. package/dist/node/foundation/Analytics/ServerAnalyticsRoute.server.js +37 -0
  83. package/dist/node/foundation/Analytics/const.d.ts +8 -0
  84. package/dist/node/foundation/Analytics/const.js +11 -0
  85. package/dist/node/foundation/Analytics/hook.d.ts +1 -0
  86. package/dist/node/foundation/Analytics/hook.js +11 -0
  87. package/dist/node/foundation/Analytics/index.d.ts +2 -0
  88. package/dist/node/foundation/Analytics/index.js +7 -0
  89. package/dist/node/foundation/Analytics/types.d.ts +5 -0
  90. package/dist/node/foundation/Analytics/types.js +2 -0
  91. package/dist/node/foundation/Analytics/utils.d.ts +1 -0
  92. package/dist/node/foundation/Analytics/utils.js +12 -0
  93. package/dist/node/foundation/Router/BrowserRouter.client.js +67 -14
  94. package/dist/node/foundation/ServerRequestProvider/ServerRequestProvider.js +2 -2
  95. package/dist/node/foundation/ShopifyProvider/types.d.ts +2 -5
  96. package/dist/node/framework/Hydration/ServerComponentRequest.server.d.ts +1 -0
  97. package/dist/node/framework/Hydration/ServerComponentRequest.server.js +13 -7
  98. package/dist/node/framework/cache/in-memory.js +5 -5
  99. package/dist/node/framework/cache.d.ts +1 -2
  100. package/dist/node/framework/cache.js +71 -27
  101. package/dist/node/framework/plugin.js +10 -0
  102. package/dist/node/framework/plugins/vite-plugin-css-modules-rsc.js +1 -1
  103. package/dist/node/framework/plugins/vite-plugin-hydrogen-config.js +11 -2
  104. package/dist/node/types.d.ts +6 -1
  105. package/dist/node/utilities/apiRoutes.d.ts +2 -3
  106. package/dist/node/utilities/apiRoutes.js +14 -9
  107. package/dist/node/utilities/flattenConnection/flattenConnection.d.ts +6 -0
  108. package/dist/node/utilities/flattenConnection/flattenConnection.js +15 -0
  109. package/dist/node/utilities/flattenConnection/index.d.ts +1 -0
  110. package/dist/node/utilities/flattenConnection/index.js +5 -0
  111. package/dist/node/utilities/hash.d.ts +2 -0
  112. package/dist/node/utilities/hash.js +11 -0
  113. package/dist/node/utilities/image_size.d.ts +30 -0
  114. package/dist/node/utilities/image_size.js +110 -0
  115. package/dist/node/utilities/index.d.ts +11 -0
  116. package/dist/node/utilities/index.js +32 -0
  117. package/dist/node/utilities/isClient/index.d.ts +1 -0
  118. package/dist/node/utilities/isClient/index.js +5 -0
  119. package/dist/node/utilities/isClient/isClient.d.ts +4 -0
  120. package/dist/node/utilities/isClient/isClient.js +10 -0
  121. package/dist/node/utilities/isServer/index.d.ts +1 -0
  122. package/dist/node/utilities/isServer/index.js +5 -0
  123. package/dist/node/utilities/isServer/isServer.d.ts +4 -0
  124. package/dist/node/utilities/isServer/isServer.js +11 -0
  125. package/dist/node/utilities/load_script.d.ts +3 -0
  126. package/dist/node/utilities/load_script.js +27 -0
  127. package/dist/node/utilities/log/log-cache-api-status.js +1 -1
  128. package/dist/node/utilities/log/log-cache-header.js +2 -2
  129. package/dist/node/utilities/log/log-query-timeline.js +2 -2
  130. package/dist/node/utilities/measurement.d.ts +3 -0
  131. package/dist/node/utilities/measurement.js +103 -0
  132. package/dist/node/utilities/parseMetafieldValue/index.d.ts +1 -0
  133. package/dist/node/utilities/parseMetafieldValue/index.js +5 -0
  134. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.d.ts +6 -0
  135. package/dist/node/utilities/parseMetafieldValue/parseMetafieldValue.js +39 -0
  136. package/dist/node/utilities/storefrontApi.d.ts +4 -0
  137. package/dist/node/utilities/storefrontApi.js +25 -0
  138. package/dist/node/utilities/suspense.d.ts +12 -0
  139. package/dist/node/utilities/suspense.js +64 -0
  140. package/dist/node/utilities/template.js +1 -1
  141. package/dist/node/utilities/video_parameters.d.ts +47 -0
  142. package/dist/node/utilities/video_parameters.js +27 -0
  143. package/dist/node/version.d.ts +1 -1
  144. package/dist/node/version.js +1 -1
  145. package/package.json +1 -1
  146. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-plugin.js +9 -21
  147. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.development.server.js +51 -47
  148. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.browser.production.min.server.js +30 -29
  149. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.development.server.js +51 -47
  150. package/vendor/react-server-dom-vite/cjs/react-server-dom-vite-writer.node.production.min.server.js +17 -17
  151. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-client-proxy.js +55 -45
  152. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-plugin.js +9 -21
  153. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.browser.server.js +51 -47
  154. package/vendor/react-server-dom-vite/esm/react-server-dom-vite-writer.node.server.js +51 -47
  155. package/vendor/react-server-dom-vite/package.json +3 -3
@@ -1,14 +1,20 @@
1
1
  import { getCache } from './runtime';
2
2
  import { CacheSeconds, generateCacheControlHeader, } from '../framework/CachingStrategy';
3
- export function generateSubRequestCacheControlHeader(userCacheOptions) {
4
- return generateCacheControlHeader(userCacheOptions || CacheSeconds());
3
+ import { hashKey } from '../utilities/hash';
4
+ import { logCacheApiStatus } from '../utilities/log';
5
+ function getCacheControlSetting(userCacheOptions, options) {
6
+ if (userCacheOptions && options) {
7
+ return {
8
+ ...userCacheOptions,
9
+ ...options,
10
+ };
11
+ }
12
+ else {
13
+ return userCacheOptions || CacheSeconds();
14
+ }
5
15
  }
6
- export function hashKey(key) {
7
- const rawKey = key instanceof Array ? key : [key];
8
- /**
9
- * TODO: Smarter hash
10
- */
11
- return rawKey.map((k) => JSON.stringify(k)).join('');
16
+ export function generateSubRequestCacheControlHeader(userCacheOptions) {
17
+ return generateCacheControlHeader(getCacheControlSetting(userCacheOptions));
12
18
  }
13
19
  /**
14
20
  * Cache API is weird. We just need a full URL, so we make one up.
@@ -29,8 +35,11 @@ export async function getItemFromCache(key) {
29
35
  const url = getKeyUrl(hashKey(key));
30
36
  const request = new Request(url);
31
37
  const response = await cache.match(request);
32
- if (!response)
38
+ if (!response) {
39
+ logCacheApiStatus('MISS', url);
33
40
  return;
41
+ }
42
+ logCacheApiStatus('HIT', url);
34
43
  return [await response.json(), response];
35
44
  }
36
45
  /**
@@ -43,14 +52,53 @@ export async function setItemInCache(key, value, userCacheOptions) {
43
52
  }
44
53
  const url = getKeyUrl(hashKey(key));
45
54
  const request = new Request(url);
55
+ /**
56
+ * We are manually managing staled request by adding this workaround.
57
+ * Why? cache control header support is dependent on hosting platform
58
+ *
59
+ * For example:
60
+ *
61
+ * Cloudflare's Cache API does not support `stale-while-revalidate`.
62
+ * Cloudflare cache control header has a very odd behaviour.
63
+ * Say we have the following cache control header on a request:
64
+ *
65
+ * public, max-age=15, stale-while-revalidate=30
66
+ *
67
+ * When there is a cache.match HIT, the cache control header would become
68
+ *
69
+ * public, max-age=14400, stale-while-revalidate=30
70
+ *
71
+ * == `stale-while-revalidate` workaround ==
72
+ * Update response max-age so that:
73
+ *
74
+ * max-age = max-age + stale-while-revalidate
75
+ *
76
+ * For example:
77
+ *
78
+ * public, max-age=1, stale-while-revalidate=9
79
+ * |
80
+ * V
81
+ * public, max-age=10, stale-while-revalidate=9
82
+ *
83
+ * Store the following information in the response header:
84
+ *
85
+ * cache-put-date - UTC time string of when this request is PUT into cache
86
+ *
87
+ * Note on `cache-put-date`: The `response.headers.get('date')` isn't static. I am
88
+ * not positive what date this is returning but it is never over 500 ms
89
+ * after subtracting from the current timestamp.
90
+ *
91
+ * `isStale` function will use the above information to test for stale-ness of a cached response
92
+ */
93
+ const cacheControl = getCacheControlSetting(userCacheOptions);
46
94
  const headers = new Headers({
47
- 'cache-control': generateSubRequestCacheControlHeader(userCacheOptions),
95
+ 'cache-control': generateSubRequestCacheControlHeader(getCacheControlSetting(cacheControl, {
96
+ maxAge: (cacheControl.maxAge || 0) + (cacheControl.staleWhileRevalidate || 0),
97
+ })),
98
+ 'cache-put-date': new Date().toUTCString(),
48
99
  });
49
100
  const response = new Response(JSON.stringify(value), { headers });
50
- /**
51
- * WARNING: Cloudflare's Cache API does not support `stale-while-revalidate`
52
- * so this implementation will not work as expected on that platform.
53
- */
101
+ logCacheApiStatus('PUT', url);
54
102
  await cache.put(request, response);
55
103
  }
56
104
  export async function deleteItemFromCache(key) {
@@ -59,20 +107,17 @@ export async function deleteItemFromCache(key) {
59
107
  return;
60
108
  const url = getKeyUrl(hashKey(key));
61
109
  const request = new Request(url);
110
+ logCacheApiStatus('DELETE', url);
62
111
  await cache.delete(request);
63
112
  }
64
113
  /**
65
114
  * Manually check the response to see if it's stale.
66
115
  */
67
- export function isStale(response) {
68
- const responseDate = response.headers.get('date');
69
- const responseCacheControl = response.headers.get('cache-control');
70
- if (!responseDate || !responseCacheControl)
71
- return false;
72
- const responseMaxAgeMatch = responseCacheControl.match(/max-age=(\d+)/);
73
- if (!responseMaxAgeMatch)
116
+ export function isStale(response, userCacheOptions) {
117
+ const responseMaxAge = getCacheControlSetting(userCacheOptions).maxAge || 0;
118
+ const responseDate = response.headers.get('cache-put-date');
119
+ if (!responseDate)
74
120
  return false;
75
- const responseMaxAge = parseInt(responseMaxAgeMatch[1]);
76
121
  const ageInMs = new Date().valueOf() - new Date(responseDate).valueOf();
77
122
  const age = ageInMs / 1000;
78
123
  return age > responseMaxAge;
@@ -12,6 +12,15 @@ import react from '@vitejs/plugin-react';
12
12
  import path from 'path';
13
13
  import cssModulesRsc from './plugins/vite-plugin-css-modules-rsc';
14
14
  export default (shopifyConfig, pluginOptions = {}) => {
15
+ let hydrogenUiPath;
16
+ try {
17
+ hydrogenUiPath = path.join(
18
+ // eslint-disable-next-line node/no-missing-require
19
+ path.dirname(require.resolve('@shopify/hydrogen-ui/client')));
20
+ }
21
+ catch (error) {
22
+ // hydrogen-ui isn't installed, so don't worry about it
23
+ }
15
24
  return [
16
25
  process.env.VITE_INSPECT && inspect(),
17
26
  hydrogenConfig(),
@@ -24,6 +33,7 @@ export default (shopifyConfig, pluginOptions = {}) => {
24
33
  rsc({
25
34
  clientComponentPaths: [
26
35
  path.join(path.dirname(require.resolve('@shopify/hydrogen/package.json'))),
36
+ ...[hydrogenUiPath].filter(Boolean),
27
37
  ],
28
38
  isServerComponentImporterAllowed(importer, source) {
29
39
  // Always allow the entry server (e.g. App.server.jsx) to be imported
@@ -20,7 +20,7 @@ export default function cssModulesRsc() {
20
20
  enforce: 'post',
21
21
  transform(code, id) {
22
22
  if (id.includes('.module.') && cssMap.has(id)) {
23
- return code.replace(/export default .*$/gms, `import React from 'react'; export const StyleTag = () => React.createElement('style', {}, \`${cssMap.get(id)}\`);`);
23
+ return code.replace(/export default .*$/gms, `import React from 'react'; export const StyleTag = () => React.createElement('style', {dangerouslySetInnerHTML: {__html: ${JSON.stringify(cssMap.get(id))}}});`);
24
24
  }
25
25
  },
26
26
  },
@@ -36,11 +36,18 @@ export default () => {
36
36
  // Reload when updating local Hydrogen lib
37
37
  server: process.env.LOCAL_DEV && {
38
38
  watch: {
39
- ignored: ['!**/node_modules/@shopify/hydrogen/**'],
39
+ ignored: [
40
+ '!**/node_modules/@shopify/hydrogen/**',
41
+ '!**/node_modules/@shopify/hydrogen-ui/**',
42
+ ],
40
43
  },
41
44
  },
42
45
  optimizeDeps: {
43
- exclude: ['@shopify/hydrogen/client', '@shopify/hydrogen/entry-client'],
46
+ exclude: [
47
+ '@shopify/hydrogen/client',
48
+ '@shopify/hydrogen/entry-client',
49
+ '@shopify/hydrogen-ui',
50
+ ],
44
51
  include: [
45
52
  /**
46
53
  * Additionally, the following dependencies have trouble loading the
@@ -57,6 +64,8 @@ export default () => {
57
64
  'react',
58
65
  'react-dom/client',
59
66
  'react-server-dom-vite/client-proxy',
67
+ // https://github.com/vitejs/vite/issues/6215
68
+ 'react/jsx-runtime',
60
69
  ],
61
70
  },
62
71
  define: {
@@ -1,31 +1,51 @@
1
1
  import { useShop } from '../../foundation/useShop';
2
2
  import { getLoggerWithContext } from '../../utilities/log';
3
- import { useQuery } from '../../foundation/useQuery';
4
- import { fetchBuilder, graphqlRequestBody } from '../../utilities';
3
+ import { graphqlRequestBody } from '../../utilities';
5
4
  import { getConfig } from '../../framework/config';
6
5
  import { useServerRequest } from '../../foundation/ServerRequestProvider';
7
6
  import { injectGraphQLTracker } from '../../utilities/graphql-tracker';
8
7
  import { sendMessageToClient } from '../../utilities/devtools';
8
+ import { fetchSync } from '../../foundation/fetchSync/server/fetchSync';
9
9
  import { META_ENV_SSR } from '../../foundation/ssr-interop';
10
+ import { getStorefrontApiRequestHeaders } from '../../utilities/storefrontApi';
10
11
  // Check if the response body has GraphQL errors
11
12
  // https://spec.graphql.org/June2018/#sec-Response-Format
12
- const shouldCacheResponse = (body) => !(body === null || body === void 0 ? void 0 : body.errors);
13
+ const shouldCacheResponse = ([body]) => { var _a; return !((_a = JSON.parse(body)) === null || _a === void 0 ? void 0 : _a.errors); };
13
14
  /**
14
15
  * The `useShopQuery` hook allows you to make server-only GraphQL queries to the Storefront API. It must be a descendent of a `ShopifyProvider` component.
15
16
  */
16
17
  export function useShopQuery({ query, variables = {}, cache, preload = false, }) {
17
18
  var _a;
19
+ /**
20
+ * If no query is passed, we no-op here to allow developers to obey the Rules of Hooks.
21
+ */
22
+ if (!query) {
23
+ return { data: undefined, errors: undefined };
24
+ }
18
25
  if (!META_ENV_SSR) {
19
26
  throw new Error('Shopify Storefront API requests should only be made from the server.');
20
27
  }
21
28
  const serverRequest = useServerRequest();
22
29
  const log = getLoggerWithContext(serverRequest);
23
30
  const body = query ? graphqlRequestBody(query, variables) : '';
24
- const { key, url, requestInit } = useCreateShopRequest(body);
25
- const { data, error: useQueryError } = useQuery(key, query
26
- ? fetchBuilder(url, requestInit)
27
- : // If no query, avoid calling SFAPI & return nothing
28
- async () => ({ data: undefined, errors: undefined }), { cache, shouldCacheResponse, preload });
31
+ const { url, requestInit } = useCreateShopRequest(body);
32
+ let data;
33
+ let useQueryError;
34
+ try {
35
+ data = fetchSync(url, {
36
+ ...requestInit,
37
+ cache,
38
+ preload,
39
+ shouldCacheResponse,
40
+ }).json();
41
+ }
42
+ catch (error) {
43
+ // Pass-through thrown promise for Suspense functionality
44
+ if (error === null || error === void 0 ? void 0 : error.then) {
45
+ throw error;
46
+ }
47
+ useQueryError = error;
48
+ }
29
49
  /**
30
50
  * The fetch request itself failed, so we handle that differently than a GraphQL error
31
51
  */
@@ -95,26 +115,13 @@ export function useShopQuery({ query, variables = {}, cache, preload = false, })
95
115
  return data;
96
116
  }
97
117
  function useCreateShopRequest(body) {
98
- var _a;
99
118
  const { storeDomain, storefrontToken, storefrontApiVersion } = useShop();
100
119
  const request = useServerRequest();
101
- const secretToken = typeof Oxygen !== 'undefined'
102
- ? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a.SHOPIFY_STOREFRONT_API_SECRET_TOKEN
103
- : null;
104
120
  const buyerIp = request.getBuyerIp();
105
- const extraHeaders = {};
106
- /**
107
- * Only pass one type of storefront token at a time.
108
- */
109
- if (secretToken) {
110
- extraHeaders['Shopify-Storefront-Private-Token'] = secretToken;
111
- }
112
- else {
113
- extraHeaders['X-Shopify-Storefront-Access-Token'] = storefrontToken;
114
- }
115
- if (buyerIp) {
116
- extraHeaders['Shopify-Storefront-Buyer-IP'] = buyerIp;
117
- }
121
+ const extraHeaders = getStorefrontApiRequestHeaders({
122
+ buyerIp,
123
+ storefrontToken,
124
+ });
118
125
  return {
119
126
  key: [storeDomain, storefrontApiVersion, body],
120
127
  url: `https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`,
@@ -13,3 +13,5 @@ export * from './hooks/useShopQuery/hooks';
13
13
  export * from './foundation/useQuery/hooks';
14
14
  export { CartQuery } from './components/CartProvider/cart-queries';
15
15
  export { generateCacheControlHeader, NoStore, CacheSeconds, CacheMinutes, CacheHours, CacheDays, CacheWeeks, CacheMonths, CacheCustom, } from './framework/CachingStrategy';
16
+ export { fetchSync } from './foundation/fetchSync/server/fetchSync';
17
+ export { useServerAnalytics } from './foundation/Analytics';
@@ -17,3 +17,5 @@ export * from './foundation/useQuery/hooks';
17
17
  // it from being bundled with other client components
18
18
  export { CartQuery } from './components/CartProvider/cart-queries';
19
19
  export { generateCacheControlHeader, NoStore, CacheSeconds, CacheMinutes, CacheHours, CacheDays, CacheWeeks, CacheMonths, CacheCustom, } from './framework/CachingStrategy';
20
+ export { fetchSync } from './foundation/fetchSync/server/fetchSync';
21
+ export { useServerAnalytics } from './foundation/Analytics';
@@ -35,14 +35,19 @@ export declare type Hook = (params: {
35
35
  url: URL;
36
36
  } & Record<string, any>) => any | Promise<any>;
37
37
  export declare type ImportGlobEagerOutput = Record<string, Record<'default' | 'api', any>>;
38
+ export declare type ServerAnalyticsConnector = {
39
+ request: (request: Request, data?: any, contentType?: 'json' | 'text') => void;
40
+ };
38
41
  export declare type ServerHandlerConfig = {
39
42
  routes?: ImportGlobEagerOutput;
40
43
  shopifyConfig: ShopifyConfig;
44
+ serverAnalyticsConnectors?: Array<ServerAnalyticsConnector>;
41
45
  };
42
46
  export declare type ClientHandlerConfig = {
43
47
  shopifyConfig: ShopifyConfig;
44
48
  /** React's StrictMode is on by default for your client side app; if you want to turn it off (not recommended), you can pass `false` */
45
49
  strictMode?: boolean;
50
+ showDevTools?: boolean;
46
51
  };
47
52
  export declare type ClientHandler = (App: React.ElementType, config: ClientHandlerConfig) => Promise<void>;
48
53
  export interface GraphQLConnection<T> {
@@ -74,7 +79,7 @@ export interface AllCacheOptions {
74
79
  sMaxAge?: number;
75
80
  staleIfError?: number;
76
81
  }
77
- export declare type CachingStrategy = NoStoreStrategy | AllCacheOptions;
82
+ export declare type CachingStrategy = AllCacheOptions;
78
83
  export interface HydrogenVitePluginOptions {
79
84
  devCache?: boolean;
80
85
  purgeQueryCacheOnBuild?: boolean;
@@ -1,4 +1,5 @@
1
1
  import { ImportGlobEagerOutput, ShopifyConfig } from '../types';
2
+ import type { ServerComponentRequest } from '../framework/Hydration/ServerComponentRequest.server';
2
3
  import type { ASTNode } from 'graphql';
3
4
  declare type RouteParams = Record<string, string>;
4
5
  declare type RequestOptions = {
@@ -29,8 +30,6 @@ interface QueryShopArgs {
29
30
  query: ASTNode | string;
30
31
  /** An object of the variables for the GraphQL query. */
31
32
  variables?: Record<string, any>;
32
- /** A string corresponding to a valid locale identifier like `en-us` used to make the request. */
33
- locale?: string;
34
33
  }
35
- export declare function renderApiRoute(request: Request, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response>;
34
+ export declare function renderApiRoute(request: ServerComponentRequest, route: ApiRouteMatch, shopifyConfig: ShopifyConfig): Promise<Response | Request>;
36
35
  export {};
@@ -1,6 +1,7 @@
1
1
  import { matchPath } from './matchPath';
2
2
  import { getLoggerWithContext, logServerResponse } from '../utilities/log/';
3
3
  import { fetchBuilder, graphqlRequestBody } from './fetch';
4
+ import { getStorefrontApiRequestHeaders } from './storefrontApi';
4
5
  let memoizedRoutes = [];
5
6
  let memoizedPages = {};
6
7
  export function getApiRoutes(pages, topLevelPath = '*') {
@@ -64,31 +65,35 @@ export function getApiRouteFromURL(url, routes) {
64
65
  hasServerComponent: foundRoute.hasServerComponent,
65
66
  };
66
67
  }
67
- function queryShopBuilder(shopifyConfig) {
68
- return async function queryShop({ query, variables, locale, }) {
69
- var _a;
70
- const { storeDomain, storefrontApiVersion, storefrontToken, defaultLocale } = shopifyConfig;
68
+ function queryShopBuilder(shopifyConfig, request) {
69
+ return async function queryShop({ query, variables, }) {
70
+ const { storeDomain, storefrontApiVersion, storefrontToken } = shopifyConfig;
71
+ const buyerIp = request.getBuyerIp();
72
+ const extraHeaders = getStorefrontApiRequestHeaders({
73
+ buyerIp,
74
+ storefrontToken,
75
+ });
71
76
  const fetcher = fetchBuilder(`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`, {
72
77
  method: 'POST',
73
78
  body: graphqlRequestBody(query, variables),
74
79
  headers: {
75
- 'X-Shopify-Storefront-Access-Token': storefrontToken,
76
- 'Accept-Language': (_a = locale) !== null && _a !== void 0 ? _a : defaultLocale,
77
80
  'Content-Type': 'application/json',
81
+ ...extraHeaders,
78
82
  },
79
83
  });
80
84
  return await fetcher();
81
85
  };
82
86
  }
83
87
  export async function renderApiRoute(request, route, shopifyConfig) {
88
+ var _a;
84
89
  let response;
85
90
  const log = getLoggerWithContext(request);
86
91
  try {
87
92
  response = await route.resource(request, {
88
93
  params: route.params,
89
- queryShop: queryShopBuilder(shopifyConfig),
94
+ queryShop: queryShopBuilder(shopifyConfig, request),
90
95
  });
91
- if (!(response instanceof Response)) {
96
+ if (!(response instanceof Response || response instanceof Request)) {
92
97
  if (typeof response === 'string' || response instanceof String) {
93
98
  response = new Response(response);
94
99
  }
@@ -105,6 +110,6 @@ export async function renderApiRoute(request, route, shopifyConfig) {
105
110
  log.error(e);
106
111
  response = new Response('Error processing: ' + request.url, { status: 500 });
107
112
  }
108
- logServerResponse('api', request, response.status);
113
+ logServerResponse('api', request, (_a = response.status) !== null && _a !== void 0 ? _a : 200);
109
114
  return response;
110
115
  }
@@ -0,0 +1,2 @@
1
+ import { QueryKey } from '../types';
2
+ export declare function hashKey(key: QueryKey): string;
@@ -0,0 +1,7 @@
1
+ export function hashKey(key) {
2
+ const rawKey = key instanceof Array ? key : [key];
3
+ /**
4
+ * TODO: Smarter hash
5
+ */
6
+ return rawKey.map((k) => JSON.stringify(k)).join('');
7
+ }
@@ -5,5 +5,5 @@ export function logCacheApiStatus(status, url) {
5
5
  if (!log.options().showCacheApiStatus) {
6
6
  return;
7
7
  }
8
- log.debug(gray(`[Cache] ${status === null || status === void 0 ? void 0 : status.padEnd(6)} query ${findQueryName(url)}`));
8
+ log.debug(gray(`[Cache] ${status === null || status === void 0 ? void 0 : status.padEnd(8)} query ${findQueryName(url)}`));
9
9
  }
@@ -1,4 +1,4 @@
1
- import { hashKey } from '../../framework/cache';
1
+ import { hashKey } from '../hash';
2
2
  import { findQueryName, parseUrl } from './utils';
3
3
  import { gray } from 'kolorist';
4
4
  import { getLoggerWithContext } from './log';
@@ -1,4 +1,4 @@
1
- import { hashKey } from '../../framework/cache';
1
+ import { hashKey } from '../hash';
2
2
  import { findQueryName, parseUrl } from './utils';
3
3
  import { gray, red, yellow, green } from 'kolorist';
4
4
  import { getLoggerWithContext } from './log';
@@ -0,0 +1,4 @@
1
+ export declare function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }: {
2
+ buyerIp?: string | null;
3
+ storefrontToken: string;
4
+ }): Record<string, any>;
@@ -0,0 +1,21 @@
1
+ import { OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE, STOREFRONT_API_SECRET_TOKEN_HEADER, STOREFRONT_API_PUBLIC_TOKEN_HEADER, STOREFRONT_API_BUYER_IP_HEADER, } from '../constants';
2
+ export function getStorefrontApiRequestHeaders({ buyerIp, storefrontToken, }) {
3
+ var _a;
4
+ const headers = {};
5
+ const secretToken = typeof Oxygen !== 'undefined'
6
+ ? (_a = Oxygen === null || Oxygen === void 0 ? void 0 : Oxygen.env) === null || _a === void 0 ? void 0 : _a[OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE]
7
+ : null;
8
+ /**
9
+ * Only pass one type of storefront token at a time.
10
+ */
11
+ if (secretToken) {
12
+ headers[STOREFRONT_API_SECRET_TOKEN_HEADER] = secretToken;
13
+ }
14
+ else {
15
+ headers[STOREFRONT_API_PUBLIC_TOKEN_HEADER] = storefrontToken;
16
+ }
17
+ if (buyerIp) {
18
+ headers[STOREFRONT_API_BUYER_IP_HEADER] = buyerIp;
19
+ }
20
+ return headers;
21
+ }
@@ -1,3 +1,4 @@
1
+ import { QueryKey } from '../types';
1
2
  /**
2
3
  * Wrap the fetch promise in a way that React Suspense understands.
3
4
  * Essentially, keep throwing something until you have legit data.
@@ -5,3 +6,7 @@
5
6
  export declare function wrapPromise<T>(promise: Promise<T>): {
6
7
  read: () => T;
7
8
  };
9
+ declare type Await<T> = T extends Promise<infer V> ? V : never;
10
+ export declare const suspendFunction: <Fn extends () => Promise<unknown>>(key: QueryKey, fn: Fn) => Await<ReturnType<Fn>>;
11
+ export declare const preloadFunction: (key: QueryKey, fn: () => Promise<unknown>) => unknown;
12
+ export {};
@@ -1,3 +1,4 @@
1
+ import { hashKey } from './hash';
1
2
  /**
2
3
  * Wrap the fetch promise in a way that React Suspense understands.
3
4
  * Essentially, keep throwing something until you have legit data.
@@ -24,3 +25,34 @@ export function wrapPromise(promise) {
24
25
  };
25
26
  return { read };
26
27
  }
28
+ const browserCache = {};
29
+ /**
30
+ * Perform an async function in a synchronous way for Suspense support.
31
+ * To be used only in the client.
32
+ * Inspired by https://github.com/pmndrs/suspend-react
33
+ */
34
+ function query(key, fn, preload = false) {
35
+ const stringKey = hashKey(key);
36
+ if (browserCache[stringKey]) {
37
+ const entry = browserCache[stringKey];
38
+ if (preload)
39
+ return undefined;
40
+ if (entry.error)
41
+ throw entry.error;
42
+ if (entry.response)
43
+ return entry.response;
44
+ if (!preload)
45
+ throw entry.promise;
46
+ }
47
+ const entry = {
48
+ promise: fn()
49
+ .then((response) => (entry.response = response))
50
+ .catch((error) => (entry.error = error)),
51
+ };
52
+ browserCache[stringKey] = entry;
53
+ if (!preload)
54
+ throw entry.promise;
55
+ return undefined;
56
+ }
57
+ export const suspendFunction = (key, fn) => query(key, fn);
58
+ export const preloadFunction = (key, fn) => query(key, fn, true);
@@ -6,7 +6,7 @@ export function stripScriptsFromTemplate(template) {
6
6
  var _a;
7
7
  const bootstrapScripts = [];
8
8
  const bootstrapModules = [];
9
- const scripts = template.matchAll(/<script.+?src="(?<script>([^"]+?))".*?><\/script>/g);
9
+ const scripts = template.matchAll(/<script\n*?.+?src="(?<script>([^"]+?))"\n*.*?><\/script>/g);
10
10
  for (const match of scripts) {
11
11
  const scriptName = (_a = match.groups) === null || _a === void 0 ? void 0 : _a.script;
12
12
  if (!scriptName)
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "0.14.0";
1
+ export declare const LIB_VERSION = "0.16.1";
@@ -1 +1 @@
1
- export const LIB_VERSION = '0.14.0';
1
+ export const LIB_VERSION = '0.16.1';
@@ -1 +1,7 @@
1
1
  export declare const RSC_PATHNAME = "/__rsc";
2
+ export declare const EVENT_PATHNAME = "/__event";
3
+ export declare const EVENT_PATHNAME_REGEX: RegExp;
4
+ export declare const OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = "SHOPIFY_STOREFRONT_API_SECRET_TOKEN";
5
+ export declare const STOREFRONT_API_SECRET_TOKEN_HEADER = "Shopify-Storefront-Private-Token";
6
+ export declare const STOREFRONT_API_PUBLIC_TOKEN_HEADER = "X-Shopify-Storefront-Access-Token";
7
+ export declare const STOREFRONT_API_BUYER_IP_HEADER = "Shopify-Storefront-Buyer-IP";
@@ -1,4 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RSC_PATHNAME = void 0;
3
+ exports.STOREFRONT_API_BUYER_IP_HEADER = exports.STOREFRONT_API_PUBLIC_TOKEN_HEADER = exports.STOREFRONT_API_SECRET_TOKEN_HEADER = exports.OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = exports.EVENT_PATHNAME_REGEX = exports.EVENT_PATHNAME = exports.RSC_PATHNAME = void 0;
4
4
  exports.RSC_PATHNAME = '/__rsc';
5
+ exports.EVENT_PATHNAME = '/__event';
6
+ exports.EVENT_PATHNAME_REGEX = new RegExp(`^${exports.EVENT_PATHNAME}\/`);
7
+ exports.OXYGEN_SECRET_TOKEN_ENVIRONMENT_VARIABLE = 'SHOPIFY_STOREFRONT_API_SECRET_TOKEN';
8
+ exports.STOREFRONT_API_SECRET_TOKEN_HEADER = 'Shopify-Storefront-Private-Token';
9
+ exports.STOREFRONT_API_PUBLIC_TOKEN_HEADER = 'X-Shopify-Storefront-Access-Token';
10
+ exports.STOREFRONT_API_BUYER_IP_HEADER = 'Shopify-Storefront-Buyer-IP';
@@ -19,5 +19,5 @@ interface RequestHandlerOptions {
19
19
  export interface RequestHandler {
20
20
  (request: Request | IncomingMessage, options: RequestHandlerOptions): Promise<Response | undefined>;
21
21
  }
22
- export declare const renderHydrogen: (App: any, { shopifyConfig, routes }: ServerHandlerConfig) => RequestHandler;
22
+ export declare const renderHydrogen: (App: any, { shopifyConfig, routes, serverAnalyticsConnectors }: ServerHandlerConfig) => RequestHandler;
23
23
  export default renderHydrogen;