@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
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import type { PageType } from '@weaverse/schema'
|
|
2
|
+
import { PageTypeSchema } from '@weaverse/schema'
|
|
3
|
+
import {
|
|
4
|
+
ROUTE_CONTEXT_I18N_KEYS,
|
|
5
|
+
sanitizeRouteContextSearch,
|
|
6
|
+
type WeaverseNextRevalidateRouteContext,
|
|
7
|
+
} from '../revalidate-item'
|
|
8
|
+
import type {
|
|
9
|
+
WeaverseNextComponentData,
|
|
10
|
+
WeaverseNextI18n,
|
|
11
|
+
WeaverseNextRequestContext,
|
|
12
|
+
WeaverseNextServerClient,
|
|
13
|
+
} from '../types'
|
|
14
|
+
import { generateDataFromSchema } from '../utils'
|
|
15
|
+
|
|
16
|
+
const NO_STORE_HEADERS = {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
'Cache-Control': 'no-store',
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
const STATUS_OK = 200
|
|
22
|
+
const STATUS_BAD_REQUEST = 400
|
|
23
|
+
const STATUS_NOT_FOUND = 404
|
|
24
|
+
const STATUS_UNPROCESSABLE = 422
|
|
25
|
+
const STATUS_SERVER_ERROR = 500
|
|
26
|
+
|
|
27
|
+
const MAX_PATHNAME_LENGTH = 2048
|
|
28
|
+
const MAX_SEARCH_LENGTH = 8192
|
|
29
|
+
const MAX_HANDLE_LENGTH = 512
|
|
30
|
+
const MAX_I18N_FIELD_LENGTH = 256
|
|
31
|
+
// ASCII control characters (C0 range + DEL) are never valid in a route path.
|
|
32
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control chars in untrusted pathnames is the intent.
|
|
33
|
+
const CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/
|
|
34
|
+
|
|
35
|
+
/** Configuration for the App Router item-revalidation handler. */
|
|
36
|
+
export interface WeaverseNextRevalidateHandlerConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Reuse the app's server-client factory so the loader runs with the same
|
|
39
|
+
* component registry and commerce (storefront) context as page loads.
|
|
40
|
+
*
|
|
41
|
+
* The optional second argument is the validated, same-origin route context
|
|
42
|
+
* reconstructed from the request body. It is `undefined` for legacy bodies
|
|
43
|
+
* that carry no `routeContext`, so a one-argument `(request) => client`
|
|
44
|
+
* callback remains valid. Consumers must still source project ID, Studio
|
|
45
|
+
* host, API bases/keys, and env from server config — never from this
|
|
46
|
+
* browser-provided context.
|
|
47
|
+
*/
|
|
48
|
+
getClient: (
|
|
49
|
+
request: Request,
|
|
50
|
+
requestContext?: WeaverseNextRequestContext
|
|
51
|
+
) => Promise<WeaverseNextServerClient> | WeaverseNextServerClient
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** POST body accepted by the revalidate route. */
|
|
55
|
+
export interface WeaverseNextRevalidateRequestBody {
|
|
56
|
+
/** Unsaved Studio item settings to pass to the registered component loader. */
|
|
57
|
+
draftItem: WeaverseNextComponentData
|
|
58
|
+
/** Public route identity used to reconstruct the loader request context. */
|
|
59
|
+
routeContext?: WeaverseNextRevalidateRouteContext
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether a browser-provided pathname is safe to reconstruct a same-origin URL
|
|
64
|
+
* from. Rejects protocol-relative (`//`), absolute-URL, backslash, control
|
|
65
|
+
* character, query-in-path, and fragment-in-path inputs before any URL is
|
|
66
|
+
* built. Literal/encoded dot segments are allowed here and normalized later by
|
|
67
|
+
* the URL pathname setter.
|
|
68
|
+
*/
|
|
69
|
+
function isValidPathname(pathname: string): boolean {
|
|
70
|
+
if (!pathname || pathname.length > MAX_PATHNAME_LENGTH) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
if (pathname[0] !== '/' || pathname.startsWith('//')) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
if (pathname.includes('\\') || pathname.includes('?')) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
if (pathname.includes('#') || CONTROL_CHAR_PATTERN.test(pathname)) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether a value is a plain object (literal or `null`-prototype), the only
|
|
87
|
+
* i18n container the wire shape allows. Arrays, class instances, and other
|
|
88
|
+
* exotic objects are rejected so nothing exotic reaches field validation.
|
|
89
|
+
*/
|
|
90
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
91
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
let proto = Object.getPrototypeOf(value)
|
|
95
|
+
return proto === Object.prototype || proto === null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validate and pick the allowed i18n fields. Returns `undefined` for a valid
|
|
100
|
+
* empty result and `null` for malformed input so the handler can fail closed.
|
|
101
|
+
*
|
|
102
|
+
* The normative i18n shape carries only the {@link ROUTE_CONTEXT_I18N_KEYS}.
|
|
103
|
+
* Malformed input is rejected rather than silently narrowed: any unknown own
|
|
104
|
+
* key (nested object, extra string, anything), a non-plain container (array,
|
|
105
|
+
* class instance), or a known key whose value is not an optional string of at
|
|
106
|
+
* most {@link MAX_I18N_FIELD_LENGTH} characters fails closed.
|
|
107
|
+
*/
|
|
108
|
+
function sanitizeRouteContextI18n(
|
|
109
|
+
input: unknown
|
|
110
|
+
): WeaverseNextI18n | undefined | null {
|
|
111
|
+
if (input === undefined) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if (!isPlainObject(input)) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
let source = input
|
|
118
|
+
let allowed = new Set<string>(ROUTE_CONTEXT_I18N_KEYS)
|
|
119
|
+
// Reject unknown own keys instead of dropping them, so a crafted context
|
|
120
|
+
// cannot smuggle extra properties past the trust boundary.
|
|
121
|
+
for (let key of Object.keys(source)) {
|
|
122
|
+
if (!allowed.has(key)) {
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
let result: Record<string, string> = {}
|
|
127
|
+
for (let key of ROUTE_CONTEXT_I18N_KEYS) {
|
|
128
|
+
let value = source[key]
|
|
129
|
+
if (value === undefined) {
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
if (typeof value !== 'string' || value.length > MAX_I18N_FIELD_LENGTH) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
result[key] = value
|
|
136
|
+
}
|
|
137
|
+
return Object.keys(result).length ? result : undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reconstruct a validated, same-origin {@link WeaverseNextRequestContext} from
|
|
142
|
+
* the untrusted route context. Returns `null` for any malformed field so the
|
|
143
|
+
* handler can answer `400 invalid-route-context` before creating a client. The
|
|
144
|
+
* endpoint origin is fixed first, then validated components are assigned, so
|
|
145
|
+
* the browser cannot change protocol, host, port, or credentials.
|
|
146
|
+
*/
|
|
147
|
+
function reconstructRouteContext(
|
|
148
|
+
request: Request,
|
|
149
|
+
routeContext: unknown
|
|
150
|
+
): WeaverseNextRequestContext | null {
|
|
151
|
+
if (
|
|
152
|
+
typeof routeContext !== 'object' ||
|
|
153
|
+
routeContext === null ||
|
|
154
|
+
Array.isArray(routeContext)
|
|
155
|
+
) {
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
let { pathname, search, handle, pageType, i18n } = routeContext as Record<
|
|
159
|
+
string,
|
|
160
|
+
unknown
|
|
161
|
+
>
|
|
162
|
+
|
|
163
|
+
if (typeof pathname !== 'string' || !isValidPathname(pathname)) {
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (typeof search !== 'string' || search.length > MAX_SEARCH_LENGTH) {
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
let rawSearch = search
|
|
171
|
+
if (rawSearch.length && !rawSearch.startsWith('?')) {
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let sanitizedI18n = sanitizeRouteContextI18n(i18n)
|
|
176
|
+
if (sanitizedI18n === null) {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let resolvedPageType: PageType | undefined
|
|
181
|
+
if (pageType !== undefined) {
|
|
182
|
+
let parsed = PageTypeSchema.safeParse(pageType)
|
|
183
|
+
if (!parsed.success) {
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
resolvedPageType = parsed.data
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let resolvedHandle: string | undefined
|
|
190
|
+
if (handle !== undefined) {
|
|
191
|
+
if (
|
|
192
|
+
typeof handle !== 'string' ||
|
|
193
|
+
!handle ||
|
|
194
|
+
handle.length > MAX_HANDLE_LENGTH
|
|
195
|
+
) {
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
resolvedHandle = handle
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Fix the endpoint origin first, then assign validated components. Reuse the
|
|
202
|
+
// URL's canonical pathname in both context fields so raw input never survives
|
|
203
|
+
// in only one place.
|
|
204
|
+
let endpointUrl = new URL(request.url)
|
|
205
|
+
let routeUrl = new URL(endpointUrl.origin)
|
|
206
|
+
routeUrl.pathname = pathname
|
|
207
|
+
routeUrl.search = sanitizeRouteContextSearch(rawSearch)
|
|
208
|
+
let canonicalPathname = routeUrl.pathname
|
|
209
|
+
let searchParams = routeUrl.searchParams
|
|
210
|
+
|
|
211
|
+
// Last value wins for duplicated Studio controls, matching
|
|
212
|
+
// `getWeaverseNextConfigs()` and its cache-mode resolution.
|
|
213
|
+
let lastParam = (key: string): string | undefined => {
|
|
214
|
+
let values = searchParams.getAll(key)
|
|
215
|
+
return values.length ? values.at(-1) : undefined
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
pathname: canonicalPathname,
|
|
220
|
+
searchParams,
|
|
221
|
+
url: routeUrl,
|
|
222
|
+
// Headers come from the actual same-origin POST, never its JSON body.
|
|
223
|
+
headers: new Headers(request.headers),
|
|
224
|
+
...(sanitizedI18n ? { i18n: sanitizedI18n } : {}),
|
|
225
|
+
...(resolvedPageType ? { pageType: resolvedPageType } : {}),
|
|
226
|
+
...(resolvedHandle ? { handle: resolvedHandle } : {}),
|
|
227
|
+
isDesignMode: lastParam('isDesignMode') === 'true',
|
|
228
|
+
isPreviewMode: lastParam('isPreviewMode') === 'true',
|
|
229
|
+
isRevisionPreview: searchParams.has('__revisionId'),
|
|
230
|
+
sectionType: lastParam('sectionType') || undefined,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function json(body: Record<string, unknown>, status: number) {
|
|
235
|
+
return new Response(JSON.stringify(body), {
|
|
236
|
+
status,
|
|
237
|
+
headers: NO_STORE_HEADERS,
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function parseDraftItem(payload: unknown): WeaverseNextComponentData | null {
|
|
242
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
let draftItem = (payload as { draftItem?: unknown }).draftItem
|
|
246
|
+
if (typeof draftItem !== 'object' || draftItem === null) {
|
|
247
|
+
return null
|
|
248
|
+
}
|
|
249
|
+
let { id, type } = draftItem as { id?: unknown; type?: unknown }
|
|
250
|
+
if (typeof id !== 'string' || !id || typeof type !== 'string' || !type) {
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
return draftItem as WeaverseNextComponentData
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** App Router handler returned for Studio item revalidation requests. */
|
|
257
|
+
export interface WeaverseNextRevalidateHandler {
|
|
258
|
+
/** Handle a Studio request to rerun one registered component loader. */
|
|
259
|
+
POST: (request: Request) => Promise<Response>
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create the Studio per-item revalidation route handler.
|
|
264
|
+
*
|
|
265
|
+
* Studio design-mode edits to loader-backed inputs (e.g. resource pickers) need
|
|
266
|
+
* fresh server loader data for a single item. Re-running it through
|
|
267
|
+
* `router.refresh()` re-applies the whole RSC tree — and the draft-item query
|
|
268
|
+
* rewrite changes the App Router page segment key, remounting the page and
|
|
269
|
+
* resetting scroll. This handler runs just the edited component's `loader`
|
|
270
|
+
* with the unsaved draft settings and returns the payload for an in-place
|
|
271
|
+
* client update (see the `2026-07-02--next-item-revalidation` spec).
|
|
272
|
+
*
|
|
273
|
+
* Security: only registered component types are executable, the handler never
|
|
274
|
+
* persists anything, and responses are `no-store`. Exposure is equivalent to
|
|
275
|
+
* the existing `weaverseDraftItem` URL param, which already lets any visitor
|
|
276
|
+
* run design-mode loaders with arbitrary draft settings.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* // app/api/weaverse/revalidate/route.ts
|
|
281
|
+
* export const { POST } = createWeaverseNextRevalidateHandler({
|
|
282
|
+
* getClient: () => getWeaverseServerClient(Promise.resolve({})),
|
|
283
|
+
* })
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export function createWeaverseNextRevalidateHandler(
|
|
287
|
+
config: WeaverseNextRevalidateHandlerConfig
|
|
288
|
+
): WeaverseNextRevalidateHandler {
|
|
289
|
+
return {
|
|
290
|
+
POST: async (request: Request) => {
|
|
291
|
+
let payload: unknown
|
|
292
|
+
try {
|
|
293
|
+
payload = await request.json()
|
|
294
|
+
} catch {
|
|
295
|
+
return json({ error: 'invalid-payload' }, STATUS_BAD_REQUEST)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let draftItem = parseDraftItem(payload)
|
|
299
|
+
if (!draftItem) {
|
|
300
|
+
return json({ error: 'invalid-payload' }, STATUS_BAD_REQUEST)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Missing `routeContext` is valid legacy input; a present but malformed
|
|
304
|
+
// context is rejected at the trust boundary before any client is created.
|
|
305
|
+
let requestContext: WeaverseNextRequestContext | undefined
|
|
306
|
+
let body = payload as { routeContext?: unknown }
|
|
307
|
+
if (Object.hasOwn(body, 'routeContext')) {
|
|
308
|
+
let reconstructed = reconstructRouteContext(request, body.routeContext)
|
|
309
|
+
if (!reconstructed) {
|
|
310
|
+
return json({ error: 'invalid-route-context' }, STATUS_BAD_REQUEST)
|
|
311
|
+
}
|
|
312
|
+
requestContext = reconstructed
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let client: WeaverseNextServerClient
|
|
316
|
+
try {
|
|
317
|
+
client = await config.getClient(request, requestContext)
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('❌ Revalidate handler getClient failed.', error)
|
|
320
|
+
return json({ error: 'client-init-failed' }, STATUS_SERVER_ERROR)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let component = client.components.find(
|
|
324
|
+
(candidate) => candidate?.schema?.type === draftItem.type
|
|
325
|
+
)
|
|
326
|
+
if (!component) {
|
|
327
|
+
return json({ error: 'unknown-component-type' }, STATUS_NOT_FOUND)
|
|
328
|
+
}
|
|
329
|
+
if (typeof component.loader !== 'function') {
|
|
330
|
+
return json({ error: 'missing-loader' }, STATUS_UNPROCESSABLE)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
// Same args shape as `runWeaverseComponentLoaders`, so component
|
|
335
|
+
// loaders behave identically between page load and revalidation.
|
|
336
|
+
let loaderData = await component.loader({
|
|
337
|
+
data: {
|
|
338
|
+
...generateDataFromSchema(component.schema),
|
|
339
|
+
...draftItem.data,
|
|
340
|
+
},
|
|
341
|
+
weaverse: client,
|
|
342
|
+
context: client.requestContext,
|
|
343
|
+
commerce: client.commerce,
|
|
344
|
+
})
|
|
345
|
+
return json({ loaderData: loaderData ?? null }, STATUS_OK)
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error(
|
|
348
|
+
'❌ Item loader revalidation failed.',
|
|
349
|
+
draftItem.type,
|
|
350
|
+
draftItem.id,
|
|
351
|
+
error
|
|
352
|
+
)
|
|
353
|
+
return json({ error: 'loader-failed' }, STATUS_SERVER_ERROR)
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
}
|