@weaverse/hydrogen 6.1.2 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/index.js +5 -6
  2. package/dist/index.mjs +1 -2
  3. package/dist/internal.js +18 -29
  4. package/dist/internal.mjs +3 -14
  5. package/dist/next-client-boundary.js +1088 -0
  6. package/dist/next-client-boundary.js.map +1 -0
  7. package/dist/next-client-boundary.mjs +1038 -0
  8. package/dist/next-client-boundary.mjs.map +1 -0
  9. package/dist/next-server.js +669 -13
  10. package/dist/next-server.js.map +1 -0
  11. package/dist/next-server.mjs +666 -2
  12. package/dist/next-server.mjs.map +1 -0
  13. package/dist/next.js +33 -41
  14. package/dist/next.mjs +5 -3
  15. package/dist/react-router.js +6 -7
  16. package/dist/react-router.mjs +2 -3
  17. package/dist/seo-B8u2r3rX.mjs +205 -0
  18. package/dist/seo-B8u2r3rX.mjs.map +1 -0
  19. package/dist/seo-D9A9uRQ4.js +264 -0
  20. package/dist/seo-D9A9uRQ4.js.map +1 -0
  21. package/dist/translate-Celryv8j.mjs +26 -0
  22. package/dist/translate-Celryv8j.mjs.map +1 -0
  23. package/dist/translate-Kt1BYL1n.js +37 -0
  24. package/dist/translate-Kt1BYL1n.js.map +1 -0
  25. package/dist/{nested-key-CR3-YMFi.js → translation-store-BmGxBALf.js} +46 -137
  26. package/dist/translation-store-BmGxBALf.js.map +1 -0
  27. package/dist/{nested-key-_k9aW_Wd.mjs → translation-store-JFs-4lvf.mjs} +35 -138
  28. package/dist/translation-store-JFs-4lvf.mjs.map +1 -0
  29. package/dist/types/hooks/translation-context.d.ts +2 -36
  30. package/dist/types/hooks/translation-context.d.ts.map +1 -1
  31. package/dist/types/next/client-boundary.d.ts +25 -0
  32. package/dist/types/next/client-boundary.d.ts.map +1 -0
  33. package/dist/types/next/client.d.ts +16 -0
  34. package/dist/types/next/client.d.ts.map +1 -0
  35. package/dist/types/next/hooks.d.ts +20 -0
  36. package/dist/types/next/hooks.d.ts.map +1 -0
  37. package/dist/types/next/index.d.ts +34 -0
  38. package/dist/types/next/index.d.ts.map +1 -0
  39. package/dist/types/next/item.d.ts +60 -0
  40. package/dist/types/next/item.d.ts.map +1 -0
  41. package/dist/types/next/loader.d.ts +25 -0
  42. package/dist/types/next/loader.d.ts.map +1 -0
  43. package/dist/types/next/main.d.ts +11 -0
  44. package/dist/types/next/main.d.ts.map +1 -0
  45. package/dist/types/next/provider.d.ts +85 -0
  46. package/dist/types/next/provider.d.ts.map +1 -0
  47. package/dist/types/next/registry.d.ts +15 -0
  48. package/dist/types/next/registry.d.ts.map +1 -0
  49. package/dist/types/next/renderer.d.ts +21 -0
  50. package/dist/types/next/renderer.d.ts.map +1 -0
  51. package/dist/types/next/request-info.d.ts +19 -0
  52. package/dist/types/next/request-info.d.ts.map +1 -0
  53. package/dist/types/next/revalidate-item.d.ts +82 -0
  54. package/dist/types/next/revalidate-item.d.ts.map +1 -0
  55. package/dist/types/next/root-provider.d.ts +65 -0
  56. package/dist/types/next/root-provider.d.ts.map +1 -0
  57. package/dist/types/next/runtime.d.ts +113 -0
  58. package/dist/types/next/runtime.d.ts.map +1 -0
  59. package/dist/types/next/seo.d.ts +55 -0
  60. package/dist/types/next/seo.d.ts.map +1 -0
  61. package/dist/types/next/server/configs.d.ts +27 -0
  62. package/dist/types/next/server/configs.d.ts.map +1 -0
  63. package/dist/types/next/server/normalize-page-url.d.ts +19 -0
  64. package/dist/types/next/server/normalize-page-url.d.ts.map +1 -0
  65. package/dist/types/next/server/revalidate-handler.d.ts +55 -0
  66. package/dist/types/next/server/revalidate-handler.d.ts.map +1 -0
  67. package/dist/types/next/server/server-client.d.ts +21 -0
  68. package/dist/types/next/server/server-client.d.ts.map +1 -0
  69. package/dist/types/next/server.d.ts +19 -0
  70. package/dist/types/next/server.d.ts.map +1 -0
  71. package/dist/types/next/studio-bridge.d.ts +16 -0
  72. package/dist/types/next/studio-bridge.d.ts.map +1 -0
  73. package/dist/types/next/studio-connect.d.ts +27 -0
  74. package/dist/types/next/studio-connect.d.ts.map +1 -0
  75. package/dist/types/next/studio-router.d.ts +50 -0
  76. package/dist/types/next/studio-router.d.ts.map +1 -0
  77. package/dist/types/next/studio-script-src.d.ts +12 -0
  78. package/dist/types/next/studio-script-src.d.ts.map +1 -0
  79. package/dist/types/next/theme-settings-store.d.ts +18 -0
  80. package/dist/types/next/theme-settings-store.d.ts.map +1 -0
  81. package/dist/types/next/translation-context.d.ts +109 -0
  82. package/dist/types/next/translation-context.d.ts.map +1 -0
  83. package/dist/types/next/types.d.ts +674 -0
  84. package/dist/types/next/types.d.ts.map +1 -0
  85. package/dist/types/next/use-weaverse-next-studio.d.ts +34 -0
  86. package/dist/types/next/use-weaverse-next-studio.d.ts.map +1 -0
  87. package/dist/types/next/utils.d.ts +21 -0
  88. package/dist/types/next/utils.d.ts.map +1 -0
  89. package/dist/types/next-server.d.ts +3 -1
  90. package/dist/types/next-server.d.ts.map +1 -1
  91. package/dist/types/next.d.ts +4 -3
  92. package/dist/types/next.d.ts.map +1 -1
  93. package/dist/types/utils/translate.d.ts +51 -0
  94. package/dist/types/utils/translate.d.ts.map +1 -0
  95. package/dist/{weaverse-client-C9IgIH7O.js → weaverse-client-BmceZc5q.js} +49 -771
  96. package/dist/weaverse-client-BmceZc5q.js.map +1 -0
  97. package/dist/{weaverse-client-D59mBW0j.mjs → weaverse-client-CXnM9ayH.mjs} +4 -720
  98. package/dist/weaverse-client-CXnM9ayH.mjs.map +1 -0
  99. package/package.json +13 -7
  100. package/src/hooks/translation-context.tsx +7 -84
  101. package/src/next/client-boundary.ts +51 -0
  102. package/src/next/client.ts +102 -0
  103. package/src/next/hooks.ts +52 -0
  104. package/src/next/index.ts +145 -0
  105. package/src/next/item.ts +167 -0
  106. package/src/next/loader.ts +135 -0
  107. package/src/next/main.tsx +45 -0
  108. package/src/next/provider.tsx +241 -0
  109. package/src/next/registry.ts +63 -0
  110. package/src/next/renderer.tsx +102 -0
  111. package/src/next/request-info.ts +124 -0
  112. package/src/next/revalidate-item.ts +211 -0
  113. package/src/next/root-provider.tsx +201 -0
  114. package/src/next/runtime.ts +559 -0
  115. package/src/next/seo.ts +157 -0
  116. package/src/next/server/configs.ts +178 -0
  117. package/src/next/server/normalize-page-url.ts +73 -0
  118. package/src/next/server/revalidate-handler.ts +357 -0
  119. package/src/next/server/server-client.ts +728 -0
  120. package/src/next/server.ts +67 -0
  121. package/src/next/studio-bridge.tsx +82 -0
  122. package/src/next/studio-connect.tsx +88 -0
  123. package/src/next/studio-router.ts +85 -0
  124. package/src/next/studio-script-src.ts +128 -0
  125. package/src/next/theme-settings-store.ts +47 -0
  126. package/src/next/translation-context.tsx +182 -0
  127. package/src/next/types.ts +764 -0
  128. package/src/next/use-weaverse-next-studio.tsx +106 -0
  129. package/src/next/utils.ts +48 -0
  130. package/src/next-server.ts +41 -1
  131. package/src/next.ts +51 -15
  132. package/src/utils/translate.ts +101 -0
  133. package/dist/internal.js.map +0 -1
  134. package/dist/internal.mjs.map +0 -1
  135. package/dist/nested-key-CR3-YMFi.js.map +0 -1
  136. package/dist/nested-key-_k9aW_Wd.mjs.map +0 -1
  137. package/dist/translation-store-4fAV771j.js +0 -69
  138. package/dist/translation-store-4fAV771j.js.map +0 -1
  139. package/dist/translation-store-BBrw0FOk.mjs +0 -58
  140. package/dist/translation-store-BBrw0FOk.mjs.map +0 -1
  141. package/dist/weaverse-client-C9IgIH7O.js.map +0 -1
  142. package/dist/weaverse-client-D59mBW0j.mjs.map +0 -1
