@weaverse/hydrogen 5.24.0 → 6.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/index.js +23 -2444
  2. package/dist/index.mjs +4 -2407
  3. package/dist/internal.js +64 -0
  4. package/dist/internal.js.map +1 -0
  5. package/dist/internal.mjs +16 -0
  6. package/dist/internal.mjs.map +1 -0
  7. package/dist/next-server.js +14 -0
  8. package/dist/next-server.mjs +2 -0
  9. package/dist/next.js +119 -0
  10. package/dist/next.mjs +5 -0
  11. package/dist/react-router.js +68 -0
  12. package/dist/react-router.mjs +5 -0
  13. package/dist/react.js +26 -0
  14. package/dist/react.mjs +2 -0
  15. package/dist/translation-store-BpPWqZ0B.js +548 -0
  16. package/dist/translation-store-BpPWqZ0B.js.map +1 -0
  17. package/dist/translation-store-C42QdI0e.mjs +441 -0
  18. package/dist/translation-store-C42QdI0e.mjs.map +1 -0
  19. package/dist/types/WeaverseHydrogenRoot.d.ts +7 -7
  20. package/dist/types/WeaverseHydrogenRoot.d.ts.map +1 -1
  21. package/dist/types/components/index.d.ts +2 -2
  22. package/dist/types/components/main.d.ts +2 -2
  23. package/dist/types/hooks/translation-context.d.ts +2 -8
  24. package/dist/types/hooks/translation-context.d.ts.map +1 -1
  25. package/dist/types/index.d.ts +12 -33
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/internal.d.ts +24 -0
  28. package/dist/types/internal.d.ts.map +1 -0
  29. package/dist/types/next-server.d.ts +8 -0
  30. package/dist/types/next-server.d.ts.map +1 -0
  31. package/dist/types/next.d.ts +12 -0
  32. package/dist/types/next.d.ts.map +1 -0
  33. package/dist/types/react-router.d.ts +18 -0
  34. package/dist/types/react-router.d.ts.map +1 -0
  35. package/dist/types/react.d.ts +11 -0
  36. package/dist/types/react.d.ts.map +1 -0
  37. package/dist/types/seo.d.ts +4 -4
  38. package/dist/types/seo.d.ts.map +1 -1
  39. package/dist/types/types.d.ts +88 -68
  40. package/dist/types/types.d.ts.map +1 -1
  41. package/dist/types/utils/index.d.ts +4 -5
  42. package/dist/types/utils/index.d.ts.map +1 -1
  43. package/dist/types/utils/nested-key.d.ts +9 -0
  44. package/dist/types/utils/nested-key.d.ts.map +1 -0
  45. package/dist/types/utils/normalize-page-url.d.ts +1 -1
  46. package/dist/types/utils/product-options.d.ts +18 -0
  47. package/dist/types/utils/product-options.d.ts.map +1 -0
  48. package/dist/types/utils/use-studio.d.ts +1 -1
  49. package/dist/types/utils/use-studio.d.ts.map +1 -1
  50. package/dist/types/utils/use-theme-settings-store.d.ts +8 -8
  51. package/dist/types/utils/use-theme-settings-store.d.ts.map +1 -1
  52. package/dist/types/weaverse-client.d.ts +14 -16
  53. package/dist/types/weaverse-client.d.ts.map +1 -1
  54. package/dist/weaverse-client-Clu3u7md.mjs +1966 -0
  55. package/dist/weaverse-client-Clu3u7md.mjs.map +1 -0
  56. package/dist/weaverse-client-yC4TYuVV.js +2073 -0
  57. package/dist/weaverse-client-yC4TYuVV.js.map +1 -0
  58. package/package.json +43 -5
  59. package/readme.md +44 -24
  60. package/src/WeaverseHydrogenRoot.tsx +18 -15
  61. package/src/components/index.ts +2 -2
  62. package/src/components/main.tsx +2 -2
  63. package/src/hooks/translation-context.tsx +3 -28
  64. package/src/index.ts +42 -81
  65. package/src/internal.ts +64 -0
  66. package/src/next-server.ts +13 -0
  67. package/src/next.ts +35 -0
  68. package/src/react-router.ts +52 -0
  69. package/src/react.ts +16 -0
  70. package/src/seo.ts +5 -5
  71. package/src/types.ts +104 -74
  72. package/src/utils/index.ts +15 -14
  73. package/src/utils/nested-key.ts +28 -0
  74. package/src/utils/normalize-page-url.ts +1 -1
  75. package/src/utils/product-options.ts +28 -0
  76. package/src/utils/use-studio.ts +1 -1
  77. package/src/utils/use-theme-settings-store.ts +7 -7
  78. package/src/weaverse-client.ts +88 -96
  79. package/dist/index.js.map +0 -1
  80. package/dist/index.mjs.map +0 -1
