@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.
- package/dist/index.js +5 -6
- package/dist/index.mjs +1 -2
- package/dist/internal.js +18 -29
- package/dist/internal.mjs +3 -14
- package/dist/next-client-boundary.js +1088 -0
- package/dist/next-client-boundary.js.map +1 -0
- package/dist/next-client-boundary.mjs +1038 -0
- package/dist/next-client-boundary.mjs.map +1 -0
- package/dist/next-server.js +669 -13
- package/dist/next-server.js.map +1 -0
- package/dist/next-server.mjs +666 -2
- package/dist/next-server.mjs.map +1 -0
- package/dist/next.js +33 -41
- package/dist/next.mjs +5 -3
- package/dist/react-router.js +6 -7
- package/dist/react-router.mjs +2 -3
- package/dist/seo-B8u2r3rX.mjs +205 -0
- package/dist/seo-B8u2r3rX.mjs.map +1 -0
- package/dist/seo-D9A9uRQ4.js +264 -0
- package/dist/seo-D9A9uRQ4.js.map +1 -0
- package/dist/translate-Celryv8j.mjs +26 -0
- package/dist/translate-Celryv8j.mjs.map +1 -0
- package/dist/translate-Kt1BYL1n.js +37 -0
- package/dist/translate-Kt1BYL1n.js.map +1 -0
- package/dist/{nested-key-CR3-YMFi.js → translation-store-BmGxBALf.js} +46 -137
- package/dist/translation-store-BmGxBALf.js.map +1 -0
- package/dist/{nested-key-_k9aW_Wd.mjs → translation-store-JFs-4lvf.mjs} +35 -138
- package/dist/translation-store-JFs-4lvf.mjs.map +1 -0
- package/dist/types/hooks/translation-context.d.ts +2 -36
- package/dist/types/hooks/translation-context.d.ts.map +1 -1
- package/dist/types/next/client-boundary.d.ts +25 -0
- package/dist/types/next/client-boundary.d.ts.map +1 -0
- package/dist/types/next/client.d.ts +16 -0
- package/dist/types/next/client.d.ts.map +1 -0
- package/dist/types/next/hooks.d.ts +20 -0
- package/dist/types/next/hooks.d.ts.map +1 -0
- package/dist/types/next/index.d.ts +34 -0
- package/dist/types/next/index.d.ts.map +1 -0
- package/dist/types/next/item.d.ts +60 -0
- package/dist/types/next/item.d.ts.map +1 -0
- package/dist/types/next/loader.d.ts +25 -0
- package/dist/types/next/loader.d.ts.map +1 -0
- package/dist/types/next/main.d.ts +11 -0
- package/dist/types/next/main.d.ts.map +1 -0
- package/dist/types/next/provider.d.ts +85 -0
- package/dist/types/next/provider.d.ts.map +1 -0
- package/dist/types/next/registry.d.ts +15 -0
- package/dist/types/next/registry.d.ts.map +1 -0
- package/dist/types/next/renderer.d.ts +21 -0
- package/dist/types/next/renderer.d.ts.map +1 -0
- package/dist/types/next/request-info.d.ts +19 -0
- package/dist/types/next/request-info.d.ts.map +1 -0
- package/dist/types/next/revalidate-item.d.ts +82 -0
- package/dist/types/next/revalidate-item.d.ts.map +1 -0
- package/dist/types/next/root-provider.d.ts +65 -0
- package/dist/types/next/root-provider.d.ts.map +1 -0
- package/dist/types/next/runtime.d.ts +113 -0
- package/dist/types/next/runtime.d.ts.map +1 -0
- package/dist/types/next/seo.d.ts +55 -0
- package/dist/types/next/seo.d.ts.map +1 -0
- package/dist/types/next/server/configs.d.ts +27 -0
- package/dist/types/next/server/configs.d.ts.map +1 -0
- package/dist/types/next/server/normalize-page-url.d.ts +19 -0
- package/dist/types/next/server/normalize-page-url.d.ts.map +1 -0
- package/dist/types/next/server/revalidate-handler.d.ts +55 -0
- package/dist/types/next/server/revalidate-handler.d.ts.map +1 -0
- package/dist/types/next/server/server-client.d.ts +21 -0
- package/dist/types/next/server/server-client.d.ts.map +1 -0
- package/dist/types/next/server.d.ts +19 -0
- package/dist/types/next/server.d.ts.map +1 -0
- package/dist/types/next/studio-bridge.d.ts +16 -0
- package/dist/types/next/studio-bridge.d.ts.map +1 -0
- package/dist/types/next/studio-connect.d.ts +27 -0
- package/dist/types/next/studio-connect.d.ts.map +1 -0
- package/dist/types/next/studio-router.d.ts +50 -0
- package/dist/types/next/studio-router.d.ts.map +1 -0
- package/dist/types/next/studio-script-src.d.ts +12 -0
- package/dist/types/next/studio-script-src.d.ts.map +1 -0
- package/dist/types/next/theme-settings-store.d.ts +18 -0
- package/dist/types/next/theme-settings-store.d.ts.map +1 -0
- package/dist/types/next/translation-context.d.ts +109 -0
- package/dist/types/next/translation-context.d.ts.map +1 -0
- package/dist/types/next/types.d.ts +674 -0
- package/dist/types/next/types.d.ts.map +1 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts +34 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts.map +1 -0
- package/dist/types/next/utils.d.ts +21 -0
- package/dist/types/next/utils.d.ts.map +1 -0
- package/dist/types/next-server.d.ts +3 -1
- package/dist/types/next-server.d.ts.map +1 -1
- package/dist/types/next.d.ts +4 -3
- package/dist/types/next.d.ts.map +1 -1
- package/dist/types/utils/translate.d.ts +51 -0
- package/dist/types/utils/translate.d.ts.map +1 -0
- package/dist/{weaverse-client-C9IgIH7O.js → weaverse-client-BmceZc5q.js} +49 -771
- package/dist/weaverse-client-BmceZc5q.js.map +1 -0
- package/dist/{weaverse-client-D59mBW0j.mjs → weaverse-client-CXnM9ayH.mjs} +4 -720
- package/dist/weaverse-client-CXnM9ayH.mjs.map +1 -0
- package/package.json +13 -7
- package/src/hooks/translation-context.tsx +7 -84
- package/src/next/client-boundary.ts +51 -0
- package/src/next/client.ts +102 -0
- package/src/next/hooks.ts +52 -0
- package/src/next/index.ts +145 -0
- package/src/next/item.ts +167 -0
- package/src/next/loader.ts +135 -0
- package/src/next/main.tsx +45 -0
- package/src/next/provider.tsx +241 -0
- package/src/next/registry.ts +63 -0
- package/src/next/renderer.tsx +102 -0
- package/src/next/request-info.ts +124 -0
- package/src/next/revalidate-item.ts +211 -0
- package/src/next/root-provider.tsx +201 -0
- package/src/next/runtime.ts +559 -0
- package/src/next/seo.ts +157 -0
- package/src/next/server/configs.ts +178 -0
- package/src/next/server/normalize-page-url.ts +73 -0
- package/src/next/server/revalidate-handler.ts +357 -0
- package/src/next/server/server-client.ts +728 -0
- package/src/next/server.ts +67 -0
- package/src/next/studio-bridge.tsx +82 -0
- package/src/next/studio-connect.tsx +88 -0
- package/src/next/studio-router.ts +85 -0
- package/src/next/studio-script-src.ts +128 -0
- package/src/next/theme-settings-store.ts +47 -0
- package/src/next/translation-context.tsx +182 -0
- package/src/next/types.ts +764 -0
- package/src/next/use-weaverse-next-studio.tsx +106 -0
- package/src/next/utils.ts +48 -0
- package/src/next-server.ts +41 -1
- package/src/next.ts +51 -15
- package/src/utils/translate.ts +101 -0
- package/dist/internal.js.map +0 -1
- package/dist/internal.mjs.map +0 -1
- package/dist/nested-key-CR3-YMFi.js.map +0 -1
- package/dist/nested-key-_k9aW_Wd.mjs.map +0 -1
- package/dist/translation-store-4fAV771j.js +0 -69
- package/dist/translation-store-4fAV771j.js.map +0 -1
- package/dist/translation-store-BBrw0FOk.mjs +0 -58
- package/dist/translation-store-BBrw0FOk.mjs.map +0 -1
- package/dist/weaverse-client-C9IgIH7O.js.map +0 -1
- package/dist/weaverse-client-D59mBW0j.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
1
|
+
import { a as getPreviewData, c as getStudioScriptSrc, d as WeaverseLoadError, f as hasError, h as isProjectIdFunction, i as generateDataFromSchema, l as resolveStudioScriptSrc, n as TranslationStore, o as getRequestQueries, p as hasWeaverseStudio, s as getWeaverseConfigs, u as WeaverseError } from "./translation-store-JFs-4lvf.mjs";
|
|
2
|
+
import { t as createTranslate } from "./translate-Celryv8j.mjs";
|
|
3
3
|
import { StudioSelectionStore, StudioSelectionStoreContext, Weaverse, WeaverseItemStore, WeaverseRoot, isBrowser, loadScript, useSafeExternalStore } from "@weaverse/react";
|
|
4
4
|
import { createSchema } from "@weaverse/schema";
|
|
5
5
|
import { Suspense, createContext, memo, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -8,62 +8,11 @@ import { Cache, createFetchWithCache, getSelectedProductOptions } from "@shopify
|
|
|
8
8
|
import { ErrorBoundary } from "react-error-boundary";
|
|
9
9
|
import { Await, useLoaderData, useLocation, useMatches, useNavigate, useNavigation, useRevalidator, useRouteLoaderData } from "react-router";
|
|
10
10
|
//#region src/hooks/translation-context.tsx
|
|
11
|
-
/**
|
|
12
|
-
* Replace `{{variable}}` patterns in a string with provided values.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* interpolate('Hello {{name}}!', { name: 'World' }) // => 'Hello World!'
|
|
16
|
-
* interpolate('-{{percentage}}% Off', { percentage: 15 }) // => '-15% Off'
|
|
17
|
-
*/
|
|
18
|
-
function interpolate(template, variables) {
|
|
19
|
-
if (!variables) return template;
|
|
20
|
-
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
21
|
-
const value = variables[key];
|
|
22
|
-
return value === void 0 ? match : String(value);
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
11
|
const TranslationContext = createContext(null);
|
|
26
12
|
TranslationContext.displayName = "WeaverseTranslationContext";
|
|
27
|
-
/**
|
|
28
|
-
* Build the `t()` translation function over a fixed set of sources.
|
|
29
|
-
*
|
|
30
|
-
* Resolution order, highest priority first:
|
|
31
|
-
* 1. `externalT` – host i18n (e.g. Shopify), unless it echoes the key
|
|
32
|
-
* 2. `designOverrides` – live design-mode edits, flat dot-path keys
|
|
33
|
-
* 3. `merchantOverrides` – locale overrides from the API, nested
|
|
34
|
-
* 4. `staticContent` – the theme's default locale JSON
|
|
35
|
-
* 5. the key itself
|
|
36
|
-
*
|
|
37
|
-
* Design overrides are looked up per-key (own properties only) rather than
|
|
38
|
-
* merged into `merchantOverrides`: a flat key like `cart.title` must override
|
|
39
|
-
* exactly that leaf without wiping sibling keys (`cart.subtitle`), and a
|
|
40
|
-
* per-key own-property lookup never builds nested objects from
|
|
41
|
-
* merchant-controlled keys, so it cannot pollute `Object.prototype`.
|
|
42
|
-
*/
|
|
43
|
-
function createTranslate(sources) {
|
|
44
|
-
const { staticContent, merchantOverrides, designOverrides, externalT } = sources;
|
|
45
|
-
return (key, variables) => {
|
|
46
|
-
if (externalT) {
|
|
47
|
-
const external = externalT(key, variables);
|
|
48
|
-
if (external && external !== key) return external;
|
|
49
|
-
}
|
|
50
|
-
const design = designOverrides && Object.hasOwn(designOverrides, key) ? designOverrides[key] : void 0;
|
|
51
|
-
const override = merchantOverrides ? getNestedKey(merchantOverrides, key) : void 0;
|
|
52
|
-
const staticValue = getNestedKey(staticContent, key);
|
|
53
|
-
return interpolate(design ?? override ?? staticValue ?? key, variables);
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
13
|
const EMPTY = {};
|
|
57
14
|
const NOOP_SUBSCRIBE = () => () => {};
|
|
58
15
|
const NOOP_SNAPSHOT = () => EMPTY;
|
|
59
|
-
/**
|
|
60
|
-
* Provider that wraps the app to expose the `t()` function.
|
|
61
|
-
*
|
|
62
|
-
* - `staticContent` – the theme's default locale JSON (from themeSchema.i18n)
|
|
63
|
-
* - `merchantOverrides` – locale-specific overrides fetched from the API
|
|
64
|
-
* - `translationStore` – optional store for live design-mode text overrides
|
|
65
|
-
* (the deprecated `themeTextStore` prop is accepted as a fallback)
|
|
66
|
-
*/
|
|
67
16
|
function TranslationProvider({ staticContent, merchantOverrides, t: externalT, translationStore, themeTextStore, children }) {
|
|
68
17
|
const store = translationStore ?? themeTextStore ?? null;
|
|
69
18
|
const designOverrides = useSafeExternalStore(store?.subscribe ?? NOOP_SUBSCRIBE, store?.getSnapshot ?? NOOP_SNAPSHOT, store?.getServerSnapshot ?? NOOP_SNAPSHOT);
|
|
@@ -89,55 +38,15 @@ function TranslationProvider({ staticContent, merchantOverrides, t: externalT, t
|
|
|
89
38
|
children
|
|
90
39
|
});
|
|
91
40
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Hook to access the translation `t()` function.
|
|
94
|
-
*
|
|
95
|
-
* Must be used inside a component wrapped by `withWeaverse` or `TranslationProvider`.
|
|
96
|
-
*
|
|
97
|
-
* @example
|
|
98
|
-
* ```tsx
|
|
99
|
-
* function CartTitle() {
|
|
100
|
-
* const { t } = useTranslation()
|
|
101
|
-
* return <h2>{t('cart.title')}</h2>
|
|
102
|
-
* }
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
41
|
function useTranslation() {
|
|
106
42
|
const ctx = useContext(TranslationContext);
|
|
107
43
|
if (!ctx) throw new Error("useTranslation must be used within <TranslationProvider>. Wrap your app with withWeaverse().");
|
|
108
44
|
return ctx;
|
|
109
45
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Legacy name for {@link TranslationProvider}.
|
|
112
|
-
* @deprecated Use {@link TranslationProvider} instead.
|
|
113
|
-
*/
|
|
114
46
|
const ThemeTextProvider = TranslationProvider;
|
|
115
|
-
/**
|
|
116
|
-
* Legacy name for {@link useTranslation}.
|
|
117
|
-
* @deprecated Use {@link useTranslation} instead.
|
|
118
|
-
*/
|
|
119
47
|
const useThemeText = useTranslation;
|
|
120
48
|
//#endregion
|
|
121
49
|
//#region src/seo.ts
|
|
122
|
-
/**
|
|
123
|
-
* Convert a Weaverse page-level SEO payload into React Router
|
|
124
|
-
* `MetaDescriptor[]`. Pure, synchronous, tree-shakeable — does not fetch.
|
|
125
|
-
*
|
|
126
|
-
* Behavior:
|
|
127
|
-
* - `title` → top-level `{ title }` descriptor.
|
|
128
|
-
* - `description` / `keywords` → `<meta name="...">`.
|
|
129
|
-
* - `canonicalUrl` → `<link rel="canonical">`.
|
|
130
|
-
* - `openGraph.*` → `<meta property="og:*">`. `og:type` defaults to
|
|
131
|
-
* `'website'` when any other OG field is present.
|
|
132
|
-
* - `twitter.*` → `<meta name="twitter:*">`. `twitter:card` defaults to
|
|
133
|
-
* `'summary'` when any other Twitter field is present.
|
|
134
|
-
* - `robots` is always emitted (defaults to `'index,follow'`) so themes
|
|
135
|
-
* stacking multiple SEO sources have a deterministic last word on
|
|
136
|
-
* indexing for Weaverse-managed pages.
|
|
137
|
-
* - Empty strings / missing fields are filtered before return so empty
|
|
138
|
-
* descriptors don't override earlier sources (e.g. Hydrogen
|
|
139
|
-
* `getSeoMeta`).
|
|
140
|
-
*/
|
|
141
50
|
function formatMetaDescriptors(seo) {
|
|
142
51
|
if (!seo) return [{
|
|
143
52
|
name: "robots",
|
|
@@ -204,38 +113,12 @@ function formatMetaDescriptors(seo) {
|
|
|
204
113
|
});
|
|
205
114
|
return descriptors;
|
|
206
115
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Convenience accessor: pull `page.seo` out of a `WeaverseLoaderData`
|
|
209
|
-
* (or `WeaversePageData`) and emit `MetaDescriptor[]`. Null-safe.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```ts
|
|
213
|
-
* export const meta: MetaFunction<typeof loader> = ({ data }) => {
|
|
214
|
-
* return getWeaverseSeoMeta(data?.weaverseData)
|
|
215
|
-
* }
|
|
216
|
-
* ```
|
|
217
|
-
*/
|
|
218
116
|
function getWeaverseSeoMeta(data) {
|
|
219
117
|
if (!data) return formatMetaDescriptors(null);
|
|
220
118
|
return formatMetaDescriptors(("items" in data && "id" in data ? data : data.page ?? null)?.seo ?? null);
|
|
221
119
|
}
|
|
222
120
|
//#endregion
|
|
223
121
|
//#region src/utils/product-options.ts
|
|
224
|
-
/**
|
|
225
|
-
* Filter out Weaverse-specific query parameters from product options.
|
|
226
|
-
* Removes `isDesignMode` and any parameters starting with `weaverse` prefix.
|
|
227
|
-
*
|
|
228
|
-
* @param request - The incoming HTTP request
|
|
229
|
-
* @returns Filtered product options for Shopify Hydrogen
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* ```typescript
|
|
233
|
-
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
234
|
-
* const selectedOptions = getSelectedProductOptions(request)
|
|
235
|
-
* // Use selectedOptions for product queries without Weaverse params
|
|
236
|
-
* }
|
|
237
|
-
* ```
|
|
238
|
-
*/
|
|
239
122
|
function getSelectedProductOptions$1(request) {
|
|
240
123
|
return getSelectedProductOptions({ searchParams: new URL(request.url).searchParams }).filter(({ name }) => name !== "isDesignMode" && !name.startsWith("weaverse"));
|
|
241
124
|
}
|
|
@@ -251,7 +134,6 @@ function Main(props) {
|
|
|
251
134
|
}
|
|
252
135
|
//#endregion
|
|
253
136
|
//#region src/components/index.ts
|
|
254
|
-
/** Built-in components required to render the root of every Weaverse page. */
|
|
255
137
|
let defaultComponents = [{
|
|
256
138
|
default: Main,
|
|
257
139
|
schema: createSchema({
|
|
@@ -261,68 +143,19 @@ let defaultComponents = [{
|
|
|
261
143
|
}];
|
|
262
144
|
//#endregion
|
|
263
145
|
//#region src/hooks/use-weaverse-data-context.ts
|
|
264
|
-
/**
|
|
265
|
-
* Creates flat route-keyed structure from matches
|
|
266
|
-
* Target: {root: {...rootData}, "routes/product": {...productData}}
|
|
267
|
-
* Eliminates all duplication by using route IDs as keys
|
|
268
|
-
*/
|
|
269
146
|
function createFlatDataContext(matches) {
|
|
270
147
|
const context = {};
|
|
271
148
|
for (const match of matches) if (match.data && typeof match.data === "object") context[match.id] = match.data;
|
|
272
149
|
return context;
|
|
273
150
|
}
|
|
274
|
-
/**
|
|
275
|
-
* Hook to get flat route-keyed data context from matches
|
|
276
|
-
* Returns: {root: {...}, "routes/xxx": {...}}
|
|
277
|
-
* No more useLoaderData dependency - everything comes from matches
|
|
278
|
-
*/
|
|
279
151
|
function useWeaverseDataContext() {
|
|
280
152
|
return createFlatDataContext(useMatches());
|
|
281
153
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Helper function to create flat route-keyed data context from useMatches() result
|
|
284
|
-
* Returns: {root: {...}, "routes/xxx": {...}}
|
|
285
|
-
*/
|
|
286
154
|
function createWeaverseDataContext(matches) {
|
|
287
155
|
return createFlatDataContext(matches);
|
|
288
156
|
}
|
|
289
157
|
//#endregion
|
|
290
158
|
//#region src/utils/pick-weaverse-data.ts
|
|
291
|
-
/**
|
|
292
|
-
* Resolve the `weaverseData` payload for a `<WeaverseHydrogenRoot>` instance.
|
|
293
|
-
*
|
|
294
|
-
* Resolution order (first match wins):
|
|
295
|
-
*
|
|
296
|
-
* 1. **Explicit prop** — the caller passed `data` directly. Used as-is,
|
|
297
|
-
* including when it is a `Promise` (handled upstream via `<Await>`) or
|
|
298
|
-
* an explicit `null` (suppresses rendering).
|
|
299
|
-
* 2. **Own route's loader data** — the loader data for the route that
|
|
300
|
-
* rendered this component, obtained via `useLoaderData()`. This is
|
|
301
|
-
* React Router's route-scoped primitive and is what makes nested
|
|
302
|
-
* layout + child Weaverse pages work: each `<WeaverseContent />`
|
|
303
|
-
* resolves to *its own* route's `weaverseData` regardless of siblings.
|
|
304
|
-
* 3. **Ancestor match fallback** — walk `useMatches()` from leaf to root
|
|
305
|
-
* and return the first ancestor whose `loaderData` exposes a
|
|
306
|
-
* `weaverseData` key. This preserves the long-standing behaviour where
|
|
307
|
-
* a child route with no loader (e.g. an `index.tsx` that omits
|
|
308
|
-
* `weaverseData`) still renders its layout's Weaverse page.
|
|
309
|
-
*
|
|
310
|
-
* The helper is intentionally pure (no hook calls) so it can be unit-tested
|
|
311
|
-
* without a React Router context. Hook calls happen in the component.
|
|
312
|
-
*
|
|
313
|
-
* ## Back-compat contract
|
|
314
|
-
*
|
|
315
|
-
* Themes that pre-date the `data` prop addition pass `dataProp === undefined`,
|
|
316
|
-
* so resolution starts at Tier 2. For the canonical single-instance pattern
|
|
317
|
-
* (one `<WeaverseContent />` inside a route whose loader returns
|
|
318
|
-
* `{ weaverseData }`), Tier 2 returns the same value the old `useMatches()`
|
|
319
|
-
* walk did. For the index-without-loader pattern (leaf route exposes no
|
|
320
|
-
* `weaverseData`, layout does), Tier 3's leaf→root walk produces an
|
|
321
|
-
* identical result to the pre-fix code. The only intentional behaviour
|
|
322
|
-
* change is the issue #451 case: two `<WeaverseContent />` instances on the
|
|
323
|
-
* same URL (layout + child, both with their own `weaverseData`) now each
|
|
324
|
-
* render their own page instead of both rendering the deepest one.
|
|
325
|
-
*/
|
|
326
159
|
function pickWeaverseData(dataProp, ownLoaderData, matches) {
|
|
327
160
|
if (dataProp !== void 0) return dataProp;
|
|
328
161
|
if (hasWeaverseData(ownLoaderData)) return ownLoaderData.weaverseData;
|
|
@@ -350,15 +183,6 @@ function getQueriesFromSearch(search) {
|
|
|
350
183
|
else queries[key] = value;
|
|
351
184
|
return queries;
|
|
352
185
|
}
|
|
353
|
-
/**
|
|
354
|
-
* React Router v7 single-fetch revalidation can render client data returned
|
|
355
|
-
* from a `*.data` request while the browser is still on the canonical URL.
|
|
356
|
-
* Instance identity must follow the browser URL, not the transient data
|
|
357
|
-
* endpoint, otherwise the SDK creates a fresh Weaverse instance mid-session:
|
|
358
|
-
* in design mode that drops the existing Studio bridge/interactions, and in
|
|
359
|
-
* the live theme it resets every section's runtime state after any
|
|
360
|
-
* revalidation (cart mutations, locale switches, `revalidate()`). See #451.
|
|
361
|
-
*/
|
|
362
186
|
function normalizeRequestInfo(params, browserLocation = getBrowserLocation()) {
|
|
363
187
|
if (!browserLocation) return params;
|
|
364
188
|
let { pathname, search } = browserLocation;
|
|
@@ -375,50 +199,12 @@ function normalizeRequestInfo(params, browserLocation = getBrowserLocation()) {
|
|
|
375
199
|
}
|
|
376
200
|
//#endregion
|
|
377
201
|
//#region src/utils/is-same-loader-payload.ts
|
|
378
|
-
/**
|
|
379
|
-
* Structural comparison used to detect whether a reused Weaverse instance
|
|
380
|
-
* must adopt a fresh loader payload.
|
|
381
|
-
*
|
|
382
|
-
* `JSON.stringify` cannot do this job on the client:
|
|
383
|
-
*
|
|
384
|
-
* - Route loader data reaches `dataContext` unserialized, so deferred values
|
|
385
|
-
* are still `Promise`s. React 19 development builds hang an enumerable,
|
|
386
|
-
* self-referencing `_debugInfo` off those promises, and stringifying one
|
|
387
|
-
* throws `TypeError: Converting circular structure to JSON` during render
|
|
388
|
-
* (issue #511).
|
|
389
|
-
* - In production every promise stringifies to `{}`, so a *fresh* deferred
|
|
390
|
-
* value looks unchanged and the reused instance keeps serving the previous
|
|
391
|
-
* render's promise. Tokenizing promises to a constant (`'[[promise]]'`) has
|
|
392
|
-
* the same defect — it is stable by construction, therefore blind to
|
|
393
|
-
* identity.
|
|
394
|
-
*
|
|
395
|
-
* Semantics, chosen deliberately:
|
|
396
|
-
*
|
|
397
|
-
* - **Promises/thenables are atomic**: compared by identity, never traversed.
|
|
398
|
-
* A fresh promise is a change; the same promise is not. Debug metadata is
|
|
399
|
-
* invisible to the comparison.
|
|
400
|
-
* - **Opaque objects are atomic**: anything that is not a plain object, an
|
|
401
|
-
* array, or `toJSON`-serializable (`Map`, `Set`, `URL`, `RegExp`, streams,
|
|
402
|
-
* async iterators, cross-realm promises, class instances) is compared by
|
|
403
|
-
* identity. `JSON.stringify` flattens all of them to `{}`, which would
|
|
404
|
-
* silently collapse two distinct async values into "unchanged".
|
|
405
|
-
* - **Cyclic graphs are compared coinductively**: a pair already being
|
|
406
|
-
* compared is assumed equal, so equal self/mutual cycles are equal while a
|
|
407
|
-
* differing leaf anywhere still reports a change. Nothing throws.
|
|
408
|
-
* - **JSON-visible fields only**: `undefined`/function/symbol properties are
|
|
409
|
-
* omitted from objects and read as `null` in arrays, and NaN/±Infinity read
|
|
410
|
-
* as `null` — matching the previous `JSON.stringify` behavior for plain wire
|
|
411
|
-
* payloads. Key order is irrelevant, which removes the old "false mismatch
|
|
412
|
-
* costs a re-render" caveat.
|
|
413
|
-
*/
|
|
414
|
-
/** `JSON.stringify` drops these from objects and nulls them inside arrays. */
|
|
415
202
|
function isJsonOmitted(value) {
|
|
416
203
|
return value === void 0 || typeof value === "function" || typeof value === "symbol";
|
|
417
204
|
}
|
|
418
205
|
function isObjectLike(value) {
|
|
419
206
|
return value !== null && typeof value === "object";
|
|
420
207
|
}
|
|
421
|
-
/** Duck-typed so cross-realm promises and hand-rolled thenables also match. */
|
|
422
208
|
function isThenable(value) {
|
|
423
209
|
return typeof value.then === "function";
|
|
424
210
|
}
|
|
@@ -426,15 +212,12 @@ function isPlainObject$1(value) {
|
|
|
426
212
|
const proto = Object.getPrototypeOf(value);
|
|
427
213
|
return proto === Object.prototype || proto === null;
|
|
428
214
|
}
|
|
429
|
-
/** Returns the `toJSON()` projection, or the value itself when there is none. */
|
|
430
215
|
function toJsonValue(value) {
|
|
431
216
|
return typeof value.toJSON === "function" ? value.toJSON() : value;
|
|
432
217
|
}
|
|
433
|
-
/** Maps NaN/±Infinity to `null`, matching how `JSON.stringify` renders them. */
|
|
434
218
|
function toFiniteOrNull(value) {
|
|
435
219
|
return typeof value === "number" && !Number.isFinite(value) ? null : value;
|
|
436
220
|
}
|
|
437
|
-
/** Compares two loader payloads for JSON-visible structural equality. */
|
|
438
221
|
function isSameLoaderPayload(left, right) {
|
|
439
222
|
const activePairs = [];
|
|
440
223
|
function isSame(rawA, rawB) {
|
|
@@ -481,25 +264,6 @@ function isSameLoaderPayload(left, right) {
|
|
|
481
264
|
}
|
|
482
265
|
//#endregion
|
|
483
266
|
//#region src/utils/sync-reused-instance.ts
|
|
484
|
-
/**
|
|
485
|
-
* Apply a fresh loader payload to a reused Weaverse instance.
|
|
486
|
-
*
|
|
487
|
-
* With requestInfo normalized to the browser URL, same-URL revalidations
|
|
488
|
-
* (cart mutations, `useRevalidator()`, locale-neutral re-fetches) reuse the
|
|
489
|
-
* cached `window.__weaverses[pageId]` instance instead of constructing a new
|
|
490
|
-
* one — which preserves section state, but would silently drop the fresh
|
|
491
|
-
* `data`/`dataContext` the loaders just returned. Sync them here.
|
|
492
|
-
*
|
|
493
|
-
* Design mode is excluded by the caller: Studio owns the instance data there
|
|
494
|
-
* (unsaved drafts must not be clobbered) and applies updates via
|
|
495
|
-
* `refreshStudio` instead.
|
|
496
|
-
*
|
|
497
|
-
* Change detection uses {@link isSameLoaderPayload}: a structural comparison
|
|
498
|
-
* that treats promises/thenables and other opaque objects as atomic values
|
|
499
|
-
* compared by identity, and tolerates cyclic graphs. `dataContext` carries
|
|
500
|
-
* live route loader data — including unresolved deferred promises whose React
|
|
501
|
-
* development metadata is cyclic — so it cannot be JSON-serialized here.
|
|
502
|
-
*/
|
|
503
267
|
function syncReusedInstance(weaverse, params) {
|
|
504
268
|
weaverse.requestInfo = params.requestInfo;
|
|
505
269
|
weaverse.internal = params.internal;
|
|
@@ -512,18 +276,13 @@ function syncReusedInstance(weaverse, params) {
|
|
|
512
276
|
}
|
|
513
277
|
//#endregion
|
|
514
278
|
//#region src/utils/use-theme-settings-store.ts
|
|
515
|
-
/** Mutable, subscribable store for theme settings received from the root loader. */
|
|
516
279
|
var ThemeSettingsStore = class {
|
|
517
|
-
/** Current merged theme setting values. */
|
|
518
280
|
settings = {};
|
|
519
|
-
/** Theme schema exposed to Studio, when available. */
|
|
520
281
|
schema;
|
|
521
|
-
/** Storefront-safe environment values, when available. */
|
|
522
282
|
publicEnv;
|
|
523
283
|
listeners = /* @__PURE__ */ new Set();
|
|
524
284
|
hasWarnedListenerCount = false;
|
|
525
285
|
isDestroyed = false;
|
|
526
|
-
/** Creates a theme settings store from root-loader theme data. */
|
|
527
286
|
constructor(data) {
|
|
528
287
|
const { theme, schema, publicEnv } = data || {};
|
|
529
288
|
this.settings = { ...theme };
|
|
@@ -531,7 +290,6 @@ var ThemeSettingsStore = class {
|
|
|
531
290
|
this.publicEnv = publicEnv;
|
|
532
291
|
if (isBrowser) window.__weaverseThemeSettingsStore = this;
|
|
533
292
|
}
|
|
534
|
-
/** Merges setting changes into the current snapshot and notifies subscribers. */
|
|
535
293
|
updateThemeSettings = (newSettings) => {
|
|
536
294
|
if (this.isDestroyed) {
|
|
537
295
|
console.warn("ThemeSettingsStore: Cannot update destroyed store");
|
|
@@ -543,11 +301,8 @@ var ThemeSettingsStore = class {
|
|
|
543
301
|
};
|
|
544
302
|
this.emit();
|
|
545
303
|
};
|
|
546
|
-
/** Returns the current client snapshot for `useSyncExternalStore`. */
|
|
547
304
|
getSnapshot = () => this.settings;
|
|
548
|
-
/** Returns the current server snapshot for `useSyncExternalStore`. */
|
|
549
305
|
getServerSnapshot = () => this.settings;
|
|
550
|
-
/** Subscribes to setting changes and returns an unsubscribe callback. */
|
|
551
306
|
subscribe = (callback) => {
|
|
552
307
|
if (this.isDestroyed) {
|
|
553
308
|
console.warn("ThemeSettingsStore: Cannot subscribe to destroyed store");
|
|
@@ -571,7 +326,6 @@ var ThemeSettingsStore = class {
|
|
|
571
326
|
console.error("ThemeSettingsStore: Listener error", error);
|
|
572
327
|
}
|
|
573
328
|
};
|
|
574
|
-
/** Clears subscribers and detaches the browser-global store reference. */
|
|
575
329
|
destroy = () => {
|
|
576
330
|
if (this.isDestroyed) return;
|
|
577
331
|
this.listeners.clear();
|
|
@@ -611,12 +365,6 @@ function useThemeSettingsStore() {
|
|
|
611
365
|
//#region src/utils/use-studio.ts
|
|
612
366
|
const STUDIO_READY_POLL_MS = 50;
|
|
613
367
|
const STUDIO_READY_TIMEOUT_MS = 5e3;
|
|
614
|
-
/**
|
|
615
|
-
* Resolve once `window.weaverseStudio` is available, or `null` on timeout.
|
|
616
|
-
* The studio script sets the global when it *executes*; `loadScript` can
|
|
617
|
-
* resolve earlier for tags it did not create (e.g. SSR-injected markup),
|
|
618
|
-
* so never assume readiness right after the script promise settles.
|
|
619
|
-
*/
|
|
620
368
|
function waitForStudio() {
|
|
621
369
|
return new Promise((resolve) => {
|
|
622
370
|
let startedAt = Date.now();
|
|
@@ -631,34 +379,6 @@ function waitForStudio() {
|
|
|
631
379
|
check();
|
|
632
380
|
});
|
|
633
381
|
}
|
|
634
|
-
/**
|
|
635
|
-
* Load the Studio bridge script on every design/preview render so Studio's
|
|
636
|
-
* `checkWeaversePage()` handshake is answered instead of timing out — crucially
|
|
637
|
-
* on routes where the page-scoped {@link useStudio} never mounts: 404s, error
|
|
638
|
-
* boundaries, and non-Weaverse routes. The bridge registers its RPC endpoint
|
|
639
|
-
* and `window.weaverseStudio` the moment the script executes, independent of any
|
|
640
|
-
* page binding. Without this, a content-less route never loads the script and
|
|
641
|
-
* Studio reports a false "Connection lost" instead of "this page has no Weaverse
|
|
642
|
-
* content set up".
|
|
643
|
-
*
|
|
644
|
-
* `loadScript` is idempotent, so on content routes this coexists with the
|
|
645
|
-
* page-scoped {@link useStudio} (same src) — that hook still binds the page via
|
|
646
|
-
* `init`. Gating this on whether a route "has Weaverse data" is deliberately
|
|
647
|
-
* avoided: route matches can't tell whether the page-scoped bridge actually
|
|
648
|
-
* mounts (an `ErrorBoundary` replacing a data route, or data passed via the
|
|
649
|
-
* `data` prop), and suppressing the script there would reintroduce the timeout
|
|
650
|
-
* this fixes. Loading the script is harmless; it only registers the responder.
|
|
651
|
-
*
|
|
652
|
-
* Known limitation: on a route whose `weaverseData` is a deferred Promise, the
|
|
653
|
-
* script can load before the page streams in, so `checkWeaversePage()` may
|
|
654
|
-
* briefly observe `NOT_WEAVERSE_PAGE`. The complete fix is a "loading" state in
|
|
655
|
-
* `checkWeaversePage` so the handshake retries rather than latching — that lives
|
|
656
|
-
* in the Studio script, not here. Tracked in Weaverse/builder#2515.
|
|
657
|
-
*
|
|
658
|
-
* Mounted via {@link withWeaverse}, which must be the root route's `Layout`
|
|
659
|
-
* export so the bridge renders even when a route's `ErrorBoundary` replaces the
|
|
660
|
-
* page. Outside Studio (no design/preview params) it is a no-op.
|
|
661
|
-
*/
|
|
662
382
|
function useStudioConnect() {
|
|
663
383
|
let { search } = useLocation();
|
|
664
384
|
useEffect(() => {
|
|
@@ -714,16 +434,10 @@ function useStudio(weaverse) {
|
|
|
714
434
|
}
|
|
715
435
|
//#endregion
|
|
716
436
|
//#region src/WeaverseHydrogenRoot.tsx
|
|
717
|
-
/** Runtime store for one rendered Hydrogen component instance. */
|
|
718
437
|
var WeaverseHydrogenItem = class extends WeaverseItemStore {
|
|
719
|
-
/**
|
|
720
|
-
* Cached merged snapshot (base + translations).
|
|
721
|
-
* Reset to `null` whenever `_store` or the item's translation entry changes.
|
|
722
|
-
*/
|
|
723
438
|
_cachedSnapshot = null;
|
|
724
439
|
_snapshotStoreRef = null;
|
|
725
440
|
_snapshotTranslationRef = null;
|
|
726
|
-
/** Creates an item store from serialized component data and its owner. */
|
|
727
441
|
constructor(initialData, weaverse) {
|
|
728
442
|
super(initialData, weaverse);
|
|
729
443
|
const { data, ...rest } = initialData;
|
|
@@ -734,26 +448,9 @@ var WeaverseHydrogenItem = class extends WeaverseItemStore {
|
|
|
734
448
|
const schemaData = generateDataFromSchema(this.Element.schema);
|
|
735
449
|
Object.assign(this._store, schemaData, data, rest);
|
|
736
450
|
}
|
|
737
|
-
/** Registered component definition for this item's type. */
|
|
738
451
|
get Element() {
|
|
739
452
|
return super.Element;
|
|
740
453
|
}
|
|
741
|
-
/**
|
|
742
|
-
* Override setData to flatten serialized `data` the same way the constructor
|
|
743
|
-
* does. Core's `initProject()` reuses item stores keyed by item id (identical
|
|
744
|
-
* ids across locales), and the inherited setData only shallow-merges the raw
|
|
745
|
-
* item — fresh settings would land under `_store.data` while the stale
|
|
746
|
-
* flattened values keep rendering.
|
|
747
|
-
*
|
|
748
|
-
* Schema defaults are re-applied ahead of incoming settings because a
|
|
749
|
-
* serialized payload can omit fields — or the optional `data` object itself —
|
|
750
|
-
* when values equal schema defaults. The stored nested `data` is also replaced
|
|
751
|
-
* so the public serialized state does not retain the previous locale payload.
|
|
752
|
-
*
|
|
753
|
-
* An empty update is the context-only refresh from `syncReusedInstance`: swap
|
|
754
|
-
* the store reference so memoized subscribers re-render, and leave the
|
|
755
|
-
* settings untouched.
|
|
756
|
-
*/
|
|
757
454
|
setData = (update) => {
|
|
758
455
|
let source = update ?? {};
|
|
759
456
|
let { data, ...rest } = source;
|
|
@@ -780,12 +477,6 @@ var WeaverseHydrogenItem = class extends WeaverseItemStore {
|
|
|
780
477
|
this.triggerUpdate();
|
|
781
478
|
return this.data;
|
|
782
479
|
};
|
|
783
|
-
/**
|
|
784
|
-
* Override getSnapShot to merge translation data when in translation mode.
|
|
785
|
-
* Memoizes the result: returns the same object reference as long as
|
|
786
|
-
* both `_store` and the translation entry haven't changed.
|
|
787
|
-
* This is critical for `useSyncExternalStore` stability.
|
|
788
|
-
*/
|
|
789
480
|
getSnapShot = () => {
|
|
790
481
|
const base = this._store;
|
|
791
482
|
const translations = this.weaverse.translationMap?.[this._id];
|
|
@@ -800,50 +491,22 @@ var WeaverseHydrogenItem = class extends WeaverseItemStore {
|
|
|
800
491
|
};
|
|
801
492
|
};
|
|
802
493
|
Weaverse.ItemConstructor = WeaverseHydrogenItem;
|
|
803
|
-
/**
|
|
804
|
-
* Hydrogen-specific Weaverse runtime that owns page data, component stores,
|
|
805
|
-
* Studio state, and translation sidecars for one rendered page.
|
|
806
|
-
*/
|
|
807
494
|
var WeaverseHydrogen = class extends Weaverse {
|
|
808
|
-
/** Identifier of the rendered page. */
|
|
809
495
|
pageId;
|
|
810
|
-
/** Runtime services and project metadata exposed to integrations. */
|
|
811
496
|
internal;
|
|
812
|
-
/** Normalized URL and locale information for the current request. */
|
|
813
497
|
requestInfo;
|
|
814
|
-
/** Identifier of the project supplying the page. */
|
|
815
498
|
projectId;
|
|
816
|
-
/** Origin used for Studio assets and editor communication. */
|
|
817
499
|
weaverseHost;
|
|
818
|
-
/** Base URL used for public content API requests. */
|
|
819
500
|
weaverseApiBase;
|
|
820
|
-
/**
|
|
821
|
-
* API key read from environment/configuration for storefront requests or
|
|
822
|
-
* supplied by Studio requests while editing and previewing.
|
|
823
|
-
*/
|
|
824
501
|
weaverseApiKey;
|
|
825
|
-
/** Studio asset version supplied by the preview request. */
|
|
826
502
|
weaverseVersion;
|
|
827
|
-
/** Whether the page is running inside the visual editor. */
|
|
828
503
|
isDesignMode;
|
|
829
|
-
/** Whether a single component preview is being rendered. */
|
|
830
504
|
isPreviewMode;
|
|
831
|
-
/** Whether a saved page revision is being previewed. */
|
|
832
505
|
isRevisionPreview;
|
|
833
|
-
/** Component type requested by section preview mode. */
|
|
834
506
|
sectionType;
|
|
835
|
-
/**
|
|
836
|
-
* Translation map received from the builder in design mode.
|
|
837
|
-
* Keyed by item ID, each entry maps field names to translated values.
|
|
838
|
-
* `WeaverseHydrogenItem.getSnapShot()` merges these into the data
|
|
839
|
-
* returned to components, so translations render without monkey-patching.
|
|
840
|
-
*/
|
|
841
507
|
translationMap = {};
|
|
842
|
-
/** Locale code for the active translation sidecar. */
|
|
843
508
|
translationLocale = "";
|
|
844
|
-
/** Builder language identifier for the active translation sidecar. */
|
|
845
509
|
translationLanguageId = "";
|
|
846
|
-
/** Creates a page runtime from resolved loader data and request settings. */
|
|
847
510
|
constructor(params) {
|
|
848
511
|
const { internal, pageId, requestInfo, ...coreParams } = params;
|
|
849
512
|
super({ ...coreParams });
|
|
@@ -861,11 +524,6 @@ var WeaverseHydrogen = class extends Weaverse {
|
|
|
861
524
|
this.sectionType = params.sectionType || "";
|
|
862
525
|
this.extractTranslationSidecar();
|
|
863
526
|
}
|
|
864
|
-
/**
|
|
865
|
-
* Extract translation sidecar data from `this.data` (the page data).
|
|
866
|
-
* The builder attaches `translationMap`, `translationLocale`, and
|
|
867
|
-
* `translationLanguageId` to the page data in design mode.
|
|
868
|
-
*/
|
|
869
527
|
extractTranslationSidecar = () => {
|
|
870
528
|
const pageData = this.data;
|
|
871
529
|
if (pageData?.translationMap) {
|
|
@@ -874,21 +532,12 @@ var WeaverseHydrogen = class extends Weaverse {
|
|
|
874
532
|
this.translationLanguageId = pageData.translationLanguageId || "";
|
|
875
533
|
}
|
|
876
534
|
};
|
|
877
|
-
/**
|
|
878
|
-
* Set the translation sidecar data.
|
|
879
|
-
* Called by the studio bridge when translation data arrives or changes.
|
|
880
|
-
* Triggers a re-render of all items so translated snapshots are recalculated.
|
|
881
|
-
*/
|
|
882
535
|
setTranslationSidecar = (translationMap, locale, languageId) => {
|
|
883
536
|
this.translationMap = translationMap;
|
|
884
537
|
this.translationLocale = locale;
|
|
885
538
|
this.translationLanguageId = languageId;
|
|
886
539
|
this.refreshAllItems();
|
|
887
540
|
};
|
|
888
|
-
/**
|
|
889
|
-
* Update a single translation entry for a specific item field.
|
|
890
|
-
* Used by the studio bridge when the user edits a translatable field.
|
|
891
|
-
*/
|
|
892
541
|
updateTranslation = (itemId, key, originalValue, translatedValue) => {
|
|
893
542
|
if (!this.translationMap[itemId]) this.translationMap[itemId] = {};
|
|
894
543
|
this.translationMap[itemId] = {
|
|
@@ -901,10 +550,6 @@ var WeaverseHydrogen = class extends Weaverse {
|
|
|
901
550
|
const instance = this.itemInstances.get(itemId);
|
|
902
551
|
if (instance) instance.triggerUpdate();
|
|
903
552
|
};
|
|
904
|
-
/**
|
|
905
|
-
* Collect all translation changes across all weaverse instances.
|
|
906
|
-
* Used by the save flow to persist translation edits.
|
|
907
|
-
*/
|
|
908
553
|
getTranslationChanges = () => {
|
|
909
554
|
const entries = [];
|
|
910
555
|
const languageId = this.translationLanguageId;
|
|
@@ -921,9 +566,6 @@ var WeaverseHydrogen = class extends Weaverse {
|
|
|
921
566
|
entries
|
|
922
567
|
};
|
|
923
568
|
};
|
|
924
|
-
/**
|
|
925
|
-
* Override setProjectData to re-extract translation sidecar from new page data.
|
|
926
|
-
*/
|
|
927
569
|
setProjectData = (data) => {
|
|
928
570
|
this.data = data;
|
|
929
571
|
this.extractTranslationSidecar();
|
|
@@ -955,12 +597,6 @@ const StudioConnect = memo(() => {
|
|
|
955
597
|
useStudioConnect();
|
|
956
598
|
return null;
|
|
957
599
|
});
|
|
958
|
-
/**
|
|
959
|
-
* Component lists already pushed into the element registry. Registration is
|
|
960
|
-
* idempotent (the registry checks `has(type)`), but walking the full list
|
|
961
|
-
* allocates and loops on every render — theme component arrays are
|
|
962
|
-
* module-level constants, so register each list identity once.
|
|
963
|
-
*/
|
|
964
600
|
const registeredComponentLists = /* @__PURE__ */ new WeakSet();
|
|
965
601
|
function RenderRoot(props) {
|
|
966
602
|
const { data, components, dataContext } = props;
|
|
@@ -986,36 +622,9 @@ function RenderRoot(props) {
|
|
|
986
622
|
});
|
|
987
623
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(WeaverseRoot, { context: weaverse }), /* @__PURE__ */ jsx(StudioBridge, { context: weaverse })] });
|
|
988
624
|
}
|
|
989
|
-
/**
|
|
990
|
-
* Register a Weaverse component with the global element registry.
|
|
991
|
-
* Must be called before rendering components in WeaverseHydrogenRoot.
|
|
992
|
-
*
|
|
993
|
-
* @param element - Component configuration with schema, Component, and optional loader
|
|
994
|
-
*
|
|
995
|
-
* @example
|
|
996
|
-
* ```typescript
|
|
997
|
-
* import Hero from './sections/Hero'
|
|
998
|
-
* import { schema } from './sections/Hero'
|
|
999
|
-
*
|
|
1000
|
-
* registerComponent({
|
|
1001
|
-
* type: 'hero',
|
|
1002
|
-
* Component: Hero,
|
|
1003
|
-
* schema,
|
|
1004
|
-
* loader: async ({ data, weaverse }) => {
|
|
1005
|
-
* return { someData: 'value' }
|
|
1006
|
-
* }
|
|
1007
|
-
* })
|
|
1008
|
-
* ```
|
|
1009
|
-
*/
|
|
1010
625
|
function registerComponent(element) {
|
|
1011
626
|
WeaverseHydrogen.registerElement(element);
|
|
1012
627
|
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Renders the Weaverse page for the current React Router route.
|
|
1015
|
-
*
|
|
1016
|
-
* Pass `data` to override automatic route-loader resolution. A promise is
|
|
1017
|
-
* handled with React Router's `Await`, while explicit `null` suppresses output.
|
|
1018
|
-
*/
|
|
1019
628
|
const WeaverseHydrogenRoot = memo(({ components, data: dataProp, errorComponent: ErrorComponent = ({ error }) => /* @__PURE__ */ jsx("div", { children: error instanceof Error ? error.message : "An unexpected error occurred" }) }) => {
|
|
1020
629
|
const matches = useMatches();
|
|
1021
630
|
const enhancedDataContext = useMemo(() => createWeaverseDataContext(matches), [matches]);
|
|
@@ -1046,33 +655,6 @@ const WeaverseHydrogenRoot = memo(({ components, data: dataProp, errorComponent:
|
|
|
1046
655
|
}
|
|
1047
656
|
return /* @__PURE__ */ jsx(ErrorComponent, { error: { message: "No Weaverse data found in route matches!" } });
|
|
1048
657
|
});
|
|
1049
|
-
/**
|
|
1050
|
-
* Higher-order component that wraps your app with Weaverse theme settings and
|
|
1051
|
-
* translation context, and mounts the Studio connect bridge.
|
|
1052
|
-
*
|
|
1053
|
-
* Export the result as your root route's **`Layout`** (not the default route
|
|
1054
|
-
* `Component`). In React Router the root `Layout` wraps both the matched route
|
|
1055
|
-
* and its `ErrorBoundary`, so the Studio bridge keeps rendering on 404s and
|
|
1056
|
-
* loader/render errors — exactly the pages where Studio must still answer its
|
|
1057
|
-
* handshake instead of reporting a false "Connection lost". A default-component
|
|
1058
|
-
* wrapper (`export default withWeaverse(App)`) is replaced by the error
|
|
1059
|
-
* boundary on those routes and would drop the bridge.
|
|
1060
|
-
*
|
|
1061
|
-
* @param Component - The root layout component to wrap
|
|
1062
|
-
* @param options - Optional configuration (e.g. an external `t` function)
|
|
1063
|
-
* @returns Wrapped component with theme settings + translation providers
|
|
1064
|
-
*
|
|
1065
|
-
* @example
|
|
1066
|
-
* ```typescript
|
|
1067
|
-
* import { withWeaverse } from '@weaverse/hydrogen'
|
|
1068
|
-
*
|
|
1069
|
-
* // Basic usage — export as the root route Layout
|
|
1070
|
-
* export const Layout = withWeaverse(RootLayout)
|
|
1071
|
-
*
|
|
1072
|
-
* // With an external translation function
|
|
1073
|
-
* export const Layout = withWeaverse(RootLayout, { t: myI18nFunction })
|
|
1074
|
-
* ```
|
|
1075
|
-
*/
|
|
1076
658
|
function withWeaverse(Component, options) {
|
|
1077
659
|
return (props) => {
|
|
1078
660
|
const themeSettingsStore = useCreateThemeSettingsStore();
|
|
@@ -1095,10 +677,6 @@ function withWeaverse(Component, options) {
|
|
|
1095
677
|
});
|
|
1096
678
|
};
|
|
1097
679
|
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Internal hook to read staticContent and merchantOverrides from root loader data.
|
|
1100
|
-
* Similar pattern to useThemeSettingsStore – reads from the 'root' route loader.
|
|
1101
|
-
*/
|
|
1102
680
|
function useTranslationData() {
|
|
1103
681
|
const data = useRouteLoaderData("root");
|
|
1104
682
|
return {
|
|
@@ -1106,22 +684,6 @@ function useTranslationData() {
|
|
|
1106
684
|
merchantOverrides: data?.weaverseTheme?.merchantOverrides
|
|
1107
685
|
};
|
|
1108
686
|
}
|
|
1109
|
-
/**
|
|
1110
|
-
* React hook to access Weaverse theme settings from anywhere in your component tree.
|
|
1111
|
-
* Must be used within a component wrapped by `withWeaverse` or inside `WeaverseHydrogenRoot`.
|
|
1112
|
-
*
|
|
1113
|
-
* @returns Current theme settings object
|
|
1114
|
-
*
|
|
1115
|
-
* @example
|
|
1116
|
-
* ```typescript
|
|
1117
|
-
* function MyComponent() {
|
|
1118
|
-
* const theme = useThemeSettings()
|
|
1119
|
-
* const primaryColor = theme.colors?.primary
|
|
1120
|
-
*
|
|
1121
|
-
* return <div style={{ color: primaryColor }}>Styled content</div>
|
|
1122
|
-
* }
|
|
1123
|
-
* ```
|
|
1124
|
-
*/
|
|
1125
687
|
function useThemeSettings() {
|
|
1126
688
|
const themeSettingsStore = useThemeSettingsStore();
|
|
1127
689
|
return useSafeExternalStore(themeSettingsStore.subscribe, themeSettingsStore.getSnapshot, themeSettingsStore.getServerSnapshot);
|
|
@@ -1129,20 +691,6 @@ function useThemeSettings() {
|
|
|
1129
691
|
//#endregion
|
|
1130
692
|
//#region src/utils/normalize-page-url.ts
|
|
1131
693
|
const KEPT_PARAM_RE = /^(?:__revisionId|isDesignMode|weaverse)/;
|
|
1132
|
-
/**
|
|
1133
|
-
* Normalize the storefront URL sent in the Builder page API request body.
|
|
1134
|
-
*
|
|
1135
|
-
* The POST body participates in Hydrogen's cached fetch key, so a raw
|
|
1136
|
-
* `request.url` fragments the cache by every marketing/tracking param:
|
|
1137
|
-
* each distinct `?utm_…`/`fbclid` URL is a guaranteed Builder round-trip
|
|
1138
|
-
* even when the resolved page is identical — precisely the paid/social
|
|
1139
|
-
* traffic that needs the cache most (builder#2450).
|
|
1140
|
-
*
|
|
1141
|
-
* The Builder reads only the pathname (locale/type/handle fallback) and
|
|
1142
|
-
* the params matched above from this URL, so keep exactly those. Params
|
|
1143
|
-
* are sorted for a stable cache key regardless of their order in the
|
|
1144
|
-
* incoming URL.
|
|
1145
|
-
*/
|
|
1146
694
|
function normalizePageUrl(url) {
|
|
1147
695
|
let parsed;
|
|
1148
696
|
try {
|
|
@@ -1159,41 +707,18 @@ function normalizePageUrl(url) {
|
|
|
1159
707
|
}
|
|
1160
708
|
//#endregion
|
|
1161
709
|
//#region src/weaverse-client.ts
|
|
1162
|
-
/**
|
|
1163
|
-
* Cache duration constants (in seconds).
|
|
1164
|
-
* These values optimize CDN hit rates while maintaining fresh content.
|
|
1165
|
-
*/
|
|
1166
710
|
const CACHE_DURATIONS = {
|
|
1167
|
-
/** Freshness for content that can change in Builder */
|
|
1168
711
|
SHORT: 60,
|
|
1169
|
-
/** Long stale window so slow Builder responses do not block storefront rendering */
|
|
1170
712
|
LONG: 86400
|
|
1171
713
|
};
|
|
1172
|
-
/**
|
|
1173
|
-
* Network timeout for fetch requests (in milliseconds).
|
|
1174
|
-
* Default is 10 seconds to balance reliability and user experience.
|
|
1175
|
-
*/
|
|
1176
714
|
const DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
1177
|
-
/**
|
|
1178
|
-
* Bounded retry for the public API fetch. A single slow/cold origin response,
|
|
1179
|
-
* a transient 5xx, or a network blip would otherwise make loadPage return null
|
|
1180
|
-
* — rendering the "Please add new section" placeholder or throwing a 404 in
|
|
1181
|
-
* the route. Two attempts (one retry) is enough: by the time the first attempt
|
|
1182
|
-
* times out, the edge proxy's single-flight leader has usually populated its
|
|
1183
|
-
* versioned cache, so the retry lands on a warm HIT.
|
|
1184
|
-
*/
|
|
1185
715
|
const MAX_FETCH_ATTEMPTS = 2;
|
|
1186
716
|
const FETCH_RETRY_BACKOFF_MS = 300;
|
|
1187
|
-
/**
|
|
1188
|
-
* Retry only transient failures: request timeouts, network errors, and origin
|
|
1189
|
-
* 5xx. A 4xx is deterministic (e.g. a genuine 404) — retrying just wastes time.
|
|
1190
|
-
*/
|
|
1191
717
|
function isRetryableFetchError(error) {
|
|
1192
718
|
if (!(error instanceof WeaverseError)) return false;
|
|
1193
719
|
if (error.code === "TIMEOUT_ERROR" || error.code === "NETWORK_ERROR" || error.code === "INVALID_RESPONSE") return true;
|
|
1194
720
|
return error.code === "FETCH_ERROR" && (error.statusCode ?? 0) >= 500;
|
|
1195
721
|
}
|
|
1196
|
-
/** Parses a JSON body, or returns the raw text when it is not JSON. */
|
|
1197
722
|
function parseResponseText(text) {
|
|
1198
723
|
if (!text) return text;
|
|
1199
724
|
try {
|
|
@@ -1205,31 +730,14 @@ function parseResponseText(text) {
|
|
|
1205
730
|
function isPlainObject(value) {
|
|
1206
731
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1207
732
|
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Whether a successful `/project` body is a page envelope.
|
|
1210
|
-
*
|
|
1211
|
-
* `page: null` is Builder's authoritative "nothing assigned at this URL" and
|
|
1212
|
-
* stays valid; anything without a `page` field, with an `error` field, or with
|
|
1213
|
-
* a page that has no item list is not an answer about this URL at all.
|
|
1214
|
-
*/
|
|
1215
733
|
function isProjectEnvelope(payload) {
|
|
1216
734
|
if (!isPlainObject(payload) || hasError(payload) || !("page" in payload)) return false;
|
|
1217
735
|
const { page, project } = payload;
|
|
1218
736
|
return (page === null || isPlainObject(page) && Array.isArray(page.items)) && (project == null || isPlainObject(project));
|
|
1219
737
|
}
|
|
1220
|
-
/**
|
|
1221
|
-
* Whether a successful `/project_configs` body carries theme settings.
|
|
1222
|
-
*
|
|
1223
|
-
* Builder always sends the `theme` field; `null` means the merchant never
|
|
1224
|
-
* saved settings, so schema defaults are the real configuration.
|
|
1225
|
-
*/
|
|
1226
738
|
function isThemeConfigsEnvelope(payload) {
|
|
1227
739
|
return isPlainObject(payload) && !hasError(payload) && "theme" in payload && (payload.theme === null || isPlainObject(payload.theme));
|
|
1228
740
|
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Classifies a page-load failure. `'absent'` is Builder's 404 for a project
|
|
1231
|
-
* that does not exist, which keeps resolving to `null` like before.
|
|
1232
|
-
*/
|
|
1233
741
|
function classifyPageLoadFailure(error) {
|
|
1234
742
|
if (error instanceof WeaverseLoadError) return error.kind;
|
|
1235
743
|
if (!(error instanceof WeaverseError)) return "invalid-response";
|
|
@@ -1242,24 +750,7 @@ function classifyPageLoadFailure(error) {
|
|
|
1242
750
|
}
|
|
1243
751
|
return "configuration";
|
|
1244
752
|
}
|
|
1245
|
-
/**
|
|
1246
|
-
* Validators for Builder requests made by this client itself, keyed on the
|
|
1247
|
-
* exact options object (same reasoning as `bodyFreeCacheRequests`). On the
|
|
1248
|
-
* cached path such a request rejects a non-2xx status instead of resolving
|
|
1249
|
-
* to `null`, and only stores a body its validator accepts. Every other
|
|
1250
|
-
* `fetchWithCache` caller keeps the existing behavior.
|
|
1251
|
-
*/
|
|
1252
753
|
const builderPayloadValidators = /* @__PURE__ */ new WeakMap();
|
|
1253
|
-
/**
|
|
1254
|
-
* Gives a rejection from Hydrogen's cached fetch the code `fetchOnce` gives
|
|
1255
|
-
* the same failure on the direct path.
|
|
1256
|
-
*
|
|
1257
|
-
* Hydrogen calls the platform `fetch` itself, so a request that never got a
|
|
1258
|
-
* response rejects with the platform's error instead of a `WeaverseError`.
|
|
1259
|
-
* The Fetch standard rejects a network failure with a `TypeError` and an
|
|
1260
|
-
* aborted request with the signal's `AbortError`/`TimeoutError`; those are
|
|
1261
|
-
* outages. Anything else is returned unchanged, so it is not reported as one.
|
|
1262
|
-
*/
|
|
1263
754
|
function cachedTransportError(error, url) {
|
|
1264
755
|
if (error instanceof WeaverseError || !(error instanceof Error)) return error;
|
|
1265
756
|
if (error.name === "AbortError" || error.name === "TimeoutError") return new WeaverseError("Request timed out", "TIMEOUT_ERROR", void 0, { url });
|
|
@@ -1269,89 +760,33 @@ function cachedTransportError(error, url) {
|
|
|
1269
760
|
});
|
|
1270
761
|
return error;
|
|
1271
762
|
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Weaverse API version path
|
|
1274
|
-
*/
|
|
1275
763
|
const API_PATH = "v1";
|
|
1276
|
-
/**
|
|
1277
|
-
* Cache strategy for Weaverse API requests: fresh for a minute, then served
|
|
1278
|
-
* stale for a day while it refreshes or while Builder errors.
|
|
1279
|
-
*
|
|
1280
|
-
* Every Builder resource uses it. Hydrogen 2026.4's subrequest cache only read
|
|
1281
|
-
* `max-age` for freshness, so the old per-resource `sMaxAge` values never
|
|
1282
|
-
* changed how long an entry stayed fresh.
|
|
1283
|
-
*/
|
|
1284
764
|
const DEFAULT_CACHE_STRATEGY = Cache({
|
|
1285
765
|
maxAge: CACHE_DURATIONS.SHORT,
|
|
1286
766
|
staleWhileRevalidate: CACHE_DURATIONS.LONG,
|
|
1287
767
|
staleIfError: CACHE_DURATIONS.LONG
|
|
1288
768
|
});
|
|
1289
|
-
/**
|
|
1290
|
-
* Options objects created by the internal `project_configs` request whose
|
|
1291
|
-
* subrequest cache identity deliberately excludes the body. Theme settings
|
|
1292
|
-
* carry the storefront origin so Builder can attribute hostnames; hashing the
|
|
1293
|
-
* body would give every domain of one project its own entry.
|
|
1294
|
-
*
|
|
1295
|
-
* The SUCCESSFUL theme payload is the same for every storefront of a project,
|
|
1296
|
-
* which is what makes one shared entry correct. This is not a claim that
|
|
1297
|
-
* Builder can never answer differently per host: a hostname-blocked request
|
|
1298
|
-
* returns a 403 `{ error }` body, and `shouldCacheResponse` rejects anything
|
|
1299
|
-
* `hasError()` matches, so a refusal is never stored under (or served from)
|
|
1300
|
-
* the shared entry. If a future response shape ever varies by host WITHOUT an
|
|
1301
|
-
* `error` field, this exclusion has to be revisited.
|
|
1302
|
-
*
|
|
1303
|
-
* Keyed on the exact options object, NOT on `cacheTarget`: `fetchWithCache`
|
|
1304
|
-
* is public API, so gating on the target would silently collapse the cache of
|
|
1305
|
-
* any consumer who selects `theme-settings` with varying bodies. An outside
|
|
1306
|
-
* caller's options object is never in this set and keeps a body-derived key.
|
|
1307
|
-
*
|
|
1308
|
-
* The WeakSet-membership slot in the cache key (`has(options)`) is the actual
|
|
1309
|
-
* discriminator — no caller-supplied value can reach it. The sentinel is not
|
|
1310
|
-
* load-bearing for that; it only keeps the omitted body legible in cache
|
|
1311
|
-
* debugging instead of a bare `undefined`.
|
|
1312
|
-
*/
|
|
1313
769
|
const bodyFreeCacheRequests = /* @__PURE__ */ new WeakSet();
|
|
1314
770
|
const BODY_FREE_CACHE_SENTINEL = "\0weaverse:body-free";
|
|
1315
|
-
/**
|
|
1316
|
-
* Request-scoped client for loading Weaverse pages and theme settings in Hydrogen.
|
|
1317
|
-
*
|
|
1318
|
-
* Create one client from the Hydrogen app context, then call
|
|
1319
|
-
* {@link WeaverseClient.loadPage} from route loaders and
|
|
1320
|
-
* {@link WeaverseClient.loadThemeSettings} from the root loader.
|
|
1321
|
-
*/
|
|
1322
771
|
var WeaverseClient = class {
|
|
1323
|
-
/** Builder API version used by this client. */
|
|
1324
772
|
API = API_PATH;
|
|
1325
|
-
/** Stable project configuration shared by page requests. */
|
|
1326
773
|
basePageConfigs;
|
|
1327
|
-
/** Stable request body fields shared by page requests. */
|
|
1328
774
|
basePageRequestBody;
|
|
1329
|
-
/** Resolved project and rendering-mode configuration. */
|
|
1330
775
|
configs;
|
|
1331
|
-
/** Hydrogen cached fetch used for Builder API requests. */
|
|
1332
776
|
cachedFetch;
|
|
1333
|
-
/** Incoming request associated with this client instance. */
|
|
1334
777
|
request;
|
|
1335
|
-
/** Customer Account API client, when the storefront passes one. */
|
|
1336
778
|
customerAccount;
|
|
1337
|
-
/** Hydrogen Storefront API client and locale context. */
|
|
1338
779
|
storefront;
|
|
1339
|
-
/** Components registered for page rendering and component loaders. */
|
|
1340
780
|
components;
|
|
1341
|
-
/** Theme schema used for defaults and Studio editing. */
|
|
1342
781
|
themeSchema;
|
|
1343
|
-
/** Runtime environment variables. */
|
|
1344
782
|
env;
|
|
1345
|
-
/** Cache storage supplied by the Hydrogen runtime. */
|
|
1346
783
|
cache;
|
|
1347
|
-
/** Background execution hook supplied by the runtime. */
|
|
1348
784
|
waitUntil;
|
|
1349
785
|
parsedUrl;
|
|
1350
786
|
componentsByType;
|
|
1351
787
|
locale;
|
|
1352
788
|
fetchTimeoutMs;
|
|
1353
789
|
throwPageLoadFailures;
|
|
1354
|
-
/** Creates a request-scoped client from the storefront's request context. */
|
|
1355
790
|
constructor(args) {
|
|
1356
791
|
this.request = args.request;
|
|
1357
792
|
this.storefront = args.storefront;
|
|
@@ -1369,10 +804,6 @@ var WeaverseClient = class {
|
|
|
1369
804
|
this.initializeCache(args);
|
|
1370
805
|
this.initializeConfigs(args);
|
|
1371
806
|
}
|
|
1372
|
-
/**
|
|
1373
|
-
* Initialize Hydrogen cache with proper configuration.
|
|
1374
|
-
* Logs warning if waitUntil is missing.
|
|
1375
|
-
*/
|
|
1376
807
|
initializeCache(args) {
|
|
1377
808
|
if (!args.waitUntil) console.warn("⚠️ WeaverseClient: waitUntil not provided, background operations may not complete");
|
|
1378
809
|
this.cachedFetch = createFetchWithCache({
|
|
@@ -1380,10 +811,6 @@ var WeaverseClient = class {
|
|
|
1380
811
|
waitUntil: args.waitUntil
|
|
1381
812
|
});
|
|
1382
813
|
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Initialize configs with projectId resolution and fail-fast validation.
|
|
1385
|
-
* Throws WeaverseError if projectId cannot be resolved.
|
|
1386
|
-
*/
|
|
1387
814
|
initializeConfigs(args) {
|
|
1388
815
|
const baseConfigs = getWeaverseConfigs(args.request, args.env);
|
|
1389
816
|
const resolvedProjectId = this.resolveProjectIdSync(args.projectId, args.request, args.env, baseConfigs);
|
|
@@ -1411,30 +838,9 @@ var WeaverseClient = class {
|
|
|
1411
838
|
Object.freeze(this.basePageConfigs);
|
|
1412
839
|
Object.freeze(this.basePageRequestBody);
|
|
1413
840
|
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Build component lookup map for O(1) access by type.
|
|
1416
|
-
* Significantly improves performance for large component lists.
|
|
1417
|
-
*/
|
|
1418
841
|
buildComponentLookupMap(components) {
|
|
1419
842
|
return new Map(components.map((comp) => [comp.schema?.type, comp]));
|
|
1420
843
|
}
|
|
1421
|
-
/**
|
|
1422
|
-
* Extract comprehensive error information from unknown error type.
|
|
1423
|
-
* Preserves error context, codes, and stack traces for debugging.
|
|
1424
|
-
*
|
|
1425
|
-
* @param error - Unknown error to extract information from
|
|
1426
|
-
* @returns Structured error information with message, code, stack, and context
|
|
1427
|
-
*
|
|
1428
|
-
* @example
|
|
1429
|
-
* ```typescript
|
|
1430
|
-
* try {
|
|
1431
|
-
* // ... operation
|
|
1432
|
-
* } catch (error) {
|
|
1433
|
-
* const errorInfo = this.extractErrorInfo(error)
|
|
1434
|
-
* console.error('Operation failed:', errorInfo.message, errorInfo.context)
|
|
1435
|
-
* }
|
|
1436
|
-
* ```
|
|
1437
|
-
*/
|
|
1438
844
|
extractErrorInfo(error) {
|
|
1439
845
|
if (error instanceof WeaverseError) return {
|
|
1440
846
|
message: error.message,
|
|
@@ -1448,22 +854,9 @@ var WeaverseClient = class {
|
|
|
1448
854
|
};
|
|
1449
855
|
return { message: String(error) };
|
|
1450
856
|
}
|
|
1451
|
-
/**
|
|
1452
|
-
* Extract error message only (for backward compatibility).
|
|
1453
|
-
* Use extractErrorInfo() for more detailed error information.
|
|
1454
|
-
*
|
|
1455
|
-
* @deprecated Use extractErrorInfo() instead to preserve error context
|
|
1456
|
-
*/
|
|
1457
857
|
extractErrorMessage(error) {
|
|
1458
858
|
return this.extractErrorInfo(error).message;
|
|
1459
859
|
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Validates projectId format (alphanumeric, hyphens, underscores only).
|
|
1462
|
-
* Returns detailed validation result with error message.
|
|
1463
|
-
*
|
|
1464
|
-
* @param projectId - Project identifier to validate
|
|
1465
|
-
* @returns Validation result with error message if invalid
|
|
1466
|
-
*/
|
|
1467
860
|
validateProjectId(projectId) {
|
|
1468
861
|
if (!projectId || projectId.trim() === "") return {
|
|
1469
862
|
valid: false,
|
|
@@ -1475,30 +868,12 @@ var WeaverseClient = class {
|
|
|
1475
868
|
};
|
|
1476
869
|
return { valid: true };
|
|
1477
870
|
}
|
|
1478
|
-
/**
|
|
1479
|
-
* Resolves projectId with multi-project architecture support.
|
|
1480
|
-
* Uses priority chain to determine the correct project ID.
|
|
1481
|
-
*
|
|
1482
|
-
* @param projectId - Project identifier (string, function, or undefined)
|
|
1483
|
-
* @param request - HTTP request for context access
|
|
1484
|
-
* @param env - Hydrogen environment with variables
|
|
1485
|
-
* @param baseConfigs - Pre-fetched base configs to avoid duplicate calls
|
|
1486
|
-
* @returns Resolved project ID string (empty string if none found)
|
|
1487
|
-
*/
|
|
1488
871
|
resolveProjectIdSync(projectId, request, env, baseConfigs) {
|
|
1489
872
|
return this.resolveFromQueryParams(request) || this.resolveFromFunction(projectId) || this.resolveFromString(projectId) || this.resolveFromEnvironment(baseConfigs, request, env) || "";
|
|
1490
873
|
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Priority 1: Resolve projectId from URL query parameters.
|
|
1493
|
-
* Used for design mode and preview mode overrides.
|
|
1494
|
-
*/
|
|
1495
874
|
resolveFromQueryParams(request) {
|
|
1496
875
|
return getRequestQueries(request).weaverseProjectId || null;
|
|
1497
876
|
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Priority 2: Resolve projectId from function parameter.
|
|
1500
|
-
* Detects and rejects async functions with helpful error.
|
|
1501
|
-
*/
|
|
1502
877
|
resolveFromFunction(projectId) {
|
|
1503
878
|
if (!isProjectIdFunction(projectId)) return null;
|
|
1504
879
|
if (projectId.constructor.name === "AsyncFunction") throw new WeaverseError("Async projectId functions must be awaited before passing to WeaverseClient. Use: projectId: await getProjectAsync() instead of: projectId: getProjectAsync", "ASYNC_PROJECT_ID");
|
|
@@ -1517,9 +892,6 @@ var WeaverseClient = class {
|
|
|
1517
892
|
}
|
|
1518
893
|
return null;
|
|
1519
894
|
}
|
|
1520
|
-
/**
|
|
1521
|
-
* Priority 3: Resolve projectId from explicit string parameter.
|
|
1522
|
-
*/
|
|
1523
895
|
resolveFromString(projectId) {
|
|
1524
896
|
if (typeof projectId !== "string") return null;
|
|
1525
897
|
const trimmed = projectId.trim();
|
|
@@ -1528,18 +900,11 @@ var WeaverseClient = class {
|
|
|
1528
900
|
if (validation.valid) return trimmed;
|
|
1529
901
|
throw new WeaverseError(validation.error || "Invalid projectId format", "INVALID_PROJECT_ID");
|
|
1530
902
|
}
|
|
1531
|
-
/**
|
|
1532
|
-
* Priority 4: Resolve projectId from environment variables.
|
|
1533
|
-
*/
|
|
1534
903
|
resolveFromEnvironment(baseConfigs, request, env) {
|
|
1535
904
|
const configs = baseConfigs || getWeaverseConfigs(request, env);
|
|
1536
905
|
if (configs.projectId && configs.projectId.trim() !== "") return configs.projectId.trim();
|
|
1537
906
|
return null;
|
|
1538
907
|
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Helper method for direct fetching in design mode.
|
|
1541
|
-
* Includes 10-second timeout to prevent hanging requests.
|
|
1542
|
-
*/
|
|
1543
908
|
fetchOnce = async (url, options, timeoutMs) => {
|
|
1544
909
|
const controller = new AbortController();
|
|
1545
910
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -1599,23 +964,6 @@ var WeaverseClient = class {
|
|
|
1599
964
|
const { weaverseApiBase, weaverseHost } = this.configs;
|
|
1600
965
|
return weaverseApiBase !== weaverseHost && url.startsWith(`${weaverseApiBase}/api/public/`);
|
|
1601
966
|
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Fetch data with caching support for both design mode and production.
|
|
1604
|
-
* Uses direct fetch in design mode, Hydrogen's cached fetch in production.
|
|
1605
|
-
* Automatically includes projectId in cache key for multi-project isolation.
|
|
1606
|
-
*
|
|
1607
|
-
* @param url - The URL to fetch from
|
|
1608
|
-
* @param options - Fetch options including optional cache strategy
|
|
1609
|
-
* @returns Promise resolving to the fetched data
|
|
1610
|
-
*
|
|
1611
|
-
* @example
|
|
1612
|
-
* ```typescript
|
|
1613
|
-
* const data = await weaverse.fetchWithCache<MyData>(
|
|
1614
|
-
* 'https://api.example.com/data',
|
|
1615
|
-
* { method: 'POST', body: JSON.stringify({ key: 'value' }) }
|
|
1616
|
-
* )
|
|
1617
|
-
* ```
|
|
1618
|
-
*/
|
|
1619
967
|
fetchWithCache = async (url, options = {}) => {
|
|
1620
968
|
const { cacheTarget, strategy: strategyOverride, ...fetchOptions } = options;
|
|
1621
969
|
const { body } = fetchOptions;
|
|
@@ -1654,30 +1002,11 @@ var WeaverseClient = class {
|
|
|
1654
1002
|
}
|
|
1655
1003
|
return parseResponseText(text);
|
|
1656
1004
|
};
|
|
1657
|
-
/**
|
|
1658
|
-
* Safe origin of the incoming storefront request: scheme, host and explicit
|
|
1659
|
-
* port. Userinfo, path, query and fragment are dropped, so no customer
|
|
1660
|
-
* route or credential-bearing value leaves the storefront. Returns undefined
|
|
1661
|
-
* for a non-http(s) request so the field is simply omitted.
|
|
1662
|
-
*/
|
|
1663
1005
|
safeStorefrontOrigin() {
|
|
1664
1006
|
const { protocol, host } = this.parsedUrl;
|
|
1665
1007
|
if (protocol !== "http:" && protocol !== "https:") return;
|
|
1666
1008
|
return host ? `${protocol}//${host}` : void 0;
|
|
1667
1009
|
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Load theme settings from Weaverse API with fallback to schema defaults.
|
|
1670
|
-
* Merges remote settings with local theme schema and handles design mode serialization.
|
|
1671
|
-
*
|
|
1672
|
-
* @param strategy - Optional cache strategy override
|
|
1673
|
-
* @returns Promise resolving to theme settings response
|
|
1674
|
-
*
|
|
1675
|
-
* @example
|
|
1676
|
-
* ```typescript
|
|
1677
|
-
* const settings = await weaverse.loadThemeSettings()
|
|
1678
|
-
* const primaryColor = settings.theme.colors?.primary
|
|
1679
|
-
* ```
|
|
1680
|
-
*/
|
|
1681
1010
|
loadThemeSettings = async (strategy) => {
|
|
1682
1011
|
const defaultThemeSettings = generateDataFromSchema(this.themeSchema);
|
|
1683
1012
|
try {
|
|
@@ -1743,11 +1072,6 @@ var WeaverseClient = class {
|
|
|
1743
1072
|
};
|
|
1744
1073
|
}
|
|
1745
1074
|
};
|
|
1746
|
-
/**
|
|
1747
|
-
* Fetch locale-specific merchant overrides from the translation API.
|
|
1748
|
-
* Returns a nested JSON object with translated values.
|
|
1749
|
-
* Falls back to empty object on error to avoid blocking theme rendering.
|
|
1750
|
-
*/
|
|
1751
1075
|
fetchMerchantOverrides = async (strategy) => {
|
|
1752
1076
|
try {
|
|
1753
1077
|
const { projectId, weaverseHost } = this.configs;
|
|
@@ -1769,13 +1093,6 @@ var WeaverseClient = class {
|
|
|
1769
1093
|
return;
|
|
1770
1094
|
}
|
|
1771
1095
|
};
|
|
1772
|
-
/**
|
|
1773
|
-
* Generate fallback page with default structure when page data is unavailable.
|
|
1774
|
-
* Used internally when API returns no page or when page needs default content.
|
|
1775
|
-
*
|
|
1776
|
-
* @param message - Custom message to display in the fallback page
|
|
1777
|
-
* @returns WeaversePageData with single main component containing the message
|
|
1778
|
-
*/
|
|
1779
1096
|
generateFallbackPage = (message) => {
|
|
1780
1097
|
const rootId = crypto.randomUUID();
|
|
1781
1098
|
return {
|
|
@@ -1789,10 +1106,6 @@ var WeaverseClient = class {
|
|
|
1789
1106
|
}]
|
|
1790
1107
|
};
|
|
1791
1108
|
};
|
|
1792
|
-
/**
|
|
1793
|
-
* Fetch all published custom pages for sitemap generation.
|
|
1794
|
-
* Paginates automatically, never throws, returns accumulated results on partial failure.
|
|
1795
|
-
*/
|
|
1796
1109
|
async fetchCustomPages(opts) {
|
|
1797
1110
|
const { weaverseApiBase, projectId } = this.configs;
|
|
1798
1111
|
const entries = [];
|
|
@@ -1820,34 +1133,6 @@ var WeaverseClient = class {
|
|
|
1820
1133
|
console.warn("[Weaverse] Custom pages pagination cap reached (100 iterations)");
|
|
1821
1134
|
return entries;
|
|
1822
1135
|
}
|
|
1823
|
-
/**
|
|
1824
|
-
* Load page data from Weaverse API with optional route-level project override.
|
|
1825
|
-
* Fetches page content, executes component loaders, and handles empty items fallback.
|
|
1826
|
-
*
|
|
1827
|
-
* @param params - Optional page loading parameters
|
|
1828
|
-
* @param params.type - Page type (INDEX, PAGE, PRODUCT, COLLECTION, etc.)
|
|
1829
|
-
* @param params.handle - Page handle for dynamic routes
|
|
1830
|
-
* @param params.locale - Locale override
|
|
1831
|
-
* @param params.projectId - Route-level project ID override (for multi-project)
|
|
1832
|
-
* @param params.strategy - Custom cache strategy override
|
|
1833
|
-
* @returns Promise resolving to page data or null on error
|
|
1834
|
-
*
|
|
1835
|
-
* @example
|
|
1836
|
-
* ```typescript
|
|
1837
|
-
* // Basic usage
|
|
1838
|
-
* const data = await weaverse.loadPage()
|
|
1839
|
-
*
|
|
1840
|
-
* // With page type
|
|
1841
|
-
* const data = await weaverse.loadPage({ type: 'INDEX' })
|
|
1842
|
-
*
|
|
1843
|
-
* // Route-level project override
|
|
1844
|
-
* const data = await weaverse.loadPage({
|
|
1845
|
-
* type: 'PAGE',
|
|
1846
|
-
* handle: 'about',
|
|
1847
|
-
* projectId: 'campaign-project-123'
|
|
1848
|
-
* })
|
|
1849
|
-
* ```
|
|
1850
|
-
*/
|
|
1851
1136
|
loadPage = async (params = {}) => {
|
|
1852
1137
|
if (params.projectId !== void 0) {
|
|
1853
1138
|
const message = typeof params.projectId === "string" ? this.validateProjectId(params.projectId).error : `must be a string. Received: ${typeof params.projectId}`;
|
|
@@ -1953,7 +1238,6 @@ var WeaverseClient = class {
|
|
|
1953
1238
|
throw new WeaverseLoadError("Unable to load Weaverse page", failure, error instanceof WeaverseError ? error.statusCode : void 0);
|
|
1954
1239
|
}
|
|
1955
1240
|
};
|
|
1956
|
-
/** Executes the registered loader for one component and attaches its result. */
|
|
1957
1241
|
execComponentLoader = async (item) => {
|
|
1958
1242
|
const { data = {}, type, id } = item;
|
|
1959
1243
|
const component = this.componentsByType.get(type);
|
|
@@ -1977,6 +1261,6 @@ var WeaverseClient = class {
|
|
|
1977
1261
|
};
|
|
1978
1262
|
};
|
|
1979
1263
|
//#endregion
|
|
1980
|
-
export {
|
|
1264
|
+
export { useTranslation as _, registerComponent as a, createWeaverseDataContext as c, getSelectedProductOptions$1 as d, formatMetaDescriptors as f, useThemeText as g, TranslationProvider as h, WeaverseHydrogenRoot as i, useWeaverseDataContext as l, ThemeTextProvider as m, WeaverseHydrogen as n, useThemeSettings as o, getWeaverseSeoMeta as p, WeaverseHydrogenItem as r, withWeaverse as s, WeaverseClient as t, defaultComponents as u };
|
|
1981
1265
|
|
|
1982
|
-
//# sourceMappingURL=weaverse-client-
|
|
1266
|
+
//# sourceMappingURL=weaverse-client-CXnM9ayH.mjs.map
|