@@ -0,0 +1,102 @@
1
+ import type { SchemaType } from '@weaverse/schema'
2
+ import { registerWeaverseNextComponents } from './registry'
3
+ import type {
4
+ WeaverseNextClient,
5
+ WeaverseNextClientConfig,
6
+ WeaverseNextCommerceContext,
7
+ WeaverseNextComponent,
8
+ WeaverseNextLoaderData,
9
+ WeaverseNextLoadPageInput,
10
+ WeaverseNextRequestContext,
11
+ WeaverseNextStorefront,
12
+ WeaverseNextThemeSchema,
13
+ } from './types'
14
+ import { generateDataFromSchema } from './utils'
15
+
16
+ /**
17
+ * Request-safe Weaverse client for Next.js. Holds the component registry, theme
18
+ * data, request/commerce context, and the currently loaded page payload.
19
+ *
20
+ * Unlike Hydrogen's `WeaverseClient`, it never receives a React Router
21
+ * `Request`/`LoaderFunctionArgs`. Network I/O is delegated to `fetchPage` /
22
+ * `fetchThemeSettings` injected through config, keeping v0 testable without
23
+ * real Weaverse API calls.
24
+ */
25
+ class NextClient implements WeaverseNextClient {
26
+ projectId: string
27
+ components: WeaverseNextComponent[]
28
+ themeSchema?: SchemaType | WeaverseNextThemeSchema
29
+ themeSettings: Record<string, unknown>
30
+ requestContext?: WeaverseNextRequestContext
31
+ commerce?: WeaverseNextCommerceContext
32
+ data: WeaverseNextLoaderData | null = null
33
+ dataContext: Record<string, unknown> = {}
34
+
35
+ private readonly _fetchPage?: WeaverseNextClientConfig['fetchPage']
36
+ private readonly _fetchThemeSettings?: WeaverseNextClientConfig['fetchThemeSettings']
37
+
38
+ constructor(config: WeaverseNextClientConfig) {
39
+ this.projectId = config.projectId
40
+ this.components = config.components
41
+ registerWeaverseNextComponents(this.components)
42
+ this.themeSchema = config.themeSchema
43
+ // Seed `themeSchema` defaults before merchant-provided overrides, mirroring
44
+ // Hydrogen's `loadThemeSettings` (schema defaults under remote `theme`).
45
+ // Without this, `useThemeSettings()` would miss any setting the merchant
46
+ // never explicitly overrode.
47
+ this.themeSettings = {
48
+ ...generateDataFromSchema(config.themeSchema),
49
+ ...config.themeSettings,
50
+ }
51
+ this.requestContext = config.requestContext
52
+ this.commerce = config.commerce
53
+ this._fetchPage = config.fetchPage
54
+ this._fetchThemeSettings = config.fetchThemeSettings
55
+ }
56
+
57
+ /** Compatibility alias so loaders can call `weaverse.storefront.query(...)`. */
58
+ get storefront(): WeaverseNextStorefront | undefined {
59
+ return this.commerce?.storefront
60
+ }
61
+
62
+ loadPage = (
63
+ input: WeaverseNextLoadPageInput = {}
64
+ ): Promise<WeaverseNextLoaderData | null> => {
65
+ if (!this._fetchPage) {
66
+ throw new Error(
67
+ '[WeaverseNextClient] loadPage requires a `fetchPage` function in client config.'
68
+ )
69
+ }
70
+ return this._fetchPage(input)
71
+ }
72
+
73
+ loadThemeSettings = (
74
+ context?: WeaverseNextRequestContext
75
+ ): Promise<unknown> => {
76
+ if (!this._fetchThemeSettings) {
77
+ throw new Error(
78
+ '[WeaverseNextClient] loadThemeSettings requires a `fetchThemeSettings` function in client config.'
79
+ )
80
+ }
81
+ return this._fetchThemeSettings(context ?? this.requestContext)
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Create a {@link WeaverseNextClient} from explicit config.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * let client = createWeaverseNextClient({
91
+ * projectId: process.env.WEAVERSE_PROJECT_ID!,
92
+ * components,
93
+ * commerce: { storefront },
94
+ * fetchPage: async ({ type, handle }) => fetchWeaversePage({ type, handle }),
95
+ * })
96
+ * ```
97
+ */
98
+ export function createWeaverseNextClient(
99
+ config: WeaverseNextClientConfig
100
+ ): WeaverseNextClient {
101
+ return new NextClient(config)
102
+ }
@@ -0,0 +1,52 @@
1
+ 'use client'
2
+
3
+ import { useContext, useSyncExternalStore } from 'react'
4
+ import { WeaverseNextContext } from './provider'
5
+ import type { WeaverseNextCommerceContext } from './types'
6
+
7
+ const OUTSIDE_PROVIDER_ERROR = 'must be used inside a <WeaverseNextProvider>.'
8
+
9
+ function useWeaverseNextContext(hookName: string) {
10
+ let context = useContext(WeaverseNextContext)
11
+ if (!context) {
12
+ throw new Error(
13
+ `[@weaverse/hydrogen/next] ${hookName} ${OUTSIDE_PROVIDER_ERROR}`
14
+ )
15
+ }
16
+ return context
17
+ }
18
+
19
+ /**
20
+ * Read explicit root/global data passed to the provider. Next-native
21
+ * replacement for `useRouteLoaderData('root')`.
22
+ */
23
+ export function useWeaverseRootData<T = unknown>(): T {
24
+ return useWeaverseNextContext('useWeaverseRootData').rootData as T
25
+ }
26
+
27
+ /**
28
+ * Read explicit page/route commerce data passed to the provider. Next-native
29
+ * replacement for section-level `useLoaderData()`.
30
+ */
31
+ export function useWeaversePageData<T = unknown>(): T {
32
+ return useWeaverseNextContext('useWeaversePageData').pageData as T
33
+ }
34
+
35
+ /** Read the app-provided commerce context (storefront/cart/customer). */
36
+ export function useWeaverseCommerce<T = WeaverseNextCommerceContext>(): T {
37
+ return useWeaverseNextContext('useWeaverseCommerce').commerce as T
38
+ }
39
+
40
+ /**
41
+ * Read live Weaverse theme settings. The Studio bridge updates the provider's
42
+ * external store via `updateThemeSettings`, so components re-render during live
43
+ * design-mode edits.
44
+ */
45
+ export function useThemeSettings<T = Record<string, unknown>>(): T {
46
+ let { themeSettingsStore } = useWeaverseNextContext('useThemeSettings')
47
+ return useSyncExternalStore(
48
+ themeSettingsStore.subscribe,
49
+ themeSettingsStore.getSnapshot,
50
+ themeSettingsStore.getServerSnapshot
51
+ ) as T
52
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Next.js App Router rendering, provider, Studio, translation, and schema APIs.
3
+ *
4
+ * When migrating from `@weaverse/hydrogen`, replace route-loader context with
5
+ * {@link WeaverseNextProvider} props and render page data with
6
+ * {@link WeaverseNextRenderer}. Import server-only fetching and route-handler
7
+ * helpers from `@weaverse/hydrogen/next/server`.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+
12
+ export type { StudioSelection } from '@weaverse/react'
13
+ // Re-export schema authoring for migration ergonomics.
14
+ export * from '@weaverse/schema'
15
+ export {
16
+ type CreateTranslateSources,
17
+ createTranslate,
18
+ getNestedKey,
19
+ interpolate,
20
+ type TranslateFunction,
21
+ } from '../utils/translate'
22
+ export { ThemeTextStore, TranslationStore } from '../utils/translation-store'
23
+ // Client code (components, hooks, runtime, and the `@weaverse/react`
24
+ // re-exports) comes by name from `./client-boundary`, which the build emits as
25
+ // its own `'use client'` file. Every other value export below is server-safe,
26
+ // so a Server Component can import this entry.
27
+ export {
28
+ bindWeaverseNextStudioRuntime,
29
+ createWeaverseNextClient,
30
+ createWeaverseNextRuntime,
31
+ loadWeaverseNextStudioScript,
32
+ ThemeTextProvider,
33
+ TranslationProvider,
34
+ useChildInstances,
35
+ useItemInstance,
36
+ useParentInstance,
37
+ useStudioSelection,
38
+ useThemeSettings,
39
+ useThemeText,
40
+ useTranslation,
41
+ useWeaverse,
42
+ useWeaverseCommerce,
43
+ useWeaverseNextStudioInternals,
44
+ useWeaversePageData,
45
+ useWeaverseRootData,
46
+ WeaverseNextProvider,
47
+ WeaverseNextRenderer,
48
+ WeaverseNextRootProvider,
49
+ WeaverseNextRuntime,
50
+ WeaverseNextStudio,
51
+ WeaverseNextStudioBridge,
52
+ WeaverseNextStudioConnect,
53
+ } from './client-boundary'
54
+ export type { WeaverseNextItem } from './item'
55
+ export {
56
+ type RunComponentLoadersArgs,
57
+ runWeaverseComponentLoaders,
58
+ } from './loader'
59
+ export type { WeaverseNextProviderProps } from './provider'
60
+ export type { WeaverseNextRendererProps } from './renderer'
61
+ export { buildWeaverseNextRequestInfo } from './request-info'
62
+ export {
63
+ buildWeaverseNextRevalidateRouteContext,
64
+ DEFAULT_REVALIDATE_ENDPOINT,
65
+ type RevalidateItemRuntimeLike,
66
+ revalidateWeaverseNextItem,
67
+ type WeaverseNextRevalidateRouteContext,
68
+ } from './revalidate-item'
69
+ export type {
70
+ WeaverseNextRootContextValue,
71
+ WeaverseNextRootProviderProps,
72
+ } from './root-provider'
73
+ export type { WeaverseNextRenderablePage } from './runtime'
74
+ export {
75
+ formatWeaverseNextSeoMetadata,
76
+ getWeaverseNextSeoMetadata,
77
+ type NextOpenGraph,
78
+ type NextTwitter,
79
+ type WeaverseNextSeoInput,
80
+ type WeaverseNextSeoMetadata,
81
+ type WeaverseNextSeoPageContainer,
82
+ } from './seo'
83
+ export type { WeaverseNextStudioBridgeProps } from './studio-bridge'
84
+ export type {
85
+ LoadWeaverseNextStudioScriptOptions,
86
+ WeaverseNextStudioConnectProps,
87
+ } from './studio-connect'
88
+ export {
89
+ type CreateWeaverseNextStudioInternalsOptions,
90
+ createWeaverseNextStudioInternals,
91
+ type WeaverseNextRouterLike,
92
+ type WeaverseNextStudioInternals,
93
+ } from './studio-router'
94
+ export {
95
+ type ResolveWeaverseNextStudioScriptSrcOptions,
96
+ resolveWeaverseNextStudioScriptSrc,
97
+ } from './studio-script-src'
98
+ export {
99
+ type CreateWeaverseNextThemeSettingsStoreOptions,
100
+ createWeaverseNextThemeSettingsStore,
101
+ } from './theme-settings-store'
102
+ export type {
103
+ ThemeTextProviderProps,
104
+ ThemeTextValue,
105
+ TranslationProviderProps,
106
+ TranslationValue,
107
+ } from './translation-context'
108
+ export type {
109
+ WeaverseArticle,
110
+ WeaverseBlog,
111
+ WeaverseCollection,
112
+ WeaverseMetaObject,
113
+ WeaverseNextCacheConfig,
114
+ WeaverseNextClient,
115
+ WeaverseNextClientConfig,
116
+ WeaverseNextCommerceContext,
117
+ WeaverseNextComponent,
118
+ WeaverseNextComponentData,
119
+ WeaverseNextComponentLoaderArgs,
120
+ WeaverseNextComponentProps,
121
+ WeaverseNextCustomPageEntry,
122
+ WeaverseNextFetchCustomPagesOptions,
123
+ WeaverseNextI18n,
124
+ WeaverseNextLoaderData,
125
+ WeaverseNextLoadPageInput,
126
+ WeaverseNextPageAssignment,
127
+ WeaverseNextPageData,
128
+ WeaverseNextRequestContext,
129
+ WeaverseNextRequestInfo,
130
+ WeaverseNextRuntimeConfig,
131
+ WeaverseNextRuntimeInternal,
132
+ WeaverseNextStorefront,
133
+ WeaverseNextThemeSchema,
134
+ WeaverseNextThemeSchemaGroup,
135
+ WeaverseNextThemeSchemaInput,
136
+ WeaverseNextThemeSettingsStore,
137
+ WeaverseNextTranslationChanges,
138
+ WeaverseNextTranslationEntry,
139
+ WeaverseNextTranslationItemEntry,
140
+ WeaverseNextTranslationMap,
141
+ WeaverseNextTranslationMapEntry,
142
+ WeaverseProduct,
143
+ } from './types'
144
+ export type { WeaverseNextStudioProps } from './use-weaverse-next-studio'
145
+ export { generateDataFromSchema, isBrowser, isIframe } from './utils'
@@ -0,0 +1,167 @@
1
+ import type { ElementData } from '@weaverse/react'
2
+ import { Weaverse, WeaverseItemStore } from '@weaverse/react'
3
+ import type { WeaverseNextRuntime } from './runtime'
4
+ import type {
5
+ WeaverseNextComponentData,
6
+ WeaverseNextTranslationItemEntry,
7
+ } from './types'
8
+ import { generateDataFromSchema } from './utils'
9
+
10
+ /**
11
+ * Spread a serialized item's nested `data` field into top-level props — the
12
+ * shape the shared `@weaverse/react` renderer expects. Top-level fields win
13
+ * over nested ones, matching the constructor's original
14
+ * `Object.assign(store, schemaDefaults, data, rest)` merge order. Flat payloads
15
+ * with no nested `data` pass through unchanged.
16
+ */
17
+ function flattenItemData(update: Omit<ElementData, 'id' | 'type'>) {
18
+ let { data, ...rest } = update
19
+ if (!(data && typeof data === 'object' && !Array.isArray(data))) {
20
+ return rest
21
+ }
22
+ return { ...data, ...rest }
23
+ }
24
+
25
+ /**
26
+ * While set, item `setData()` queues its subscriber notification here instead
27
+ * of emitting synchronously. `createWeaverseNextRuntime()` runs during React
28
+ * render (the renderer's `useMemo`), and constructing a runtime makes core's
29
+ * `initProject()` call `setData()` on reused item instances (locale/client
30
+ * navigation) — emitting to `useSyncExternalStore` subscribers mid-render
31
+ * trips React's setState-in-render warning. Data is still applied
32
+ * synchronously; only the emit is deferred.
33
+ */
34
+ let deferredItemUpdates: Set<WeaverseNextItem> | null = null
35
+
36
+ /**
37
+ * Run `create` with item update emissions deferred, returning its result and
38
+ * the items whose `setData()` fired meanwhile. The caller is responsible for
39
+ * calling `triggerUpdate()` on them once React has committed.
40
+ */
41
+ export function collectDeferredItemUpdates<T>(
42
+ create: () => T
43
+ ): [T, WeaverseNextItem[]] {
44
+ let previousQueue = deferredItemUpdates
45
+ let queue = new Set<WeaverseNextItem>()
46
+ deferredItemUpdates = queue
47
+ try {
48
+ return [create(), [...queue]]
49
+ } finally {
50
+ deferredItemUpdates = previousQueue
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Item store that flattens the serialized item's nested `data` field onto the
56
+ * store and seeds schema defaults — same shape Hydrogen produces, so the
57
+ * shared `@weaverse/react` renderer can spread props at the top level.
58
+ */
59
+ export class WeaverseNextItem extends WeaverseItemStore {
60
+ /** Browser runtime that owns this item store. */
61
+ declare weaverse: WeaverseNextRuntime
62
+
63
+ /**
64
+ * Cached merged snapshot (base `_store` + translations). Reset whenever the
65
+ * `_store` ref or the item's translation entry ref changes, so
66
+ * `useSyncExternalStore` sees a stable object between unrelated re-renders.
67
+ */
68
+ private _cachedSnapshot: ElementData | null = null
69
+ private _snapshotStoreRef: ElementData | null = null
70
+ private _snapshotTranslationRef: WeaverseNextTranslationItemEntry | null =
71
+ null
72
+
73
+ /** Create an item store from serialized component data and its runtime. */
74
+ constructor(initialData: WeaverseNextComponentData, weaverse: Weaverse) {
75
+ super(initialData as ElementData, weaverse)
76
+ let schemaData = generateDataFromSchema(this.Element?.schema)
77
+ Object.assign(this._store, schemaData, flattenItemData(initialData))
78
+ }
79
+
80
+ /**
81
+ * Flatten nested `data` on updates exactly like the constructor does. Core's
82
+ * `initProject()` calls `setData(item)` when a new runtime serves a page
83
+ * whose item instances already exist (e.g. locale/client navigation).
84
+ *
85
+ * A complete serialized item (it carries `id` and `type`; core's
86
+ * `initProject()` always passes one) replaces the store the way the
87
+ * constructor builds it: the schema defaults of the item's own `type` — the
88
+ * Builder can replace a section in place, keeping its id — then the item's
89
+ * settings. A payload omits a setting — or the whole optional `data` — when
90
+ * its value equals the schema default, so merging into the previous store
91
+ * would keep the previous locale's value visible. Partial updates still
92
+ * merge. Either way the `_store` ref changes, which invalidates the memoized
93
+ * snapshot below. The subscriber emit is queued instead of fired when a
94
+ * render-phase deferral is active (see `collectDeferredItemUpdates`).
95
+ */
96
+ setData = (update: Omit<ElementData, 'id' | 'type'>) => {
97
+ if ('id' in update && 'type' in update) {
98
+ let { data, type } = update as ElementData
99
+ let schema = this.weaverse.elementRegistry.get(type)?.schema
100
+ this._store = {
101
+ ...generateDataFromSchema(schema),
102
+ ...(data === undefined ? {} : { data }),
103
+ ...flattenItemData(update),
104
+ } as ElementData
105
+ } else {
106
+ this.data = flattenItemData(update)
107
+ }
108
+ if (deferredItemUpdates) {
109
+ deferredItemUpdates.add(this)
110
+ } else {
111
+ this.triggerUpdate()
112
+ }
113
+ return this.data
114
+ }
115
+
116
+ /**
117
+ * Overlay item-level translations onto the base store when the runtime is in
118
+ * translation mode. Mirrors Hydrogen's `WeaverseHydrogenItem.getSnapShot()`:
119
+ * memoized by `_store` ref + translation entry ref so the returned object is
120
+ * referentially stable, and a fast path that returns the base store untouched
121
+ * when the item has no translations.
122
+ */
123
+ getSnapShot = (): ElementData => {
124
+ let base = this._store
125
+ let translations = this.weaverse.translationMap[this._id]
126
+
127
+ // No translations → return the base store directly (fast path).
128
+ if (!translations) {
129
+ return base
130
+ }
131
+
132
+ // Reuse the cached merge while both the store and translation refs hold.
133
+ if (
134
+ this._cachedSnapshot &&
135
+ this._snapshotStoreRef === base &&
136
+ this._snapshotTranslationRef === translations
137
+ ) {
138
+ return this._cachedSnapshot
139
+ }
140
+
141
+ let merged = { ...base }
142
+ for (let [key, entry] of Object.entries(translations)) {
143
+ if (entry.translatedValue !== undefined) {
144
+ merged[key] = entry.translatedValue
145
+ }
146
+ }
147
+
148
+ this._cachedSnapshot = merged
149
+ this._snapshotStoreRef = base
150
+ this._snapshotTranslationRef = translations
151
+ return merged
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Register the Next item constructor globally. Idempotent: calling it more than
157
+ * once is safe. Invoked by the renderer before it builds a Weaverse instance.
158
+ */
159
+ export function ensureNextItemConstructor() {
160
+ if (
161
+ !Weaverse.ItemConstructor ||
162
+ Weaverse.ItemConstructor === WeaverseItemStore ||
163
+ Weaverse.ItemConstructor === WeaverseNextItem
164
+ ) {
165
+ Weaverse.ItemConstructor = WeaverseNextItem
166
+ }
167
+ }
@@ -0,0 +1,135 @@
1
+ import type {
2
+ WeaverseNextClient,
3
+ WeaverseNextCommerceContext,
4
+ WeaverseNextComponent,
5
+ WeaverseNextComponentData,
6
+ WeaverseNextLoaderData,
7
+ WeaverseNextRequestContext,
8
+ } from './types'
9
+ import { generateDataFromSchema } from './utils'
10
+
11
+ /** Inputs for running registered component loaders over a page tree. */
12
+ export interface RunComponentLoadersArgs {
13
+ /** Weaverse client containing the component registry. */
14
+ client: WeaverseNextClient
15
+ /** Request-scoped commerce override; defaults to `client.commerce`. */
16
+ commerce?: WeaverseNextCommerceContext
17
+ /** Request-context override; defaults to `client.requestContext`. */
18
+ context?: WeaverseNextRequestContext
19
+ /** Page payload to walk; defaults to `client.data`. */
20
+ data?: WeaverseNextLoaderData | null
21
+ }
22
+
23
+ function isItemNode(node: unknown): node is WeaverseNextComponentData {
24
+ return (
25
+ typeof node === 'object' &&
26
+ node !== null &&
27
+ typeof (node as WeaverseNextComponentData).type === 'string'
28
+ )
29
+ }
30
+
31
+ function cloneItem(item: WeaverseNextComponentData): WeaverseNextComponentData {
32
+ return {
33
+ ...item,
34
+ children: Array.isArray(item.children)
35
+ ? item.children.map((child) =>
36
+ isItemNode(child) ? cloneItem(child) : { ...child }
37
+ )
38
+ : item.children,
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Walk a serialized page tree and run each registered component's `loader`,
44
+ * attaching the result to the item as `loaderData`.
45
+ *
46
+ * Loaders receive explicit `commerce.storefront` (and `weaverse.storefront` as
47
+ * a compatibility alias via the client). Children are walked recursively when
48
+ * they are inline item objects; flat `items` arrays are covered by the
49
+ * top-level walk.
50
+ *
51
+ * @returns a cloned `data` object with `loaderData` attached to cloned items.
52
+ */
53
+ export async function runWeaverseComponentLoaders(
54
+ args: RunComponentLoadersArgs
55
+ ): Promise<WeaverseNextLoaderData | null> {
56
+ let { client, context, commerce } = args
57
+ let sourceData = args.data ?? client.data
58
+ if (!sourceData) {
59
+ return null
60
+ }
61
+ if (!sourceData.page.items.length) {
62
+ return sourceData
63
+ }
64
+
65
+ let data: WeaverseNextLoaderData = {
66
+ ...sourceData,
67
+ page: {
68
+ ...sourceData.page,
69
+ items: sourceData.page.items.map(cloneItem),
70
+ },
71
+ }
72
+ let items = data.page.items
73
+
74
+ let componentMap = new Map<string, WeaverseNextComponent>()
75
+ for (let component of client.components) {
76
+ if (component?.schema?.type) {
77
+ componentMap.set(component.schema.type, component)
78
+ }
79
+ }
80
+
81
+ let visited = new Set<string>()
82
+ let resolvedContext = context ?? client.requestContext
83
+ let resolvedCommerce = commerce ?? client.commerce
84
+
85
+ // Per-call `weaverse` alias. When a request-scoped `commerce` is passed, the
86
+ // `weaverse.storefront` compatibility alias must resolve to *that* storefront,
87
+ // not the client's construction-time commerce. Pilot-style loaders call
88
+ // `weaverse.storefront.query(...)`, so a stale alias would query the wrong
89
+ // (or an undefined) storefront. When no override is given we reuse the client
90
+ // as-is to preserve its prototype getters.
91
+ let resolvedWeaverse: WeaverseNextClient =
92
+ resolvedCommerce === client.commerce
93
+ ? client
94
+ : {
95
+ ...client,
96
+ commerce: resolvedCommerce,
97
+ storefront: resolvedCommerce?.storefront,
98
+ }
99
+
100
+ async function walk(nodes: WeaverseNextComponentData[]) {
101
+ for (let item of nodes) {
102
+ if (!item || visited.has(item.id)) {
103
+ continue
104
+ }
105
+ visited.add(item.id)
106
+
107
+ let component = componentMap.get(item.type)
108
+ if (component?.loader) {
109
+ try {
110
+ item.loaderData = await component.loader({
111
+ data: { ...generateDataFromSchema(component.schema), ...item.data },
112
+ weaverse: resolvedWeaverse,
113
+ context: resolvedContext,
114
+ commerce: resolvedCommerce,
115
+ })
116
+ } catch (error) {
117
+ console.warn('❌ Item loader run failed.', item.type, item.id, error)
118
+ }
119
+ }
120
+
121
+ let children = item.children
122
+ if (Array.isArray(children)) {
123
+ let inlineChildren = children.filter(
124
+ isItemNode
125
+ ) as WeaverseNextComponentData[]
126
+ if (inlineChildren.length) {
127
+ await walk(inlineChildren)
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ await walk(items)
134
+ return data
135
+ }
@@ -0,0 +1,45 @@
1
+ import { createSchema, type SchemaType } from '@weaverse/schema'
2
+ import type { HTMLAttributes, RefObject } from 'react'
3
+ import type { WeaverseNextComponent, WeaverseNextComponentProps } from './types'
4
+
5
+ /**
6
+ * Default `main` root component.
7
+ *
8
+ * A Weaverse page tree is a flat `items` array with a single root item of type
9
+ * `main` whose `children` are the page's top-level sections. Hydrogen ships its
10
+ * own `main`; the Next adapter provides this minimal, framework-neutral
11
+ * equivalent so consumers don't have to register one just to render a basic
12
+ * page tree (omitting it would leave child sections unrendered).
13
+ *
14
+ * It renders `children` and forwards the Weaverse DOM attributes the shared
15
+ * renderer injects (`data-wv-id`, `data-wv-type`, `ref`, `className`, ...rest)
16
+ * onto the root element. `loaderData` is adapter-internal and stripped so it
17
+ * never lands on the DOM node.
18
+ */
19
+ function Main(
20
+ props: WeaverseNextComponentProps & {
21
+ ref?: RefObject<HTMLDivElement | null>
22
+ }
23
+ ) {
24
+ let { children, loaderData, ...rest } = props
25
+ // `rest` carries the Weaverse-injected DOM attributes (data-wv-*, ref,
26
+ // className). They are all valid div attributes, so the cast is safe.
27
+ return <div {...(rest as HTMLAttributes<HTMLDivElement>)}>{children}</div>
28
+ }
29
+ Main.displayName = 'main'
30
+
31
+ /** Schema for the default {@link Main} root component. Type is exactly `main`. */
32
+ export const mainSchema: SchemaType = createSchema({
33
+ type: 'main',
34
+ title: 'Main',
35
+ })
36
+
37
+ /**
38
+ * Default `main` component module, registered ahead of user components by
39
+ * {@link registerWeaverseNextComponents}. Kept package-internal — consumers
40
+ * override it simply by registering their own component with `type: 'main'`.
41
+ */
42
+ export const defaultMainComponent: WeaverseNextComponent = {
43
+ default: Main,
44
+ schema: mainSchema,
45
+ }