@weaverse/next 0.1.0-alpha.13 → 0.1.0-alpha.15

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 (106) hide show
  1. package/README.md +125 -23
  2. package/dist/{chunk-MQ6N5ODA.mjs → chunk-NL22OVD7.mjs} +93 -4
  3. package/dist/chunk-NL22OVD7.mjs.map +1 -0
  4. package/dist/index.js +86 -5
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +20 -27
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/server.js +158 -2
  9. package/dist/server.js.map +1 -1
  10. package/dist/server.mjs +126 -5
  11. package/dist/server.mjs.map +1 -1
  12. package/dist/types/client.d.ts +16 -0
  13. package/dist/types/client.d.ts.map +1 -0
  14. package/dist/types/hooks.d.ts +20 -0
  15. package/dist/types/hooks.d.ts.map +1 -0
  16. package/dist/types/index.d.ts +34 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/types/item.d.ts +54 -0
  19. package/dist/types/item.d.ts.map +1 -0
  20. package/dist/types/loader.d.ts +25 -0
  21. package/dist/types/loader.d.ts.map +1 -0
  22. package/dist/types/main.d.ts +11 -0
  23. package/dist/types/main.d.ts.map +1 -0
  24. package/dist/types/provider.d.ts +82 -0
  25. package/dist/types/provider.d.ts.map +1 -0
  26. package/dist/types/registry.d.ts +15 -0
  27. package/dist/types/registry.d.ts.map +1 -0
  28. package/dist/types/renderer.d.ts +20 -0
  29. package/dist/types/renderer.d.ts.map +1 -0
  30. package/dist/types/request-info.d.ts +8 -0
  31. package/dist/types/request-info.d.ts.map +1 -0
  32. package/dist/types/revalidate-item.d.ts +81 -0
  33. package/dist/types/revalidate-item.d.ts.map +1 -0
  34. package/dist/types/root-provider.d.ts +61 -0
  35. package/dist/types/root-provider.d.ts.map +1 -0
  36. package/dist/types/runtime.d.ts +113 -0
  37. package/dist/types/runtime.d.ts.map +1 -0
  38. package/dist/types/seo.d.ts +55 -0
  39. package/dist/types/seo.d.ts.map +1 -0
  40. package/dist/types/server/configs.d.ts +27 -0
  41. package/dist/types/server/configs.d.ts.map +1 -0
  42. package/dist/types/server/normalize-page-url.d.ts +19 -0
  43. package/dist/types/server/normalize-page-url.d.ts.map +1 -0
  44. package/dist/types/server/revalidate-handler.d.ts +55 -0
  45. package/dist/types/server/revalidate-handler.d.ts.map +1 -0
  46. package/dist/types/server/server-client.d.ts +21 -0
  47. package/dist/types/server/server-client.d.ts.map +1 -0
  48. package/dist/types/server.d.ts +19 -0
  49. package/dist/types/server.d.ts.map +1 -0
  50. package/dist/types/studio-bridge.d.ts +16 -0
  51. package/dist/types/studio-bridge.d.ts.map +1 -0
  52. package/dist/types/studio-connect.d.ts +27 -0
  53. package/dist/types/studio-connect.d.ts.map +1 -0
  54. package/dist/types/studio-router.d.ts +50 -0
  55. package/dist/types/studio-router.d.ts.map +1 -0
  56. package/dist/types/studio-script-src.d.ts +12 -0
  57. package/dist/types/studio-script-src.d.ts.map +1 -0
  58. package/dist/types/theme-settings-store.d.ts +17 -0
  59. package/dist/types/theme-settings-store.d.ts.map +1 -0
  60. package/dist/types/translation-context.d.ts +156 -0
  61. package/dist/types/translation-context.d.ts.map +1 -0
  62. package/dist/types/translation-store.d.ts +44 -0
  63. package/dist/types/translation-store.d.ts.map +1 -0
  64. package/dist/types/types.d.ts +661 -0
  65. package/dist/types/types.d.ts.map +1 -0
  66. package/dist/types/use-weaverse-next-studio.d.ts +34 -0
  67. package/dist/types/use-weaverse-next-studio.d.ts.map +1 -0
  68. package/dist/types/utils.d.ts +12 -0
  69. package/dist/types/utils.d.ts.map +1 -0
  70. package/package.json +7 -6
  71. package/src/client.ts +101 -0
  72. package/src/hooks.ts +50 -0
  73. package/src/index.ts +153 -0
  74. package/src/item.ts +151 -0
  75. package/src/loader.ts +135 -0
  76. package/src/main.tsx +45 -0
  77. package/src/provider.tsx +225 -0
  78. package/src/registry.ts +63 -0
  79. package/src/renderer.tsx +99 -0
  80. package/src/request-info.ts +95 -0
  81. package/src/revalidate-item.ts +210 -0
  82. package/src/root-provider.tsx +181 -0
  83. package/src/runtime.ts +521 -0
  84. package/src/seo.ts +157 -0
  85. package/src/server/configs.ts +182 -0
  86. package/src/server/normalize-page-url.ts +65 -0
  87. package/src/server/revalidate-handler.ts +357 -0
  88. package/src/server/server-client.ts +727 -0
  89. package/src/server.ts +67 -0
  90. package/src/studio-bridge.tsx +82 -0
  91. package/src/studio-connect.tsx +88 -0
  92. package/src/studio-router.ts +85 -0
  93. package/src/studio-script-src.ts +128 -0
  94. package/src/theme-settings-store.ts +46 -0
  95. package/src/translation-context.tsx +299 -0
  96. package/src/translation-store.ts +69 -0
  97. package/src/types.ts +748 -0
  98. package/src/use-weaverse-next-studio.tsx +106 -0
  99. package/src/utils.ts +37 -0
  100. package/dist/chunk-MQ6N5ODA.mjs.map +0 -1
  101. package/dist/index.d.mts +0 -592
  102. package/dist/index.d.ts +0 -592
  103. package/dist/seo-CrEFl_Up.d.mts +0 -516
  104. package/dist/seo-CrEFl_Up.d.ts +0 -516
  105. package/dist/server.d.mts +0 -100
  106. package/dist/server.d.ts +0 -100
package/README.md CHANGED
@@ -170,15 +170,26 @@ Main exports:
170
170
 
171
171
  Example helper for a Next route/server component boundary:
172
172
 
