@weaverse/next 0.1.0-alpha.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +155 -73
  2. package/dist/{chunk-6PMZ77RA.mjs → chunk-LH6AAEIK.mjs} +115 -8
  3. package/dist/chunk-LH6AAEIK.mjs.map +1 -0
  4. package/dist/index.js +260 -30
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +167 -46
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/server.js +321 -91
  9. package/dist/server.js.map +1 -1
  10. package/dist/server.mjs +205 -28
  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 +60 -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 +21 -0
  29. package/dist/types/renderer.d.ts.map +1 -0
  30. package/dist/types/request-info.d.ts +19 -0
  31. package/dist/types/request-info.d.ts.map +1 -0
  32. package/dist/types/revalidate-item.d.ts +82 -0
  33. package/dist/types/revalidate-item.d.ts.map +1 -0
  34. package/dist/types/root-provider.d.ts +62 -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 +18 -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 +665 -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 +12 -8
  71. package/src/client.ts +102 -0
  72. package/src/hooks.ts +50 -0
  73. package/src/index.ts +153 -0
  74. package/src/item.ts +167 -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 +100 -0
  80. package/src/request-info.ts +124 -0
  81. package/src/revalidate-item.ts +211 -0
  82. package/src/root-provider.tsx +182 -0
  83. package/src/runtime.ts +555 -0
  84. package/src/seo.ts +157 -0
  85. package/src/server/configs.ts +178 -0
  86. package/src/server/normalize-page-url.ts +73 -0
  87. package/src/server/revalidate-handler.ts +357 -0
  88. package/src/server/server-client.ts +728 -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 +47 -0
  95. package/src/translation-context.tsx +299 -0
  96. package/src/translation-store.ts +69 -0
  97. package/src/types.ts +753 -0
  98. package/src/use-weaverse-next-studio.tsx +106 -0
  99. package/src/utils.ts +37 -0
  100. package/dist/chunk-6PMZ77RA.mjs.map +0 -1
  101. package/dist/index.d.mts +0 -517
  102. package/dist/index.d.ts +0 -517
  103. package/dist/seo-CpN6tC36.d.mts +0 -477
  104. package/dist/seo-CpN6tC36.d.ts +0 -477
  105. package/dist/server.d.mts +0 -99
  106. package/dist/server.d.ts +0 -99