@@ -1,25 +1,22 @@
1
- import {
2
- CacheCustom,
3
- createWithCache,
4
- type HydrogenEnv,
5
- type I18nBase,
6
- } from '@shopify/hydrogen'
1
+ import { Cache, createFetchWithCache } from '@shopify/hydrogen'
7
2
  import type {
8
3
  CachingStrategy,
9
4
  CustomPageEntry,
10
5
  FetchProjectPayload,
11
6
  FetchProjectRequestBody,
12
- HydrogenComponent,
13
- HydrogenComponentData,
14
- HydrogenPageData,
15
7
  LoadPageParams,
16
8
  PageType,
17
9
  ProjectIdValidationResult,
18
- ThemeSettingsResponse,
19
10
  WeaverseClientArgs,
11
+ WeaverseComponent,
12
+ WeaverseComponentData,
13
+ WeaverseEnv,
14
+ WeaverseI18n,
20
15
  WeaverseLoaderData,
16
+ WeaversePageData,
21
17
  WeaverseProjectConfigs,
22
18
  WeaverseStudioQueries,
19
+ WeaverseThemeSettingsResponse,
23
20
  WithCacheFetchResponse,
24
21
  } from './types'
25
22
  import {
@@ -42,14 +39,10 @@ import { normalizePageUrl } from './utils/normalize-page-url'
42
39
  * These values optimize CDN hit rates while maintaining fresh content.
43
40
  */
44
41
  const CACHE_DURATIONS = {
45
- /** Short browser freshness for content that can change in Builder */
42
+ /** Freshness for content that can change in Builder */
46
43
  SHORT: 60,
47
- /** Shared cache freshness for anonymous storefront traffic */
48
- SHARED_SHORT: 300,
49
44
  /** Long stale window so slow Builder responses do not block storefront rendering */
50
45
  LONG: 86_400,
51
- /** Extra freshness for sitemap-like index data */
52
- STATIC: 3600,
53
46
  } as const
54
47
 
55
48
  /**
@@ -89,6 +82,18 @@ function isRetryableFetchError(error: unknown): boolean {
89
82
  return error.code === 'FETCH_ERROR' && (error.statusCode ?? 0) >= 500
90
83
  }
91
84
 
85
+ /** Parses a JSON body, or returns the raw text when it is not JSON. */
86
+ function parseResponseText(text: string): unknown {
87
+ if (!text) {
88
+ return text
89
+ }
90
+ try {
91
+ return JSON.parse(text)
92
+ } catch {
93
+ return text
94
+ }
95
+ }
96
+
92
97
  function isPlainObject(value: unknown): value is Record<string, unknown> {
93
98
  return typeof value === 'object' && value !== null && !Array.isArray(value)
94
99
  }
@@ -207,15 +212,18 @@ function cachedTransportError(error: unknown, url: string): unknown {
207
212
  const API_PATH = 'v1' as const
208
213
 
209
214
  /**
210
- * Default cache strategy for Weaverse API requests.
211
- * Optimized for CDN performance with 23-hour stale content tolerance.
215
+ * Cache strategy for Weaverse API requests: fresh for a minute, then served
216
+ * stale for a day while it refreshes or while Builder errors.
217
+ *
218
+ * Every Builder resource uses it. Hydrogen 2026.4's subrequest cache only read
219
+ * `max-age` for freshness, so the old per-resource `sMaxAge` values never
220
+ * changed how long an entry stayed fresh.
212
221
  */
213
- const DEFAULT_CACHE_STRATEGY: CachingStrategy = {
222
+ const DEFAULT_CACHE_STRATEGY: CachingStrategy = Cache({
214
223
  maxAge: CACHE_DURATIONS.SHORT,
215
- sMaxAge: CACHE_DURATIONS.SHARED_SHORT,
216
224
  staleWhileRevalidate: CACHE_DURATIONS.LONG,
217
225
  staleIfError: CACHE_DURATIONS.LONG,
218
- } as const
226
+ })
219
227
 
220
228
  /** Builder API resource whose freshness policy should be used for a request. */
221
229
  export type BuilderApiCacheTarget =
@@ -226,9 +234,9 @@ export type BuilderApiCacheTarget =
226
234
 
227
235
  /** Fetch options accepted by {@link WeaverseClient.fetchWithCache}. */
228
236
  export interface WeaverseFetchWithCacheOptions extends RequestInit {
229
- /** Builder API resource whose default freshness policy should be used. */
237
+ /** Builder API resource the request reads; part of the cache key. */
230
238
  cacheTarget?: BuilderApiCacheTarget
231
- /** Explicit Hydrogen caching strategy overriding the resource default. */
239
+ /** Explicit Hydrogen caching strategy overriding the default. */
232
240
  strategy?: CachingStrategy
233
241
  }
234
242
 
@@ -240,18 +248,6 @@ export interface FetchCustomPagesOptions {
240
248
  locale?: string
241
249
  }
242
250
 
243
- const SMART_CACHE_STRATEGIES: Record<BuilderApiCacheTarget, CachingStrategy> = {
244
- project: DEFAULT_CACHE_STRATEGY,
245
- 'theme-settings': DEFAULT_CACHE_STRATEGY,
246
- 'merchant-overrides': DEFAULT_CACHE_STRATEGY,
247
- 'custom-pages': {
248
- maxAge: CACHE_DURATIONS.SHORT,
249
- sMaxAge: CACHE_DURATIONS.STATIC,
250
- staleWhileRevalidate: CACHE_DURATIONS.LONG,
251
- staleIfError: CACHE_DURATIONS.LONG,
252
- },
253
- } as const
254
-
255
251
  /**
256
252
  * Options objects created by the internal `project_configs` request whose
257
253
  * subrequest cache identity deliberately excludes the body. Theme settings
@@ -295,12 +291,12 @@ export class WeaverseClient {
295
291
  basePageRequestBody: Omit<FetchProjectRequestBody, 'url'>
296
292
  /** Resolved project and rendering-mode configuration. */
297
293
  configs: WeaverseProjectConfigs
298
- /** Hydrogen cache wrapper used for Builder API requests. */
299
- withCache: ReturnType<typeof createWithCache>
294
+ /** Hydrogen cached fetch used for Builder API requests. */
295
+ private cachedFetch: ReturnType<typeof createFetchWithCache>
300
296
 
301
297
  /** Incoming request associated with this client instance. */
302
298
  request: WeaverseClientArgs['request']
303
- /** Hydrogen customer-account client, when configured. */
299
+ /** Customer Account API client, when the storefront passes one. */
304
300
  customerAccount: WeaverseClientArgs['customerAccount']
305
301
  /** Hydrogen Storefront API client and locale context. */
306
302
  storefront: WeaverseClientArgs['storefront']
@@ -308,22 +304,22 @@ export class WeaverseClient {
308
304
  components: WeaverseClientArgs['components']
309
305
  /** Theme schema used for defaults and Studio editing. */
310
306
  themeSchema: WeaverseClientArgs['themeSchema']
311
- /** Hydrogen runtime environment variables. */
307
+ /** Runtime environment variables. */
312
308
  env: WeaverseClientArgs['env']
313
309
  /** Cache storage supplied by the Hydrogen runtime. */
314
310
  cache: WeaverseClientArgs['cache']
315
- /** Background execution hook supplied by the Oxygen runtime. */
311
+ /** Background execution hook supplied by the runtime. */
316
312
  waitUntil: WeaverseClientArgs['waitUntil']
317
313
 
318
314
  // Performance optimizations
319
315
  private readonly parsedUrl: URL
320
- private readonly componentsByType: Map<string, HydrogenComponent>
316
+ private readonly componentsByType: Map<string, WeaverseComponent>
321
317
 
322
318
  // Configuration
323
319
  private readonly fetchTimeoutMs: number
324
320
  private readonly throwPageLoadFailures: boolean
325
321
 
326
- /** Creates a request-scoped client from the Hydrogen app context. */
322
+ /** Creates a request-scoped client from the storefront's request context. */
327
323
  constructor(args: WeaverseClientArgs) {
328
324
  // Assign required dependencies
329
325
  this.request = args.request
@@ -359,10 +355,9 @@ export class WeaverseClient {
359
355
  )
360
356
  }
361
357
 
362
- this.withCache = createWithCache({
358
+ this.cachedFetch = createFetchWithCache({
363
359
  cache: args.cache,
364
- waitUntil: args.waitUntil || (() => Promise.resolve()),
365
- request: args.request,
360
+ waitUntil: args.waitUntil,
366
361
  })
367
362
  }
368
363
 
@@ -372,16 +367,13 @@ export class WeaverseClient {
372
367
  */
373
368
  private initializeConfigs(args: WeaverseClientArgs): void {
374
369
  // Get base configs once to avoid duplicate calls
375
- const baseConfigs = getWeaverseConfigs(
376
- args.request,
377
- args.env as HydrogenEnv
378
- )
370
+ const baseConfigs = getWeaverseConfigs(args.request, args.env)
379
371
 
380
372
  // Resolve projectId with multi-project support
381
373
  const resolvedProjectId = this.resolveProjectIdSync(
382
374
  args.projectId,
383
375
  args.request,
384
- args.env as HydrogenEnv,
376
+ args.env,
385
377
  baseConfigs
386
378
  )
387
379
 
@@ -429,8 +421,8 @@ export class WeaverseClient {
429
421
  * Significantly improves performance for large component lists.
430
422
  */
431
423
  private buildComponentLookupMap(
432
- components: HydrogenComponent[]
433
- ): Map<string, HydrogenComponent> {
424
+ components: WeaverseComponent[]
425
+ ): Map<string, WeaverseComponent> {
434
426
  return new Map(components.map((comp) => [comp.schema?.type, comp]))
435
427
  }
436
428
 
@@ -528,7 +520,7 @@ export class WeaverseClient {
528
520
  private resolveProjectIdSync(
529
521
  projectId: string | (() => string) | (() => Promise<string>) | undefined,
530
522
  request: Request,
531
- env: HydrogenEnv,
523
+ env: WeaverseEnv,
532
524
  baseConfigs?: WeaverseProjectConfigs
533
525
  ): string {
534
526
  return (
@@ -646,7 +638,7 @@ export class WeaverseClient {
646
638
  private resolveFromEnvironment(
647
639
  baseConfigs: WeaverseProjectConfigs | undefined,
648
640
  request: Request,
649
- env: HydrogenEnv
641
+ env: WeaverseEnv
650
642
  ): string | null {
651
643
  const configs = baseConfigs || getWeaverseConfigs(request, env)
652
644
 
@@ -659,7 +651,6 @@ export class WeaverseClient {
659
651
 
660
652
  /**
661
653
  * Helper method for direct fetching in design mode.
662
- * Returns same format as withCache.fetch for consistency.
663
654
  * Includes 10-second timeout to prevent hanging requests.
664
655
  */
665
656
  private readonly fetchOnce = async <T>(
@@ -771,7 +762,7 @@ export class WeaverseClient {
771
762
 
772
763
  /**
773
764
  * Fetch data with caching support for both design mode and production.
774
- * Uses direct fetch in design mode, Hydrogen's withCache in production.
765
+ * Uses direct fetch in design mode, Hydrogen's cached fetch in production.
775
766
  * Automatically includes projectId in cache key for multi-project isolation.
776
767
  *
777
768
  * @param url - The URL to fetch from
@@ -791,13 +782,8 @@ export class WeaverseClient {
791
782
  options: WeaverseFetchWithCacheOptions = {}
792
783
  ): Promise<T> => {
793
784
  const { cacheTarget, strategy: strategyOverride, ...fetchOptions } = options
794
- const strategy =
795
- strategyOverride ||
796
- CacheCustom(
797
- cacheTarget
798
- ? SMART_CACHE_STRATEGIES[cacheTarget]
799
- : DEFAULT_CACHE_STRATEGY
800
- )
785
+ const strategy = strategyOverride || DEFAULT_CACHE_STRATEGY
786
+ const { body } = fetchOptions
801
787
 
802
788
  // Update cache key to include method, body content, and projectId for multi-project isolation
803
789
  // Prefix for easier debugging in cache systems
@@ -812,7 +798,9 @@ export class WeaverseClient {
812
798
  options.method || 'GET',
813
799
  bodyFreeCacheRequests.has(options)
814
800
  ? BODY_FREE_CACHE_SENTINEL
815
- : fetchOptions.body,
801
+ : body == null
802
+ ? null
803
+ : String(body),
816
804
  // WeakSet-derived discriminator: this slot cannot be produced by any
817
805
  // caller-supplied shape, so no external call can reach the marked
818
806
  // identity — even one that happens to send the sentinel string as its
@@ -821,7 +809,6 @@ export class WeaverseClient {
821
809
  this.configs.projectId,
822
810
  cacheTarget || 'default',
823
811
  ]
824
- let result: WithCacheFetchResponse<T>
825
812
 
826
813
  // Bypass the shared Hydrogen subrequest cache for design/revision modes
827
814
  // and for the Cloudflare public API proxy. The proxy owns freshness with
@@ -835,41 +822,42 @@ export class WeaverseClient {
835
822
  this.configs.isRevisionPreview ||
836
823
  this.isExternalPublicApiUrl(url)
837
824
  ) {
838
- result = await this.directFetch<T>(url, fetchOptions)
839
- } else {
840
- // Use withCache.fetch for better integration with Hydrogen's caching system
841
- const cachedFetch = this.withCache.fetch(url, fetchOptions, {
842
- cacheKey,
843
- cacheStrategy: strategy,
844
- shouldCacheResponse: (response: any): boolean => {
825
+ const result = await this.directFetch<T>(url, fetchOptions)
826
+ return result.data
827
+ }
828
+
829
+ let response: Response
830
+ let text: string
831
+ try {
832
+ response = await this.cachedFetch(url, fetchOptions, {
833
+ key: cacheKey,
834
+ strategy,
835
+ shouldCacheResponse: async ({ text: readText }) => {
845
836
  // An aborted request never caches: Hydrogen turns a body read cut
846
- // short by the abort into an empty payload, not an error.
837
+ // short by the abort into an empty body, not an error.
847
838
  if (fetchOptions.signal?.aborted) {
848
839
  return false
849
840
  }
850
- if (validateBuilderPayload && !validateBuilderPayload(response)) {
841
+ const payload = parseResponseText(await readText())
842
+ if (validateBuilderPayload && !validateBuilderPayload(payload)) {
851
843
  return false
852
844
  }
853
845
  // Cache any non-error response (for both Weaverse API and third-party APIs)
854
- return (
855
- !hasError(response) && response !== null && response !== undefined
856
- )
846
+ return !hasError(payload) && payload !== null && payload !== ''
857
847
  },
858
- displayName: 'Weaverse API',
859
848
  })
849
+ text = await response.text()
850
+ } catch (error) {
860
851
  // Only this client's own Builder requests are normalized; a
861
852
  // third-party `fetchWithCache` caller keeps the platform error.
862
- result = (await (validateBuilderPayload
863
- ? cachedFetch.catch((error: unknown) => {
864
- throw cachedTransportError(error, url)
865
- })
866
- : cachedFetch)) as WithCacheFetchResponse<T>
867
-
868
- // Hydrogen resolves a non-2xx status with a `null` body. For this
869
- // client's own Builder requests that would read as "nothing published";
870
- // surface the status the direct path already throws instead.
871
- const { response } = result
872
- if (validateBuilderPayload && response && !response.ok) {
853
+ throw validateBuilderPayload ? cachedTransportError(error, url) : error
854
+ }
855
+
856
+ if (!response.ok) {
857
+ // For this client's own Builder requests a non-2xx status must not read
858
+ // as "nothing published"; surface the status the direct path already
859
+ // throws instead. Other callers keep the `null` body they always got.
860
+ if (validateBuilderPayload) {
873
861
  throw new WeaverseError(
874
862
  `HTTP ${response.status}: ${response.statusText}`,
875
863
  'FETCH_ERROR',
@@ -877,9 +865,10 @@ export class WeaverseClient {
877
865
  { url }
878
866
  )
879
867
  }
868
+ return null as T
880
869
  }
881
870
 
882
- return result.data
871
+ return parseResponseText(text) as T
883
872
  }
884
873
 
885
874
  /**
@@ -911,7 +900,7 @@ export class WeaverseClient {
911
900
  */
912
901
  loadThemeSettings = async (
913
902
  strategy?: CachingStrategy
914
- ): Promise<ThemeSettingsResponse> => {
903
+ ): Promise<WeaverseThemeSettingsResponse> => {
915
904
  const defaultThemeSettings = generateDataFromSchema(this.themeSchema)
916
905
  try {
917
906
  const { configs } = this
@@ -957,7 +946,10 @@ export class WeaverseClient {
957
946
  builderPayloadValidators.set(themeSettingsOptions, isThemeConfigsEnvelope)
958
947
 
959
948
  const [data, merchantOverrides] = await Promise.all([
960
- this.fetchWithCache<ThemeSettingsResponse>(url, themeSettingsOptions),
949
+ this.fetchWithCache<WeaverseThemeSettingsResponse>(
950
+ url,
951
+ themeSettingsOptions
952
+ ),
961
953
  this.fetchMerchantOverrides(strategy),
962
954
  ])
963
955
 
@@ -1108,9 +1100,9 @@ export class WeaverseClient {
1108
1100
  * Used internally when API returns no page or when page needs default content.
1109
1101
  *
1110
1102
  * @param message - Custom message to display in the fallback page
1111
- * @returns HydrogenPageData with single main component containing the message
1103
+ * @returns WeaversePageData with single main component containing the message
1112
1104
  */
1113
- generateFallbackPage = (message: string): HydrogenPageData => {
1105
+ generateFallbackPage = (message: string): WeaversePageData => {
1114
1106
  const rootId = crypto.randomUUID()
1115
1107
  return {
1116
1108
  id: `fallback_page_${Date.now()}`,
@@ -1259,7 +1251,7 @@ export class WeaverseClient {
1259
1251
  const body: {
1260
1252
  projectId: string
1261
1253
  url: string
1262
- i18n?: I18nBase
1254
+ i18n?: WeaverseI18n
1263
1255
  params?: {
1264
1256
  type?: PageType
1265
1257
  locale?: string
@@ -1320,7 +1312,7 @@ export class WeaverseClient {
1320
1312
 
1321
1313
  // Extract what we can; a legacy-mode malformed body may lack any field.
1322
1314
  const responseData = projectData as Partial<FetchProjectPayload> | null
1323
- let page: HydrogenPageData | undefined = responseData?.page ?? undefined
1315
+ let page: WeaversePageData | undefined = responseData?.page ?? undefined
1324
1316
  let project: WeaverseLoaderData['project'] | undefined =
1325
1317
  responseData?.project
1326
1318
  const pageAssignment: WeaverseLoaderData['pageAssignment'] | undefined =
@@ -1410,7 +1402,7 @@ export class WeaverseClient {
1410
1402
  }
1411
1403
 
1412
1404
  /** Executes the registered loader for one component and attaches its result. */
1413
- execComponentLoader = async (item: HydrogenComponentData) => {
1405
+ execComponentLoader = async (item: WeaverseComponentData) => {
1414
1406
  const { data = {}, type, id } = item
1415
1407
  const component = this.componentsByType.get(type)
1416
1408
  const loader = component?.loader