173
+ This helper is app-level (not exported by `@weaverse/next`); it plays the role
174
+ of Hydrogen's global load context, but the app owns route identity explicitly.
175
+ The App Router boundary supplies `pathname`, `pageType`, and `handle`, so the
176
+ same identity reaches page loads and per-item revalidation loaders. It uses the
177
+ exported `PageType` and `WeaverseNextRequestContext` types. The module defines
178
+ both helpers used by the route-handler example below:
179
+
173
180
  ```ts
174
181
  import { createWeaverseNextServerClient } from '@weaverse/next/server'
182
+ import type { PageType, WeaverseNextRequestContext } from '@weaverse/next'
175
183
  import { headers } from 'next/headers'
176
184
  import { serverComponents } from './server-components'
177
185
  import { getStaticStorefrontClient } from './storefront'
178
186
 
179
187
  export async function getWeaverseServerClient(
180
188
  searchParamsPromise: Promise<Record<string, string | string[] | undefined>>,
181
- pathname = '/'
189
+ pathname = '/',
190
+ // Route identity the App Router segment owns. Seeding it here means loaders
191
+ // resolve the same page type / handle during SSR and per-item revalidation.
192
+ identity: { pageType?: PageType; handle?: string } = {}
182
193
  ) {
183
194
  let [headersList, rawSearchParams] = await Promise.all([
184
195
  headers(),
@@ -202,6 +213,27 @@ export async function getWeaverseServerClient(
202
213
  }`
203
214
  )
204
215
 
216
+ let requestContext: WeaverseNextRequestContext = {
217
+ url,
218
+ headers: requestHeaders,
219
+ searchParams,
220
+ pathname,
221
+ pageType: identity.pageType,
222
+ handle: identity.handle,
223
+ i18n: { country: 'US', language: 'EN', locale: 'en-US' },
224
+ }
225
+
226
+ return createWeaverseServerClientFromContext(requestContext)
227
+ }
228
+
229
+ // Explicit-context path used by the revalidation callback. The context has
230
+ // already been validated by the SDK handler; project/host/env remain app-owned.
231
+ export function createWeaverseServerClientFromContext(
232
+ requestContext: WeaverseNextRequestContext
233
+ ) {
234
+ // A localized app should select/cache this client from its own supported
235
+ // market allowlist using requestContext.i18n. This compact example uses the
236
+ // default market configured by the app helper.
205
237
  let storefront = getStaticStorefrontClient()
206
238
 
207
239
  return createWeaverseNextServerClient({
@@ -220,26 +252,36 @@ export async function getWeaverseServerClient(
220
252
  },
221
253
  }
222
254
  : undefined,
223
- requestContext: {
224
- url,
225
- headers: requestHeaders,
226
- searchParams,
227
- pathname,
228
- i18n: { country: 'US', language: 'EN', locale: 'en-US' },
229
- },
255
+ requestContext,
230
256
  cache: { revalidate: 60, tags: ['weaverse'] },
231
257
  })
232
258
  }
233
259
  ```
234
260
 
235
- Then each route can stay close to the Hydrogen mental model:
261
+ Then each route stays close to the Hydrogen mental model while supplying the
262
+ route identity the segment owns — an `INDEX` home route and a handle-bearing
263
+ `PRODUCT` route:
236
264
 
237
265
  ```ts
238
- let weaverse = await getWeaverseServerClient(props.searchParams, '/')
266
+ // app/page.tsx — the INDEX segment owns the page type.
267
+ let weaverse = await getWeaverseServerClient(props.searchParams, '/', {
268
+ pageType: 'INDEX',
269
+ })
239
270
  let data = await weaverse.loadPage({ type: 'INDEX' })
240
271
  let theme = await weaverse.loadThemeSettings()
241
272
  ```
242
273
 
274
+ ```ts
275
+ // app/products/[handle]/page.tsx — the PRODUCT segment supplies the handle.
276
+ let { handle } = await props.params
277
+ let weaverse = await getWeaverseServerClient(
278
+ props.searchParams,
279
+ `/products/${handle}`,
280
+ { pageType: 'PRODUCT', handle }
281
+ )
282
+ let data = await weaverse.loadPage({ type: 'PRODUCT', handle })
283
+ ```
284
+
243
285
  ### Server client members
244
286
 
245
287
  | Member | Description |
@@ -349,16 +391,18 @@ import {
349
391
  WeaverseNextProvider,
350
392
  WeaverseNextRenderer,
351
393
  } from '@weaverse/next'
352
- import type { WeaverseNextLoaderData } from '@weaverse/next'
394
+ import type {
395
+ WeaverseNextLoaderData,
396
+ WeaverseNextRequestInfo,
397
+ } from '@weaverse/next'
353
398
  import { components } from './components'
354
399
 
355
400
  export function WeaversePage({ data }: { data: WeaverseNextLoaderData }) {
401
+ // The server client returns `requestInfo` in the exported shape, so restore
402
+ // the full route identity — including `pageType` and `handle` — into the
403
+ // client request context instead of a narrow inline subset.
356
404
  let requestInfo = data.configs?.requestInfo as
357
- | {
358
- i18n?: { country?: string; language?: string; locale?: string }
359
- pathname?: string
360
- search?: string
361
- }
405
+ | WeaverseNextRequestInfo
362
406
  | undefined
363
407
  let projectId =
364
408
  (data.configs?.projectId as string | undefined) ?? data.project?.id
@@ -374,6 +418,8 @@ export function WeaversePage({ data }: { data: WeaverseNextLoaderData }) {
374
418
  isPreviewMode: Boolean(data.configs?.isPreviewMode),
375
419
  isRevisionPreview: Boolean(data.configs?.isRevisionPreview),
376
420
  i18n: requestInfo?.i18n,
421
+ pageType: requestInfo?.pageType,
422
+ handle: requestInfo?.handle,
377
423
  },
378
424
  })
379
425
 
@@ -492,13 +538,38 @@ The root script connector and page-level renderer are both required for full Stu
492
538
 
493
539
  Resource-picker edits should refresh only the affected item's server loader data. The happy path should not call `router.refresh()` because refreshing the whole RSC tree can remount the page and reset scroll.
494
540
 
495
- Mount the route handler in the consuming app:
541
+ Mount the route handler in the consuming app. `getClient` receives a second
542
+ argument — the validated, same-origin route context reconstructed from the
543
+ request body — so the revalidated loader runs with the same pathname, locale,
544
+ page type, handle, and ordinary search params as the active storefront route.
545
+ The two server-client helpers below are app-level consumer helpers, not exports
546
+ from `@weaverse/next`:
496
547
 
497
548
  ```ts
498
549
  // app/api/weaverse/revalidate/route.ts
499
550
  import { createWeaverseNextRevalidateHandler } from '@weaverse/next/server'