@@ -0,0 +1,211 @@
1
+ import type { PageType } from '@weaverse/schema'
2
+ import { PageTypeSchema } from '@weaverse/schema'
3
+ import type {
4
+ WeaverseNextComponentData,
5
+ WeaverseNextI18n,
6
+ WeaverseNextRequestInfo,
7
+ } from './types'
8
+
9
+ /** Default App Router endpoint used for Studio item-loader revalidation. */
10
+ export const DEFAULT_REVALIDATE_ENDPOINT = '/api/weaverse/revalidate'
11
+
12
+ const MAX_ROUTE_CONTEXT_HANDLE_LENGTH = 512
13
+
14
+ /**
15
+ * Server-owned and transient controls stripped from the revalidation route
16
+ * context on both sides of the trust boundary. Compared case-insensitively, so
17
+ * every entry is stored lowercased. Client sanitization keeps normal traffic
18
+ * clean; the route handler repeats this deny-list as the security boundary.
19
+ *
20
+ * - Server-owned: a crafted `?weaverseProjectId=`/`?weaverseHost=` must never
21
+ * influence server config resolution (see `getWeaverseNextConfigs`), and
22
+ * credential-shaped params never ride along in route context.
23
+ * - Transient: draft-item and RSC transport params never describe route identity.
24
+ */
25
+ export const DENIED_ROUTE_CONTEXT_PARAMS = [
26
+ 'weaverseprojectid',
27
+ 'weaversehost',
28
+ 'weaverseapikey',
29
+ 'weaverseapibase',
30
+ 'weaversepublicapibase',
31
+ 'weaverseversion',
32
+ 'projectid',
33
+ 'weaversedraftitem',
34
+ '__weaversedraftitem',
35
+ '_rsc',
36
+ ]
37
+
38
+ /** i18n fields allowed to cross the browser boundary. */
39
+ export const ROUTE_CONTEXT_I18N_KEYS = [
40
+ 'country',
41
+ 'label',
42
+ 'language',
43
+ 'locale',
44
+ 'pathPrefix',
45
+ ] as const
46
+
47
+ /**
48
+ * JSON-safe route subset that may cross the browser boundary alongside the
49
+ * draft item. Never carries headers, cookies, auth, env, project ID, Studio
50
+ * host, API keys/bases, commerce clients, the request context, runtime, or
51
+ * client. `search` is canonical (preserves duplicate keys); the derived
52
+ * `queries` record is intentionally omitted.
53
+ */
54
+ export interface WeaverseNextRevalidateRouteContext {
55
+ /** Route handle used for page assignment. */
56
+ handle?: string
57
+ /** Public locale and market identity allowed across the browser boundary. */
58
+ i18n?: Pick<
59
+ WeaverseNextI18n,
60
+ 'country' | 'label' | 'language' | 'locale' | 'pathPrefix'
61
+ >
62
+ /** Weaverse page type resolved by the active route. */
63
+ pageType?: PageType
64
+ /** Canonical same-origin route pathname. */
65
+ pathname: string
66
+ /** Sanitized serialized query string, including the leading `?` when set. */
67
+ search: string
68
+ }
69
+
70
+ /**
71
+ * Minimal runtime surface the in-place apply needs. Structural (not the
72
+ * concrete `WeaverseNextRuntime` class) so unit tests can drive it with a
73
+ * plain mock. `requestInfo` stays optional: a real `WeaverseNextRuntime`
74
+ * always supplies it, but when absent the client omits `routeContext` and the
75
+ * legacy `{ draftItem }` request is preserved.
76
+ */
77
+ export interface RevalidateItemRuntimeLike {
78
+ /** Live item stores keyed by Weaverse item ID. */
79
+ itemInstances: Map<
80
+ string,
81
+ {
82
+ /** Merge refreshed loader data into the live item store. */
83
+ setData: (update: Record<string, unknown>) => unknown
84
+ }
85
+ >
86
+ /** Active route identity used to construct the optional revalidation payload. */
87
+ requestInfo?: WeaverseNextRequestInfo
88
+ }
89
+
90
+ /**
91
+ * Strip denied (server-owned/transient) controls from a `search` string and
92
+ * re-serialize it. Preserves ordinary application queries, duplicate keys,
93
+ * encoded Unicode, and mode controls (`isDesignMode`, `isPreviewMode`,
94
+ * `__revisionId`, `sectionType`). Returns `''` or a `?`-prefixed string.
95
+ */
96
+ export function sanitizeRouteContextSearch(search: string): string {
97
+ let params = new URLSearchParams(search)
98
+ let denied = new Set(DENIED_ROUTE_CONTEXT_PARAMS)
99
+ for (let key of [...new Set(params.keys())]) {
100
+ if (denied.has(key.toLowerCase())) {
101
+ params.delete(key)
102
+ }
103
+ }
104
+ let serialized = params.toString()
105
+ return serialized ? `?${serialized}` : ''
106
+ }
107
+
108
+ /**
109
+ * Build the sanitized {@link WeaverseNextRevalidateRouteContext} from the
110
+ * active runtime's request info. Returns `undefined` without a usable pathname
111
+ * so the caller omits `routeContext` and keeps the legacy request. This is not
112
+ * the security boundary — the route handler re-validates and re-sanitizes.
113
+ */
114
+ export function buildWeaverseNextRevalidateRouteContext(
115
+ requestInfo?: WeaverseNextRequestInfo
116
+ ): WeaverseNextRevalidateRouteContext | undefined {
117
+ if (
118
+ !requestInfo ||
119
+ typeof requestInfo.pathname !== 'string' ||
120
+ !requestInfo.pathname
121
+ ) {
122
+ return
123
+ }
124
+
125
+ let routeContext: WeaverseNextRevalidateRouteContext = {
126
+ pathname: requestInfo.pathname,
127
+ search: sanitizeRouteContextSearch(requestInfo.search ?? ''),
128
+ }
129
+
130
+ let { i18n } = requestInfo
131
+ if (i18n && typeof i18n === 'object') {
132
+ let picked: Record<string, string> = {}
133
+ for (let key of ROUTE_CONTEXT_I18N_KEYS) {
134
+ let value = i18n[key]
135
+ if (typeof value === 'string') {
136
+ picked[key] = value
137
+ }
138
+ }
139
+ if (Object.keys(picked).length) {
140
+ routeContext.i18n = picked
141
+ }
142
+ }
143
+
144
+ if (
145
+ requestInfo.pageType &&
146
+ PageTypeSchema.safeParse(requestInfo.pageType).success
147
+ ) {
148
+ routeContext.pageType = requestInfo.pageType
149
+ }
150
+
151
+ if (
152
+ typeof requestInfo.handle === 'string' &&
153
+ requestInfo.handle &&
154
+ requestInfo.handle.length <= MAX_ROUTE_CONTEXT_HANDLE_LENGTH
155
+ ) {
156
+ routeContext.handle = requestInfo.handle
157
+ }
158
+
159
+ return routeContext
160
+ }
161
+
162
+ /**
163
+ * Studio per-item loader revalidation: POST the unsaved draft item (and, when
164
+ * the runtime exposes route context, a sanitized `routeContext`) to the app's
165
+ * revalidate route (see `createWeaverseNextRevalidateHandler`) and apply the
166
+ * returned `loaderData` to the live item instance in place.
167
+ *
168
+ * No `router.refresh()` is involved, so the RSC tree is never re-applied — no
169
+ * page remount, no scroll reset, and the draft payload never touches the URL.
170
+ *
171
+ * Throws on any failure (missing route, non-OK response, unknown item) so the
172
+ * Builder Studio bridge can detect the miss and fall back to its legacy
173
+ * refresh-based revalidation for this window.
174
+ */
175
+ export async function revalidateWeaverseNextItem(
176
+ runtime: RevalidateItemRuntimeLike,
177
+ draftItem: WeaverseNextComponentData,
178
+ endpoint: string = DEFAULT_REVALIDATE_ENDPOINT
179
+ ): Promise<void> {
180
+ let instance = runtime.itemInstances.get(draftItem.id)
181
+ if (!instance) {
182
+ throw new Error(
183
+ `[weaverse-next] No live item instance for "${draftItem.id}".`
184
+ )
185
+ }
186
+
187
+ // Snapshot route identity at revalidation start so a later navigation cannot
188
+ // retarget this in-flight request. Omit `routeContext` for legacy runtimes.
189
+ let routeContext = buildWeaverseNextRevalidateRouteContext(
190
+ runtime.requestInfo
191
+ )
192
+ let body = routeContext ? { draftItem, routeContext } : { draftItem }
193
+
194
+ let response = await fetch(endpoint, {
195
+ method: 'POST',
196
+ headers: { 'Content-Type': 'application/json' },
197
+ body: JSON.stringify(body),
198
+ })
199
+ if (!response.ok) {
200
+ throw new Error(
201
+ `[weaverse-next] Revalidate endpoint responded ${response.status}.`
202
+ )
203
+ }
204
+
205
+ let { loaderData } = (await response.json()) as { loaderData?: unknown }
206
+ // The item's settings were already mutated by the Studio edit; only the
207
+ // loader payload needs applying. `setData` merges and notifies subscribers,
208
+ // and the new `loaderData` identity is what Builder's revalidation poll
209
+ // observes for completion.
210
+ instance.setData({ loaderData: loaderData ?? null })
211
+ }
@@ -0,0 +1,182 @@
1
+ 'use client'
2
+
3
+ import type { SchemaType } from '@weaverse/schema'
4
+ import {
5
+ createContext,
6
+ type ReactNode,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ } from 'react'
11
+ import {
12
+ getSchemaStaticContent,
13
+ WeaverseNextContext,
14
+ type WeaverseNextContextValue,
15
+ } from './provider'
16
+ import { createWeaverseNextThemeSettingsStore } from './theme-settings-store'
17
+ import {
18
+ type TranslateFunction,
19
+ TranslationProvider,
20
+ } from './translation-context'
21
+ import { TranslationStore } from './translation-store'
22
+ import type {
23
+ WeaverseNextThemeSchema,
24
+ WeaverseNextThemeSettingsStore,
25
+ } from './types'
26
+
27
+ /** Root-scoped theme and translation stores adopted by route providers. */
28
+ export interface WeaverseNextRootContextValue {
29
+ /** Active-locale static-text overrides shared with route-level providers. */
30
+ merchantOverrides?: Record<string, unknown>
31
+ /** Theme default locale JSON shared with route-level providers. */
32
+ staticContent: Record<string, unknown>
33
+ /** Root-scoped external store shared with route-level providers. */
34
+ themeSettingsStore: WeaverseNextThemeSettingsStore
35
+ /** Single live design-mode static-text override store for the whole session. */
36
+ translationStore: TranslationStore
37
+ }
38
+
39
+ /**
40
+ * Ambient root-scoped context. Route-level `WeaverseNextProvider` reads this to
41
+ * adopt the shared theme settings store instead of creating its own.
42
+ */
43
+ export const WeaverseNextRootContext =
44
+ createContext<WeaverseNextRootContextValue | null>(null)
45
+
46
+ /** Root theme and translation data shared across the App Router tree. */
47
+ export interface WeaverseNextRootProviderProps {
48
+ /** Application subtree, including global and route-level components. */
49
+ children: ReactNode
50
+ /** Root-loaded theme settings (published-mode fetch, no design-mode context). */
51
+ initialThemeSettings?: Record<string, unknown>
52
+ /** Active-locale static-text overrides from `loadThemeSettings().merchantOverrides`. */
53
+ merchantOverrides?: Record<string, unknown>
54
+ /** Public environment values exposed to the theme-settings store. */
55
+ publicEnv?: Record<string, string | undefined>
56
+ /**
57
+ * Theme default locale JSON (`loadThemeSettings().staticContent`). Falls back
58
+ * to `themeSchema.i18n.staticContent` when omitted.
59
+ */
60
+ staticContent?: Record<string, unknown>
61
+ /** Optional external translation function (host i18n); highest priority in `t()`. */
62
+ t?: TranslateFunction
63
+ /** Theme schema associated with the initial settings. */
64
+ themeSchema?: SchemaType | WeaverseNextThemeSchema
65
+ }
66
+
67
+ /**
68
+ * Root-scoped client boundary that owns the single `WeaverseNextThemeSettingsStore`
69
+ * for the whole app session, matching Hydrogen's `withWeaverse()` root store.
70
+ * Mount once in `app/layout.tsx`, above `Header`/`Footer`/global modules and any
71
+ * route-level `WeaverseNextProvider`.
72
+ *
73
+ * Also renders `WeaverseNextContext` with a minimal value so `useThemeSettings()`
74
+ * works for components mounted directly under this provider, even without a
75
+ * route-level `WeaverseNextProvider` above them.
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * <WeaverseNextRootProvider initialThemeSettings={theme.theme} themeSchema={theme.schema}>
80
+ * <Header />
81
+ * {children}
82
+ * <Footer />
83
+ * </WeaverseNextRootProvider>
84
+ * ```
85
+ */
86
+ export function WeaverseNextRootProvider(props: WeaverseNextRootProviderProps) {
87
+ let {
88
+ children,
89
+ initialThemeSettings,
90
+ merchantOverrides,
91
+ staticContent,
92
+ t,
93
+ themeSchema,
94
+ publicEnv,
95
+ } = props
96
+
97
+ // `useRef`, not `useMemo`: a parent re-render must never replace this store —
98
+ // it is the single instance Studio's `updateThemeSettings()` mutates in place.
99
+ let storeRef = useRef<WeaverseNextThemeSettingsStore | null>(null)
100
+ if (!storeRef.current) {
101
+ storeRef.current = createWeaverseNextThemeSettingsStore({
102
+ publicEnv,
103
+ schema: themeSchema,
104
+ settings: initialThemeSettings,
105
+ })
106
+ }
107
+ let themeSettingsStore = storeRef.current
108
+
109
+ // Same `useRef` rationale as the theme store: one live static-text store for
110
+ // the whole session, which Builder's `updateStaticText()` RPC mutates in place
111
+ // via `runtime.internal.translationStore`. A parent re-render must never swap
112
+ // it, or design-mode overrides would be lost.
113
+ let translationStoreRef = useRef<TranslationStore | null>(null)
114
+ if (!translationStoreRef.current) {
115
+ translationStoreRef.current = new TranslationStore()
116
+ }
117
+ let translationStore = translationStoreRef.current
118
+
119
+ // If `initialThemeSettings` changes after mount (e.g. root revalidation),
120
+ // update the existing store in place instead of replacing it. This is an
121
+ // effect because external store updates are side effects; the initial render
122
+ // is already seeded synchronously by store construction above.
123
+ let appliedThemeSettingsRef = useRef(initialThemeSettings)
124
+ useEffect(() => {
125
+ if (
126
+ initialThemeSettings &&
127
+ appliedThemeSettingsRef.current !== initialThemeSettings
128
+ ) {
129
+ themeSettingsStore.updateThemeSettings(initialThemeSettings)
130
+ appliedThemeSettingsRef.current = initialThemeSettings
131
+ }
132
+ }, [initialThemeSettings, themeSettingsStore])
133
+
134
+ // Prefer an explicit `staticContent` prop (from `loadThemeSettings()`); fall
135
+ // back to `themeSchema.i18n.staticContent`. Both resolve to referentially
136
+ // stable objects, so memoizing the context values below stays effective.
137
+ let resolvedStaticContent =
138
+ staticContent ?? getSchemaStaticContent(themeSchema)
139
+
140
+ // `themeSettingsStore` / `translationStore` are `useRef`-stable, so memoizing
141
+ // on them (rather than recreating these plain objects every render) keeps
142
+ // `useThemeSettings()` / `useTranslation()` consumers (Header, Footer, ...)
143
+ // from re-rendering on every root re-render.
144
+ let rootContextValue = useMemo<WeaverseNextRootContextValue>(
145
+ () => ({
146
+ merchantOverrides,
147
+ staticContent: resolvedStaticContent,
148
+ themeSettingsStore,
149
+ translationStore,
150
+ }),
151
+ [
152
+ merchantOverrides,
153
+ resolvedStaticContent,
154
+ themeSettingsStore,
155
+ translationStore,
156
+ ]
157
+ )
158
+ let contextValue = useMemo<WeaverseNextContextValue>(
159
+ () => ({
160
+ merchantOverrides,
161
+ themeSettings: themeSettingsStore.getSnapshot(),
162
+ themeSettingsStore,
163
+ translationStore,
164
+ }),
165
+ [merchantOverrides, themeSettingsStore, translationStore]
166
+ )
167
+
168
+ return (
169
+ <WeaverseNextRootContext.Provider value={rootContextValue}>
170
+ <WeaverseNextContext.Provider value={contextValue}>
171
+ <TranslationProvider
172
+ merchantOverrides={merchantOverrides}
173
+ staticContent={resolvedStaticContent}
174
+ t={t}
175
+ translationStore={translationStore}
176
+ >
177
+ {children}
178
+ </TranslationProvider>
179
+ </WeaverseNextContext.Provider>
180
+ </WeaverseNextRootContext.Provider>
181
+ )
182
+ }