500
- import { getWeaverseServerClient } from '../../../weaverse-next/server'
551
+ import {
552
+ createWeaverseServerClientFromContext,
553
+ getWeaverseServerClient,
554
+ } from '../../../weaverse-next/server'
555
+
556
+ export const { POST } = createWeaverseNextRevalidateHandler({
557
+ // `requestContext` is validated browser input for route identity only.
558
+ // Project ID, Studio host, API base/key, and env must still come from
559
+ // server config — never from this context — and it is `undefined` for
560
+ // legacy request bodies that carry no route context.
561
+ getClient: (_request, requestContext) =>
562
+ requestContext
563
+ ? createWeaverseServerClientFromContext(requestContext)
564
+ : getWeaverseServerClient(Promise.resolve({})),
565
+ })
566
+ ```
501
567
 
568
+ The existing one-argument callback still works. A handler mounted with a
569
+ `(request) => client` callback ignores the optional second argument and keeps
570
+ compiling and running, so old wiring needs no change:
571
+
572
+ ```ts
502
573
  export const { POST } = createWeaverseNextRevalidateHandler({
503
574
  getClient: () => getWeaverseServerClient(Promise.resolve({})),
504
575
  })
@@ -508,15 +579,46 @@ Flow:
508
579
 
509
580
  ```text
510
581
  Studio resource picker edit
511
- -> Builder calls runtime.internal.revalidateItem(draftItem)
512
- -> SDK POSTs { draftItem } to /api/weaverse/revalidate
582
+ -> Builder calls runtime.internal.revalidateItem(draftItem) (unchanged)
583
+ -> SDK snapshots runtime.requestInfo, strips server-owned/transient controls
584
+ -> SDK POSTs { draftItem, routeContext? } to /api/weaverse/revalidate
585
+ -> handler validates + sanitizes routeContext at the trust boundary
586
+ -> handler reconstructs a same-origin WeaverseNextRequestContext
587
+ -> getClient(request, requestContext) builds the route-aware server client
513
588
  -> route handler finds the registered component by draftItem.type
514
- -> handler runs that component's loader with draft item data
515
- -> response returns { loaderData }
589
+ -> handler runs that component's loader with draft data + client.requestContext
590
+ -> response returns { loaderData } with Cache-Control: no-store
516
591
  -> SDK applies loaderData to the live item instance in place
517
592
  ```
518
593
 
519
- If the route is missing or returns an error, Builder falls back to the older route-refresh path.
594
+ If the route is missing or returns an error, Builder falls back to the older
595
+ route-refresh path.
596
+
597
+ ### Trust boundary
598
+
599
+ The revalidation endpoint is public and every JSON field is attacker-controlled.
600
+ The SDK sanitizes `routeContext` on the client for clean traffic, but the route
601
+ handler is the security boundary and re-validates independently:
602
+
603
+ - Only `pathname`, sanitized `search`, a narrow i18n subset, a
604
+ `PageTypeSchema`-valid `pageType`, and a bounded `handle` cross the boundary.
605
+ Headers, cookies, auth, env, project ID, Studio host, API base/key, commerce
606
+ clients, the runtime, and the client are never serialized.
607
+ - Server-owned controls (`weaverseProjectId`, `weaverseHost`, `weaverseApiKey`,
608
+ `weaverseApiBase`, `weaversePublicApiBase`, `weaverseVersion`, `projectId`) and
609
+ transient transport controls (`weaverseDraftItem`, `__weaverseDraftItem`,
610
+ `_rsc`) are stripped case-insensitively on both sides, so a crafted body cannot
611
+ influence server config resolution.
612
+ - The origin is fixed from the endpoint request before assigning any
613
+ browser-provided pathname/search, so input cannot change protocol, host, port,
614
+ or credentials. `pathname` and `url.pathname` share one URL-canonicalized value.
615
+ - A malformed present `routeContext` returns `400 { error: 'invalid-route-context' }`
616
+ before `getClient` runs; a missing `routeContext` is valid legacy input and
617
+ passes `undefined` to the callback.
618
+ - Route context is routing input, never an authorization decision. A registered
619
+ loader must not treat pathname, handle, page type, locale, or search as proof
620
+ of access; customer/private data still requires independent server-side session
621
+ and authorization checks derived from the actual request.
520
622
 
521
623
  ## Config resolution
522
624
 
@@ -1,3 +1,5 @@
1
+ import { PageTypeSchema } from '@weaverse/schema';
2
+
1
3
  // src/utils.ts
2
4
  function generateDataFromSchema(schema) {
3
5
  let data = {};
@@ -154,8 +156,95 @@ function buildWeaverseNextRequestInfo(context) {
154
156
  pathname: getPathnameFromContext(context),
155
157
  search: search ? `?${search}` : "",
156
158
  queries: toQueryRecord(searchParams),
157
- ...context?.i18n ? { i18n: context.i18n } : {}
159
+ ...context?.i18n ? { i18n: context.i18n } : {},
160
+ ...context?.pageType ? { pageType: context.pageType } : {},
161
+ ...context?.handle ? { handle: context.handle } : {}
162
+ };
163
+ }
164
+ var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
165
+ var MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512;
166
+ var DENIED_ROUTE_CONTEXT_PARAMS = [
167
+ "weaverseprojectid",
168
+ "weaversehost",
169
+ "weaverseapikey",
170
+ "weaverseapibase",
171
+ "weaversepublicapibase",
172
+ "weaverseversion",
173
+ "projectid",
174
+ "weaversedraftitem",
175
+ "__weaversedraftitem",
176
+ "_rsc"
177
+ ];
178
+ var ROUTE_CONTEXT_I18N_KEYS = [
179
+ "country",
180
+ "label",
181
+ "language",
182
+ "locale",
183
+ "pathPrefix"
184
+ ];
185
+ function sanitizeRouteContextSearch(search) {
186
+ let params = new URLSearchParams(search);
187
+ let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS);
188
+ for (let key of [...new Set(params.keys())]) {
189
+ if (denied.has(key.toLowerCase())) {
190
+ params.delete(key);
191
+ }
192
+ }
193
+ let serialized = params.toString();
194
+ return serialized ? `?${serialized}` : "";
195
+ }
196
+ function buildWeaverseNextRevalidateRouteContext(requestInfo) {
197
+ if (!requestInfo || typeof requestInfo.pathname !== "string" || !requestInfo.pathname) {
198
+ return;
199
+ }
200
+ let routeContext = {
201
+ pathname: requestInfo.pathname,
202
+ search: sanitizeRouteContextSearch(requestInfo.search ?? "")
158
203
  };
204
+ let { i18n } = requestInfo;
205
+ if (i18n && typeof i18n === "object") {
206
+ let picked = {};
207
+ for (let key of ROUTE_CONTEXT_I18N_KEYS) {
208
+ let value = i18n[key];
209
+ if (typeof value === "string") {
210
+ picked[key] = value;
211
+ }
212
+ }
213
+ if (Object.keys(picked).length) {
214
+ routeContext.i18n = picked;
215
+ }
216
+ }
217
+ if (requestInfo.pageType && PageTypeSchema.safeParse(requestInfo.pageType).success) {
218
+ routeContext.pageType = requestInfo.pageType;
219
+ }
220
+ if (typeof requestInfo.handle === "string" && requestInfo.handle && requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH) {
221
+ routeContext.handle = requestInfo.handle;
222
+ }
223
+ return routeContext;
224
+ }
225
+ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
226
+ let instance = runtime.itemInstances.get(draftItem.id);
227
+ if (!instance) {
228
+ throw new Error(
229
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
230
+ );
231
+ }
232
+ let routeContext = buildWeaverseNextRevalidateRouteContext(
233
+ runtime.requestInfo
234
+ );
235
+ let body = routeContext ? { draftItem, routeContext } : { draftItem };
236
+ let response = await fetch(endpoint, {
237
+ method: "POST",
238
+ headers: { "Content-Type": "application/json" },
239
+ body: JSON.stringify(body)
240
+ });
241
+ if (!response.ok) {
242
+ throw new Error(
243
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
244
+ );
245
+ }
246
+ let { loaderData } = await response.json();
247
+ instance.setData({ loaderData: loaderData ?? null });
159
248
  }
160
249
 
161
250
  // src/seo.ts
@@ -223,6 +312,6 @@ function getWeaverseNextSeoMetadata(data) {
223
312
  return formatWeaverseNextSeoMetadata(page?.seo ?? null);
224
313
  }
225
314
 
226
- export { buildWeaverseNextRequestInfo, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, runWeaverseComponentLoaders };
227
- //# sourceMappingURL=chunk-MQ6N5ODA.mjs.map
228
- //# sourceMappingURL=chunk-MQ6N5ODA.mjs.map
315
+ export { DEFAULT_REVALIDATE_ENDPOINT, ROUTE_CONTEXT_I18N_KEYS, buildWeaverseNextRequestInfo, buildWeaverseNextRevalidateRouteContext, formatWeaverseNextSeoMetadata, generateDataFromSchema, getWeaverseNextSeoMetadata, revalidateWeaverseNextItem, runWeaverseComponentLoaders, sanitizeRouteContextSearch };
316
+ //# sourceMappingURL=chunk-NL22OVD7.mjs.map
317
+ //# sourceMappingURL=chunk-NL22OVD7.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/loader.ts","../src/request-info.ts","../src/revalidate-item.ts","../src/seo.ts"],"names":[],"mappings":"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","file":"chunk-NL22OVD7.mjs","sourcesContent":["import type { SchemaType } from '@weaverse/schema'\nimport type { WeaverseNextThemeSchema } from './types'\n\n/**\n * Generate default component data from a schema's `settings` inputs.\n *\n * Mirrors Hydrogen's `generateDataFromSchema` (settings → `{ [name]:\n * defaultValue }`) but is kept local so `@weaverse/next` doesn't depend on\n * `@weaverse/hydrogen`. Falls back to the deprecated `inspector` array when a\n * schema predates `settings`.\n */\nexport function generateDataFromSchema(\n schema: SchemaType | WeaverseNextThemeSchema | undefined\n): Record<string, unknown> {\n let data: Record<string, unknown> = {}\n if (!schema) {\n return data\n }\n\n let groups =\n (schema.settings as {\n inputs?: { name?: string; defaultValue?: unknown }[]\n }[]) ??\n (schema as { inspector?: typeof groups }).inspector ??\n []\n\n for (let group of groups) {\n for (let input of group?.inputs ?? []) {\n let { name, defaultValue } = input\n if (name && defaultValue !== null && defaultValue !== undefined) {\n data[name] = defaultValue\n }\n }\n }\n\n return data\n}\n","import type {\n WeaverseNextClient,\n WeaverseNextCommerceContext,\n WeaverseNextComponent,\n WeaverseNextComponentData,\n WeaverseNextLoaderData,\n WeaverseNextRequestContext,\n} from './types'\nimport { generateDataFromSchema } from './utils'\n\n/** Inputs for running registered component loaders over a page tree. */\nexport interface RunComponentLoadersArgs {\n /** Weaverse client containing the component registry. */\n client: WeaverseNextClient\n /** Request-scoped commerce override; defaults to `client.commerce`. */\n commerce?: WeaverseNextCommerceContext\n /** Request-context override; defaults to `client.requestContext`. */\n context?: WeaverseNextRequestContext\n /** Page payload to walk; defaults to `client.data`. */\n data?: WeaverseNextLoaderData | null\n}\n\nfunction isItemNode(node: unknown): node is WeaverseNextComponentData {\n return (\n typeof node === 'object' &&\n node !== null &&\n typeof (node as WeaverseNextComponentData).type === 'string'\n )\n}\n\nfunction cloneItem(item: WeaverseNextComponentData): WeaverseNextComponentData {\n return {\n ...item,\n children: Array.isArray(item.children)\n ? item.children.map((child) =>\n isItemNode(child) ? cloneItem(child) : { ...child }\n )\n : item.children,\n }\n}\n\n/**\n * Walk a serialized page tree and run each registered component's `loader`,\n * attaching the result to the item as `loaderData`.\n *\n * Loaders receive explicit `commerce.storefront` (and `weaverse.storefront` as\n * a compatibility alias via the client). Children are walked recursively when\n * they are inline item objects; flat `items` arrays are covered by the\n * top-level walk.\n *\n * @returns a cloned `data` object with `loaderData` attached to cloned items.\n */\nexport async function runWeaverseComponentLoaders(\n args: RunComponentLoadersArgs\n): Promise<WeaverseNextLoaderData | null> {\n let { client, context, commerce } = args\n let sourceData = args.data ?? client.data\n if (!sourceData) {\n return null\n }\n if (!sourceData.page.items.length) {\n return sourceData\n }\n\n let data: WeaverseNextLoaderData = {\n ...sourceData,\n page: {\n ...sourceData.page,\n items: sourceData.page.items.map(cloneItem),\n },\n }\n let items = data.page.items\n\n let componentMap = new Map<string, WeaverseNextComponent>()\n for (let component of client.components) {\n if (component?.schema?.type) {\n componentMap.set(component.schema.type, component)\n }\n }\n\n let visited = new Set<string>()\n let resolvedContext = context ?? client.requestContext\n let resolvedCommerce = commerce ?? client.commerce\n\n // Per-call `weaverse` alias. When a request-scoped `commerce` is passed, the\n // `weaverse.storefront` compatibility alias must resolve to *that* storefront,\n // not the client's construction-time commerce. Pilot-style loaders call\n // `weaverse.storefront.query(...)`, so a stale alias would query the wrong\n // (or an undefined) storefront. When no override is given we reuse the client\n // as-is to preserve its prototype getters.\n let resolvedWeaverse: WeaverseNextClient =\n resolvedCommerce === client.commerce\n ? client\n : {\n ...client,\n commerce: resolvedCommerce,\n storefront: resolvedCommerce?.storefront,\n }\n\n async function walk(nodes: WeaverseNextComponentData[]) {\n for (let item of nodes) {\n if (!item || visited.has(item.id)) {\n continue\n }\n visited.add(item.id)\n\n let component = componentMap.get(item.type)\n if (component?.loader) {\n try {\n item.loaderData = await component.loader({\n data: { ...generateDataFromSchema(component.schema), ...item.data },\n weaverse: resolvedWeaverse,\n context: resolvedContext,\n commerce: resolvedCommerce,\n })\n } catch (error) {\n console.warn('❌ Item loader run failed.', item.type, item.id, error)\n }\n }\n\n let children = item.children\n if (Array.isArray(children)) {\n let inlineChildren = children.filter(\n isItemNode\n ) as WeaverseNextComponentData[]\n if (inlineChildren.length) {\n await walk(inlineChildren)\n }\n }\n }\n }\n\n await walk(items)\n return data\n}\n","import type {\n WeaverseNextRequestContext,\n WeaverseNextRequestInfo,\n} from './types'\n\nfunction toUrl(value: string | URL): URL {\n return typeof value === 'string' ? new URL(value, 'http://localhost') : value\n}\n\nfunction getSearchParamsFromContext(\n context?: WeaverseNextRequestContext\n): URLSearchParams {\n if (context?.searchParams) {\n return new URLSearchParams(context.searchParams)\n }\n\n if (context?.url) {\n let url = toUrl(context.url)\n return new URLSearchParams(url.searchParams)\n }\n\n return new URLSearchParams()\n}\n\nfunction getPathnameFromContext(context?: WeaverseNextRequestContext): string {\n if (context?.pathname) {\n return context.pathname\n }\n\n if (context?.url) {\n let url = toUrl(context.url)\n return url.pathname || '/'\n }\n\n return '/'\n}\n\nfunction coerceQueryValue(value: string): string | boolean {\n if (value === 'true') {\n return true\n }\n if (value === 'false') {\n return false\n }\n return value\n}\n\nfunction toQueryRecord(\n searchParams: URLSearchParams\n): Record<string, string | boolean> {\n let queries: Record<string, string | boolean> = {}\n for (let [key, value] of searchParams.entries()) {\n queries[key] = coerceQueryValue(value)\n }\n return queries\n}\n\nconst TRANSIENT_STUDIO_PARAMS = new Set([\n 'weaverseDraftItem',\n // Legacy name from the original design-mode draft-item spike. Keep it out of\n // runtime requestInfo too, in case an older Builder bundle is in front of a\n // newer Next storefront.\n '__weaverseDraftItem',\n])\n\nfunction getRequestInfoSearchParams(\n context?: WeaverseNextRequestContext\n): URLSearchParams {\n let searchParams = new URLSearchParams(getSearchParamsFromContext(context))\n for (let key of TRANSIENT_STUDIO_PARAMS) {\n searchParams.delete(key)\n }\n return searchParams\n}\n\n/**\n * Build the Hydrogen-like request info shape consumed by the Studio bridge.\n * Duplicate query keys keep the last value, matching `URLSearchParams` iteration\n * assignment and Hydrogen's practical query-object behavior.\n */\nexport function buildWeaverseNextRequestInfo(\n context?: WeaverseNextRequestContext\n): WeaverseNextRequestInfo {\n let searchParams = getRequestInfoSearchParams(context)\n let search = searchParams.toString()\n\n return {\n pathname: getPathnameFromContext(context),\n search: search ? `?${search}` : '',\n queries: toQueryRecord(searchParams),\n ...(context?.i18n ? { i18n: context.i18n } : {}),\n ...(context?.pageType ? { pageType: context.pageType } : {}),\n ...(context?.handle ? { handle: context.handle } : {}),\n }\n}\n","import type { PageType } from '@weaverse/schema'\nimport { PageTypeSchema } from '@weaverse/schema'\nimport type {\n WeaverseNextComponentData,\n WeaverseNextI18n,\n WeaverseNextRequestInfo,\n} from './types'\n\n/** Default App Router endpoint used for Studio item-loader revalidation. */\nexport const DEFAULT_REVALIDATE_ENDPOINT = '/api/weaverse/revalidate'\n\nconst MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512\n\n/**\n * Server-owned and transient controls stripped from the revalidation route\n * context on both sides of the trust boundary. Compared case-insensitively, so\n * every entry is stored lowercased. Client sanitization keeps normal traffic\n * clean; the route handler repeats this deny-list as the security boundary.\n *\n * - Server-owned: a crafted `?weaverseProjectId=`/`?weaverseHost=`/`?weaverseApiKey=`\n * must never influence server config resolution (see `getWeaverseNextConfigs`).\n * - Transient: draft-item and RSC transport params never describe route identity.\n */\nexport const DENIED_ROUTE_CONTEXT_PARAMS = [\n 'weaverseprojectid',\n 'weaversehost',\n 'weaverseapikey',\n 'weaverseapibase',\n 'weaversepublicapibase',\n 'weaverseversion',\n 'projectid',\n 'weaversedraftitem',\n '__weaversedraftitem',\n '_rsc',\n]\n\n/** i18n fields allowed to cross the browser boundary. */\nexport const ROUTE_CONTEXT_I18N_KEYS = [\n 'country',\n 'label',\n 'language',\n 'locale',\n 'pathPrefix',\n] as const\n\n/**\n * JSON-safe route subset that may cross the browser boundary alongside the\n * draft item. Never carries headers, cookies, auth, env, project ID, Studio\n * host, API keys/bases, commerce clients, the request context, runtime, or\n * client. `search` is canonical (preserves duplicate keys); the derived\n * `queries` record is intentionally omitted.\n */\nexport interface WeaverseNextRevalidateRouteContext {\n /** Route handle used for page assignment. */\n handle?: string\n /** Public locale and market identity allowed across the browser boundary. */\n i18n?: Pick<\n WeaverseNextI18n,\n 'country' | 'label' | 'language' | 'locale' | 'pathPrefix'\n >\n /** Weaverse page type resolved by the active route. */\n pageType?: PageType\n /** Canonical same-origin route pathname. */\n pathname: string\n /** Sanitized serialized query string, including the leading `?` when set. */\n search: string\n}\n\n/**\n * Minimal runtime surface the in-place apply needs. Structural (not the\n * concrete `WeaverseNextRuntime` class) so unit tests can drive it with a\n * plain mock. `requestInfo` stays optional: a real `WeaverseNextRuntime`\n * always supplies it, but when absent the client omits `routeContext` and the\n * legacy `{ draftItem }` request is preserved.\n */\nexport interface RevalidateItemRuntimeLike {\n /** Live item stores keyed by Weaverse item ID. */\n itemInstances: Map<\n string,\n {\n /** Merge refreshed loader data into the live item store. */\n setData: (update: Record<string, unknown>) => unknown\n }\n >\n /** Active route identity used to construct the optional revalidation payload. */\n requestInfo?: WeaverseNextRequestInfo\n}\n\n/**\n * Strip denied (server-owned/transient) controls from a `search` string and\n * re-serialize it. Preserves ordinary application queries, duplicate keys,\n * encoded Unicode, and mode controls (`isDesignMode`, `isPreviewMode`,\n * `__revisionId`, `sectionType`). Returns `''` or a `?`-prefixed string.\n */\nexport function sanitizeRouteContextSearch(search: string): string {\n let params = new URLSearchParams(search)\n let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS)\n for (let key of [...new Set(params.keys())]) {\n if (denied.has(key.toLowerCase())) {\n params.delete(key)\n }\n }\n let serialized = params.toString()\n return serialized ? `?${serialized}` : ''\n}\n\n/**\n * Build the sanitized {@link WeaverseNextRevalidateRouteContext} from the\n * active runtime's request info. Returns `undefined` without a usable pathname\n * so the caller omits `routeContext` and keeps the legacy request. This is not\n * the security boundary — the route handler re-validates and re-sanitizes.\n */\nexport function buildWeaverseNextRevalidateRouteContext(\n requestInfo?: WeaverseNextRequestInfo\n): WeaverseNextRevalidateRouteContext | undefined {\n if (\n !requestInfo ||\n typeof requestInfo.pathname !== 'string' ||\n !requestInfo.pathname\n ) {\n return\n }\n\n let routeContext: WeaverseNextRevalidateRouteContext = {\n pathname: requestInfo.pathname,\n search: sanitizeRouteContextSearch(requestInfo.search ?? ''),\n }\n\n let { i18n } = requestInfo\n if (i18n && typeof i18n === 'object') {\n let picked: Record<string, string> = {}\n for (let key of ROUTE_CONTEXT_I18N_KEYS) {\n let value = i18n[key]\n if (typeof value === 'string') {\n picked[key] = value\n }\n }\n if (Object.keys(picked).length) {\n routeContext.i18n = picked\n }\n }\n\n if (\n requestInfo.pageType &&\n PageTypeSchema.safeParse(requestInfo.pageType).success\n ) {\n routeContext.pageType = requestInfo.pageType\n }\n\n if (\n typeof requestInfo.handle === 'string' &&\n requestInfo.handle &&\n requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH\n ) {\n routeContext.handle = requestInfo.handle\n }\n\n return routeContext\n}\n\n/**\n * Studio per-item loader revalidation: POST the unsaved draft item (and, when\n * the runtime exposes route context, a sanitized `routeContext`) to the app's\n * revalidate route (see `createWeaverseNextRevalidateHandler`) and apply the\n * returned `loaderData` to the live item instance in place.\n *\n * No `router.refresh()` is involved, so the RSC tree is never re-applied — no\n * page remount, no scroll reset, and the draft payload never touches the URL.\n *\n * Throws on any failure (missing route, non-OK response, unknown item) so the\n * Builder Studio bridge can detect the miss and fall back to its legacy\n * refresh-based revalidation for this window.\n */\nexport async function revalidateWeaverseNextItem(\n runtime: RevalidateItemRuntimeLike,\n draftItem: WeaverseNextComponentData,\n endpoint: string = DEFAULT_REVALIDATE_ENDPOINT\n): Promise<void> {\n let instance = runtime.itemInstances.get(draftItem.id)\n if (!instance) {\n throw new Error(\n `[weaverse-next] No live item instance for \"${draftItem.id}\".`\n )\n }\n\n // Snapshot route identity at revalidation start so a later navigation cannot\n // retarget this in-flight request. Omit `routeContext` for legacy runtimes.\n let routeContext = buildWeaverseNextRevalidateRouteContext(\n runtime.requestInfo\n )\n let body = routeContext ? { draftItem, routeContext } : { draftItem }\n\n let response = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n throw new Error(\n `[weaverse-next] Revalidate endpoint responded ${response.status}.`\n )\n }\n\n let { loaderData } = (await response.json()) as { loaderData?: unknown }\n // The item's settings were already mutated by the Studio edit; only the\n // loader payload needs applying. `setData` merges and notifies subscribers,\n // and the new `loaderData` identity is what Builder's revalidation poll\n // observes for completion.\n instance.setData({ loaderData: loaderData ?? null })\n}\n","import type { PageSEOData } from '@weaverse/schema'\nimport type { Metadata } from 'next'\nimport type { WeaverseNextLoaderData, WeaverseNextPageData } from './types'\n\n/** Object containing an optional Weaverse page for SEO extraction. */\nexport interface WeaverseNextSeoPageContainer {\n /** Page whose published SEO fields should be formatted. */\n page?: WeaverseNextPageData | null\n}\n\n/** Input accepted by {@link getWeaverseNextSeoMetadata}. */\nexport type WeaverseNextSeoInput =\n | WeaverseNextLoaderData\n | WeaverseNextPageData\n | WeaverseNextSeoPageContainer\n | null\n | undefined\n\n/** Open Graph metadata accepted by Next.js's `Metadata` API. */\nexport type NextOpenGraph = NonNullable<Metadata['openGraph']>\n\n/** Twitter card metadata accepted by Next.js's `Metadata` API. */\nexport type NextTwitter = NonNullable<Metadata['twitter']>\n\n/**\n * Weaverse page SEO expressed as a subset of Next's `Metadata`. Assignable to\n * `Metadata` as-is: `const metadata: Metadata = getWeaverseNextSeoMetadata(data)`.\n */\nexport interface WeaverseNextSeoMetadata {\n /** Canonical URL settings for Next's metadata API. */\n alternates?: {\n /** Preferred URL for search indexing. */\n canonical?: string\n }\n /** Page meta description. */\n description?: string\n /** Page meta keywords. */\n keywords?: string\n /** Open Graph metadata normalized to Next's supported union. */\n openGraph?: NextOpenGraph\n /** Search crawler indexing and link-following directives. */\n robots: {\n /** Whether crawlers may follow links on the page. */\n follow: boolean\n /** Whether crawlers may index the page. */\n index: boolean\n }\n /** Page title. */\n title?: string\n /** Twitter card metadata normalized for Next's metadata API. */\n twitter?: NextTwitter\n}\n\nfunction hasOpenGraphContent(seo: PageSEOData['openGraph']): boolean {\n return Boolean(seo?.title || seo?.description || seo?.image || seo?.type)\n}\n\nfunction hasTwitterContent(seo: PageSEOData['twitter']): boolean {\n return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType)\n}\n\nfunction normalizeTwitterCard(\n cardType: NonNullable<PageSEOData['twitter']>['cardType']\n): 'summary' | 'summary_large_image' {\n return cardType === 'summary_large_image' ? 'summary_large_image' : 'summary'\n}\n\n/**\n * Map a Builder Open Graph type onto Next's `OpenGraphType` union. Every\n * Builder value except `product` exists in Next's union and is preserved\n * verbatim; `product` is not modeled by Next and makes `generateMetadata()`\n * throw, so it degrades to `website` — the closest renderable equivalent.\n */\nfunction normalizeOpenGraphType(\n type: NonNullable<PageSEOData['openGraph']>['type']\n): 'article' | 'profile' | 'video.other' | 'website' {\n if (type === 'article' || type === 'profile' || type === 'video.other') {\n return type\n }\n return 'website'\n}\n\n/**\n * Convert Builder SEO fields into metadata accepted by Next.js.\n *\n * Unsupported Open Graph product types and incomplete Twitter app/player\n * cards are safely downgraded to renderable Next.js variants.\n */\nexport function formatWeaverseNextSeoMetadata(\n seo: PageSEOData | null | undefined\n): WeaverseNextSeoMetadata {\n let index = seo?.robots?.index !== false\n let follow = seo?.robots?.follow !== false\n let metadata: WeaverseNextSeoMetadata = {\n robots: { index, follow },\n }\n\n if (!seo) {\n return metadata\n }\n\n if (seo.title) {\n metadata.title = seo.title\n }\n if (seo.description) {\n metadata.description = seo.description\n }\n if (seo.keywords) {\n metadata.keywords = seo.keywords\n }\n if (seo.canonicalUrl) {\n metadata.alternates = { canonical: seo.canonicalUrl }\n }\n\n let openGraph = seo.openGraph\n if (hasOpenGraphContent(openGraph)) {\n metadata.openGraph = {\n title: openGraph?.title,\n description: openGraph?.description,\n images: openGraph?.image ? [openGraph.image] : undefined,\n type: normalizeOpenGraphType(openGraph?.type),\n }\n }\n\n let twitter = seo.twitter\n if (hasTwitterContent(twitter)) {\n // Builder does not collect the descriptors Next requires for `app` cards\n // (Next throws when they are missing) or `player` cards (which render an\n // empty, useless card without a player URL and dimensions). Downgrade both\n // to `summary` rather than emitting metadata Next cannot render.\n metadata.twitter = {\n card: normalizeTwitterCard(twitter?.cardType),\n title: twitter?.title,\n description: twitter?.description,\n images: twitter?.image ? [twitter.image] : undefined,\n }\n }\n\n return metadata\n}\n\n/**\n * Read page SEO from loader data or a page payload and format it for Next.js\n * `generateMetadata`. Missing data returns indexable/followable robot defaults.\n */\nexport function getWeaverseNextSeoMetadata(\n data: WeaverseNextSeoInput\n): WeaverseNextSeoMetadata {\n if (!data) {\n return formatWeaverseNextSeoMetadata(null)\n }\n let page =\n 'items' in data && 'id' in data\n ? (data as WeaverseNextPageData)\n : ((data as WeaverseNextSeoPageContainer).page ?? null)\n return formatWeaverseNextSeoMetadata(page?.seo ?? null)\n}\n"]}
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ var WeaverseNextItem = class extends react.WeaverseItemStore {
54
54
  _cachedSnapshot = null;
55
55
  _snapshotStoreRef = null;
56
56
  _snapshotTranslationRef = null;
57
+ /** Create an item store from serialized component data and its runtime. */
57
58
  constructor(initialData, weaverse) {
58
59
  super(initialData, weaverse);
59
60
  let schemaData = generateDataFromSchema(this.Element?.schema);
@@ -319,18 +320,24 @@ var useThemeText = useTranslation;
319
320
 
320
321
  // src/translation-store.ts
321
322
  var TranslationStore = class {
323
+ /** Current flat translation overrides keyed by dot path. */
322
324
  overrides = {};
323
325
  listeners = /* @__PURE__ */ new Set();
326
+ /** Replace every override and notify subscribers. */
324
327
  setOverrides = (overrides) => {
325
328
  this.overrides = { ...overrides };
326
329
  this.emit();
327
330
  };
331
+ /** Merge changed overrides into the current values and notify subscribers. */
328
332
  updateOverrides = (newOverrides) => {
329
333
  this.overrides = { ...this.overrides, ...newOverrides };
330
334
  this.emit();
331
335
  };
336
+ /** Return the current client-side override snapshot. */
332
337
  getSnapshot = () => this.overrides;
338
+ /** Return the override snapshot used during server rendering. */
333
339
  getServerSnapshot = () => this.overrides;
340
+ /** Subscribe to override changes and return an unsubscribe function. */
334
341
  subscribe = (callback) => {
335
342
  this.listeners.add(callback);
336
343
  return () => {
@@ -648,7 +655,9 @@ function buildWeaverseNextRequestInfo(context) {
648
655
  pathname: getPathnameFromContext(context),
649
656
  search: search ? `?${search}` : "",
650
657
  queries: toQueryRecord(searchParams),
651
- ...context?.i18n ? { i18n: context.i18n } : {}
658
+ ...context?.i18n ? { i18n: context.i18n } : {},
659
+ ...context?.pageType ? { pageType: context.pageType } : {},
660
+ ...context?.handle ? { handle: context.handle } : {}
652
661
  };
653
662
  }
654
663
 
@@ -714,11 +723,17 @@ function rebindPageItemsToRuntime(runtime) {
714
723
  }
715
724
  }
716
725
  var WeaverseNextRuntime = class extends react.Weaverse {
726
+ /** Stable ID of the page owned by this runtime. */
717
727
  pageId;
728
+ /** Mutable callbacks and stores consumed by the Studio bridge. */
718
729
  internal;
730
+ /** Path, query, and locale metadata for Studio navigation. */
719
731
  requestInfo;
732
+ /** Whether this runtime renders a component preview. */
720
733
  isPreviewMode;
734
+ /** Whether this runtime renders a saved revision. */
721
735
  isRevisionPreview;
736
+ /** Component type rendered in section preview mode. */
722
737
  sectionType;
723
738
  // ─── Item-level translation sidecar (design mode only) ─────────────
724
739
  /**
@@ -727,7 +742,9 @@ var WeaverseNextRuntime = class extends react.Weaverse {
727
742
  * components, so translations render without mutating the base `_store`.
728
743
  */
729
744
  translationMap = {};
745
+ /** Locale associated with the item-level translation sidecar. */
730
746
  translationLocale = String();
747
+ /** Weaverse language ID used when saving translation changes. */
731
748
  translationLanguageId = String();
732
749
  /**
733
750
  * Fresh page data captured while React was rendering. Runtime reuse happens
@@ -745,6 +762,7 @@ var WeaverseNextRuntime = class extends react.Weaverse {
745
762
  * deferred to `flushRenderPhaseUpdates()`.
746
763
  */
747
764
  pendingItemUpdates = [];
765
+ /** Create a browser runtime from a loaded page and its request context. */
748
766
  constructor(config) {
749
767
  let { client, data } = config;
750
768
  let page = getRenderablePage(data);
@@ -966,9 +984,67 @@ function bindWeaverseNextStudioRuntime(runtime) {
966
984
  });
967
985
  return true;
968
986
  }
969
-
970
- // src/revalidate-item.ts
971
987
  var DEFAULT_REVALIDATE_ENDPOINT = "/api/weaverse/revalidate";
988
+ var MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512;
989
+ var DENIED_ROUTE_CONTEXT_PARAMS = [
990
+ "weaverseprojectid",
991
+ "weaversehost",
992
+ "weaverseapikey",
993
+ "weaverseapibase",
994
+ "weaversepublicapibase",
995
+ "weaverseversion",
996
+ "projectid",
997
+ "weaversedraftitem",
998
+ "__weaversedraftitem",
999
+ "_rsc"
1000
+ ];
1001
+ var ROUTE_CONTEXT_I18N_KEYS = [
1002
+ "country",
1003
+ "label",
1004
+ "language",
1005
+ "locale",
1006
+ "pathPrefix"
1007
+ ];
1008
+ function sanitizeRouteContextSearch(search) {
1009
+ let params = new URLSearchParams(search);
1010
+ let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS);
1011
+ for (let key of [...new Set(params.keys())]) {
1012
+ if (denied.has(key.toLowerCase())) {
1013
+ params.delete(key);
1014
+ }
1015
+ }
1016
+ let serialized = params.toString();
1017
+ return serialized ? `?${serialized}` : "";
1018
+ }
1019
+ function buildWeaverseNextRevalidateRouteContext(requestInfo) {
1020
+ if (!requestInfo || typeof requestInfo.pathname !== "string" || !requestInfo.pathname) {
1021
+ return;
1022
+ }
1023
+ let routeContext = {
1024
+ pathname: requestInfo.pathname,
1025
+ search: sanitizeRouteContextSearch(requestInfo.search ?? "")
1026
+ };
1027
+ let { i18n } = requestInfo;
1028
+ if (i18n && typeof i18n === "object") {
1029
+ let picked = {};
1030
+ for (let key of ROUTE_CONTEXT_I18N_KEYS) {
1031
+ let value = i18n[key];
1032
+ if (typeof value === "string") {
1033
+ picked[key] = value;
1034
+ }
1035
+ }
1036
+ if (Object.keys(picked).length) {
1037
+ routeContext.i18n = picked;
1038
+ }
1039
+ }
1040
+ if (requestInfo.pageType && schema.PageTypeSchema.safeParse(requestInfo.pageType).success) {
1041
+ routeContext.pageType = requestInfo.pageType;
1042
+ }
1043
+ if (typeof requestInfo.handle === "string" && requestInfo.handle && requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH) {
1044
+ routeContext.handle = requestInfo.handle;
1045
+ }
1046
+ return routeContext;
1047
+ }
972
1048
  async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT_REVALIDATE_ENDPOINT) {
973
1049
  let instance = runtime.itemInstances.get(draftItem.id);
974
1050
  if (!instance) {
@@ -976,10 +1052,14 @@ async function revalidateWeaverseNextItem(runtime, draftItem, endpoint = DEFAULT
976
1052
  `[weaverse-next] No live item instance for "${draftItem.id}".`
977
1053
  );
978
1054
  }
1055
+ let routeContext = buildWeaverseNextRevalidateRouteContext(
1056
+ runtime.requestInfo
1057
+ );
1058
+ let body = routeContext ? { draftItem, routeContext } : { draftItem };
979
1059
  let response = await fetch(endpoint, {
980
1060
  method: "POST",
981
1061
  headers: { "Content-Type": "application/json" },
982
- body: JSON.stringify({ draftItem })
1062
+ body: JSON.stringify(body)
983
1063
  });
984
1064
  if (!response.ok) {
985
1065
  throw new Error(
@@ -1268,7 +1348,7 @@ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
1268
1348
  if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
1269
1349
  return null;
1270
1350
  }
1271
- let bundle = isDesignMode ? "index.js" : "preview.js";
1351
+ let bundle = isPreviewMode || isRevisionPreview ? "preview.js" : "index.js";
1272
1352
  let version = last(searchParams, "weaverseVersion");
1273
1353
  let url = `${origin}/static/studio/next/${bundle}`;
1274
1354
  return version ? `${url}?v=${encodeURIComponent(version)}` : url;
@@ -1356,6 +1436,7 @@ exports.WeaverseNextStudioBridge = WeaverseNextStudioBridge;
1356
1436
  exports.WeaverseNextStudioConnect = WeaverseNextStudioConnect;
1357
1437
  exports.bindWeaverseNextStudioRuntime = bindWeaverseNextStudioRuntime;
1358
1438
  exports.buildWeaverseNextRequestInfo = buildWeaverseNextRequestInfo;
1439
+ exports.buildWeaverseNextRevalidateRouteContext = buildWeaverseNextRevalidateRouteContext;
1359
1440
  exports.createTranslate = createTranslate;
1360
1441
  exports.createWeaverseNextClient = createWeaverseNextClient;
1361
1442
  exports.createWeaverseNextRuntime = createWeaverseNextRuntime;