@weaverse/next 0.1.0-alpha.0 → 0.1.0-alpha.10
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/README.md +562 -0
- package/dist/chunk-XXGMPTWH.mjs +219 -0
- package/dist/chunk-XXGMPTWH.mjs.map +1 -0
- package/dist/index.d.mts +487 -161
- package/dist/index.d.ts +487 -161
- package/dist/index.js +1060 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +896 -106
- package/dist/index.mjs.map +1 -1
- package/dist/seo-CgO21rLg.d.mts +517 -0
- package/dist/seo-CgO21rLg.d.ts +517 -0
- package/dist/server.d.mts +99 -0
- package/dist/server.d.ts +99 -0
- package/dist/server.js +892 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +671 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +23 -4
package/dist/server.js
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/seo.ts
|
|
4
|
+
function hasOpenGraphContent(seo) {
|
|
5
|
+
return Boolean(seo?.title || seo?.description || seo?.image || seo?.type);
|
|
6
|
+
}
|
|
7
|
+
function hasTwitterContent(seo) {
|
|
8
|
+
return Boolean(seo?.title || seo?.description || seo?.image || seo?.cardType);
|
|
9
|
+
}
|
|
10
|
+
function formatWeaverseNextSeoMetadata(seo) {
|
|
11
|
+
let index = seo?.robots?.index !== false;
|
|
12
|
+
let follow = seo?.robots?.follow !== false;
|
|
13
|
+
let metadata = {
|
|
14
|
+
robots: { index, follow }
|
|
15
|
+
};
|
|
16
|
+
if (!seo) {
|
|
17
|
+
return metadata;
|
|
18
|
+
}
|
|
19
|
+
if (seo.title) {
|
|
20
|
+
metadata.title = seo.title;
|
|
21
|
+
}
|
|
22
|
+
if (seo.description) {
|
|
23
|
+
metadata.description = seo.description;
|
|
24
|
+
}
|
|
25
|
+
if (seo.keywords) {
|
|
26
|
+
metadata.keywords = seo.keywords;
|
|
27
|
+
}
|
|
28
|
+
if (seo.canonicalUrl) {
|
|
29
|
+
metadata.alternates = { canonical: seo.canonicalUrl };
|
|
30
|
+
}
|
|
31
|
+
let openGraph = seo.openGraph;
|
|
32
|
+
if (hasOpenGraphContent(openGraph)) {
|
|
33
|
+
metadata.openGraph = {
|
|
34
|
+
title: openGraph?.title,
|
|
35
|
+
description: openGraph?.description,
|
|
36
|
+
images: openGraph?.image ? [openGraph.image] : void 0,
|
|
37
|
+
type: openGraph?.type || "website"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
let twitter = seo.twitter;
|
|
41
|
+
if (hasTwitterContent(twitter)) {
|
|
42
|
+
metadata.twitter = {
|
|
43
|
+
card: twitter?.cardType || "summary",
|
|
44
|
+
title: twitter?.title,
|
|
45
|
+
description: twitter?.description,
|
|
46
|
+
images: twitter?.image ? [twitter.image] : void 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return metadata;
|
|
50
|
+
}
|
|
51
|
+
function getWeaverseNextSeoMetadata(data) {
|
|
52
|
+
if (!data) {
|
|
53
|
+
return formatWeaverseNextSeoMetadata(null);
|
|
54
|
+
}
|
|
55
|
+
let page = "items" in data && "id" in data ? data : data.page ?? null;
|
|
56
|
+
return formatWeaverseNextSeoMetadata(page?.seo ?? null);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/server/configs.ts
|
|
60
|
+
var DEFAULT_WEAVERSE_HOST = "https://studio.weaverse.io";
|
|
61
|
+
var DEFAULT_WEAVERSE_API_BASE = "https://api.weaverse.io";
|
|
62
|
+
var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
|
|
63
|
+
function isTrustedStudioHost(host) {
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = new URL(host);
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.protocol !== "https:") {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
let { hostname } = parsed;
|
|
74
|
+
return TRUSTED_STUDIO_DOMAINS.some(
|
|
75
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function toUrl(value) {
|
|
79
|
+
try {
|
|
80
|
+
return typeof value === "string" ? new URL(value, "http://localhost") : value;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function getContextSearchParams(context) {
|
|
86
|
+
if (context?.searchParams) {
|
|
87
|
+
return new URLSearchParams(context.searchParams);
|
|
88
|
+
}
|
|
89
|
+
if (context?.url) {
|
|
90
|
+
let url = toUrl(context.url);
|
|
91
|
+
if (url) {
|
|
92
|
+
return new URLSearchParams(url.searchParams);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return new URLSearchParams();
|
|
96
|
+
}
|
|
97
|
+
function lastParam(searchParams, key) {
|
|
98
|
+
let values = searchParams.getAll(key);
|
|
99
|
+
return values.length ? values.at(-1) : void 0;
|
|
100
|
+
}
|
|
101
|
+
function coerceBool(value) {
|
|
102
|
+
return value === "true";
|
|
103
|
+
}
|
|
104
|
+
function readEnv(env, key) {
|
|
105
|
+
let fromConfig = env?.[key];
|
|
106
|
+
if (fromConfig !== void 0) {
|
|
107
|
+
return fromConfig;
|
|
108
|
+
}
|
|
109
|
+
if (typeof process !== "undefined" && process.env) {
|
|
110
|
+
return process.env[key];
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
function getWeaverseNextConfigs(context, options = {}) {
|
|
115
|
+
let searchParams = getContextSearchParams(context);
|
|
116
|
+
let { env } = options;
|
|
117
|
+
let queryHost = lastParam(searchParams, "weaverseHost");
|
|
118
|
+
let trustedUrlHost = queryHost && isTrustedStudioHost(queryHost) ? new URL(queryHost).origin : "";
|
|
119
|
+
let configuredHost = options.weaverseHost || readEnv(env, "WEAVERSE_HOST") || "";
|
|
120
|
+
let weaverseHost = trustedUrlHost || configuredHost || DEFAULT_WEAVERSE_HOST;
|
|
121
|
+
let weaverseApiBase = trustedUrlHost || options.weaverseApiBase || readEnv(env, "WEAVERSE_PUBLIC_API_BASE") || (configuredHost && configuredHost !== DEFAULT_WEAVERSE_HOST ? configuredHost : DEFAULT_WEAVERSE_API_BASE);
|
|
122
|
+
let isRevisionPreview = context?.isRevisionPreview ?? searchParams.has("__revisionId");
|
|
123
|
+
let isDesignMode = context?.isDesignMode ?? coerceBool(lastParam(searchParams, "isDesignMode"));
|
|
124
|
+
let isPreviewMode = context?.isPreviewMode ?? coerceBool(lastParam(searchParams, "isPreviewMode"));
|
|
125
|
+
return {
|
|
126
|
+
queryProjectId: lastParam(searchParams, "weaverseProjectId") || "",
|
|
127
|
+
envProjectId: readEnv(env, "WEAVERSE_PROJECT_ID") || "",
|
|
128
|
+
weaverseHost,
|
|
129
|
+
weaverseApiBase,
|
|
130
|
+
weaverseApiKey: lastParam(searchParams, "weaverseApiKey") || readEnv(env, "WEAVERSE_API_KEY") || "",
|
|
131
|
+
weaverseVersion: lastParam(searchParams, "weaverseVersion") || options.weaverseVersion || "",
|
|
132
|
+
isDesignMode,
|
|
133
|
+
isPreviewMode,
|
|
134
|
+
isRevisionPreview,
|
|
135
|
+
sectionType: context?.sectionType || lastParam(searchParams, "sectionType") || "",
|
|
136
|
+
publicEnv: {
|
|
137
|
+
PUBLIC_STORE_DOMAIN: readEnv(env, "PUBLIC_STORE_DOMAIN") || "",
|
|
138
|
+
PUBLIC_STOREFRONT_API_TOKEN: readEnv(env, "PUBLIC_STOREFRONT_API_TOKEN") || ""
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/server/normalize-page-url.ts
|
|
144
|
+
var KEPT_PARAM_RE = /^(?:__revisionId|__weaverseDraftItem|isDesignMode|weaverse)/;
|
|
145
|
+
var DATA_SUFFIX = ".data";
|
|
146
|
+
function normalizeNextPageUrl(url) {
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = new URL(url);
|
|
150
|
+
} catch {
|
|
151
|
+
try {
|
|
152
|
+
parsed = new URL(url, "http://localhost");
|
|
153
|
+
} catch {
|
|
154
|
+
return url;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let kept = [...parsed.searchParams].filter(([key]) => KEPT_PARAM_RE.test(key));
|
|
158
|
+
kept.sort(([a], [b]) => a.localeCompare(b));
|
|
159
|
+
let search = new URLSearchParams(kept).toString();
|
|
160
|
+
let { pathname } = parsed;
|
|
161
|
+
if (pathname.endsWith(DATA_SUFFIX)) {
|
|
162
|
+
pathname = pathname.slice(0, -DATA_SUFFIX.length);
|
|
163
|
+
}
|
|
164
|
+
return `${parsed.origin}${pathname}${search ? `?${search}` : ""}`;
|
|
165
|
+
}
|
|
166
|
+
function resolveRequestUrl(context) {
|
|
167
|
+
if (context?.url) {
|
|
168
|
+
return typeof context.url === "string" ? context.url : context.url.toString();
|
|
169
|
+
}
|
|
170
|
+
let pathname = context?.pathname || "/";
|
|
171
|
+
let search = context?.searchParams ? new URLSearchParams(context.searchParams).toString() : "";
|
|
172
|
+
return `http://localhost${pathname}${search ? `?${search}` : ""}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/utils.ts
|
|
176
|
+
function generateDataFromSchema(schema) {
|
|
177
|
+
let data = {};
|
|
178
|
+
if (!schema) {
|
|
179
|
+
return data;
|
|
180
|
+
}
|
|
181
|
+
let groups = schema.settings ?? schema.inspector ?? [];
|
|
182
|
+
for (let group of groups) {
|
|
183
|
+
for (let input of group?.inputs ?? []) {
|
|
184
|
+
let { name, defaultValue } = input;
|
|
185
|
+
if (name && defaultValue !== null && defaultValue !== void 0) {
|
|
186
|
+
data[name] = defaultValue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return data;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/server/revalidate-handler.ts
|
|
194
|
+
var NO_STORE_HEADERS = {
|
|
195
|
+
"Content-Type": "application/json",
|
|
196
|
+
"Cache-Control": "no-store"
|
|
197
|
+
};
|
|
198
|
+
var STATUS_OK = 200;
|
|
199
|
+
var STATUS_BAD_REQUEST = 400;
|
|
200
|
+
var STATUS_NOT_FOUND = 404;
|
|
201
|
+
var STATUS_UNPROCESSABLE = 422;
|
|
202
|
+
var STATUS_SERVER_ERROR = 500;
|
|
203
|
+
function json(body, status) {
|
|
204
|
+
return new Response(JSON.stringify(body), {
|
|
205
|
+
status,
|
|
206
|
+
headers: NO_STORE_HEADERS
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function parseDraftItem(payload) {
|
|
210
|
+
if (typeof payload !== "object" || payload === null) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
let draftItem = payload.draftItem;
|
|
214
|
+
if (typeof draftItem !== "object" || draftItem === null) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
let { id, type } = draftItem;
|
|
218
|
+
if (typeof id !== "string" || !id || typeof type !== "string" || !type) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return draftItem;
|
|
222
|
+
}
|
|
223
|
+
function createWeaverseNextRevalidateHandler(config) {
|
|
224
|
+
return {
|
|
225
|
+
POST: async (request) => {
|
|
226
|
+
let payload;
|
|
227
|
+
try {
|
|
228
|
+
payload = await request.json();
|
|
229
|
+
} catch {
|
|
230
|
+
return json({ error: "invalid-payload" }, STATUS_BAD_REQUEST);
|
|
231
|
+
}
|
|
232
|
+
let draftItem = parseDraftItem(payload);
|
|
233
|
+
if (!draftItem) {
|
|
234
|
+
return json({ error: "invalid-payload" }, STATUS_BAD_REQUEST);
|
|
235
|
+
}
|
|
236
|
+
let client;
|
|
237
|
+
try {
|
|
238
|
+
client = await config.getClient(request);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error("\u274C Revalidate handler getClient failed.", error);
|
|
241
|
+
return json({ error: "client-init-failed" }, STATUS_SERVER_ERROR);
|
|
242
|
+
}
|
|
243
|
+
let component = client.components.find(
|
|
244
|
+
(candidate) => candidate?.schema?.type === draftItem.type
|
|
245
|
+
);
|
|
246
|
+
if (!component) {
|
|
247
|
+
return json({ error: "unknown-component-type" }, STATUS_NOT_FOUND);
|
|
248
|
+
}
|
|
249
|
+
if (typeof component.loader !== "function") {
|
|
250
|
+
return json({ error: "missing-loader" }, STATUS_UNPROCESSABLE);
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
let loaderData = await component.loader({
|
|
254
|
+
data: {
|
|
255
|
+
...generateDataFromSchema(component.schema),
|
|
256
|
+
...draftItem.data
|
|
257
|
+
},
|
|
258
|
+
weaverse: client,
|
|
259
|
+
context: client.requestContext,
|
|
260
|
+
commerce: client.commerce
|
|
261
|
+
});
|
|
262
|
+
return json({ loaderData: loaderData ?? null }, STATUS_OK);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error(
|
|
265
|
+
"\u274C Item loader revalidation failed.",
|
|
266
|
+
draftItem.type,
|
|
267
|
+
draftItem.id,
|
|
268
|
+
error
|
|
269
|
+
);
|
|
270
|
+
return json({ error: "loader-failed" }, STATUS_SERVER_ERROR);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/loader.ts
|
|
277
|
+
function isItemNode(node) {
|
|
278
|
+
return typeof node === "object" && node !== null && typeof node.type === "string";
|
|
279
|
+
}
|
|
280
|
+
function cloneItem(item) {
|
|
281
|
+
return {
|
|
282
|
+
...item,
|
|
283
|
+
children: Array.isArray(item.children) ? item.children.map(
|
|
284
|
+
(child) => isItemNode(child) ? cloneItem(child) : { ...child }
|
|
285
|
+
) : item.children
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async function runWeaverseComponentLoaders(args) {
|
|
289
|
+
let { client, context, commerce } = args;
|
|
290
|
+
let sourceData = args.data ?? client.data;
|
|
291
|
+
if (!sourceData) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
if (!sourceData.page.items.length) {
|
|
295
|
+
return sourceData;
|
|
296
|
+
}
|
|
297
|
+
let data = {
|
|
298
|
+
...sourceData,
|
|
299
|
+
page: {
|
|
300
|
+
...sourceData.page,
|
|
301
|
+
items: sourceData.page.items.map(cloneItem)
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
let items = data.page.items;
|
|
305
|
+
let componentMap = /* @__PURE__ */ new Map();
|
|
306
|
+
for (let component of client.components) {
|
|
307
|
+
if (component?.schema?.type) {
|
|
308
|
+
componentMap.set(component.schema.type, component);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let visited = /* @__PURE__ */ new Set();
|
|
312
|
+
let resolvedContext = context ?? client.requestContext;
|
|
313
|
+
let resolvedCommerce = commerce ?? client.commerce;
|
|
314
|
+
let resolvedWeaverse = resolvedCommerce === client.commerce ? client : {
|
|
315
|
+
...client,
|
|
316
|
+
commerce: resolvedCommerce,
|
|
317
|
+
storefront: resolvedCommerce?.storefront
|
|
318
|
+
};
|
|
319
|
+
async function walk(nodes) {
|
|
320
|
+
for (let item of nodes) {
|
|
321
|
+
if (!item || visited.has(item.id)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
visited.add(item.id);
|
|
325
|
+
let component = componentMap.get(item.type);
|
|
326
|
+
if (component?.loader) {
|
|
327
|
+
try {
|
|
328
|
+
item.loaderData = await component.loader({
|
|
329
|
+
data: { ...generateDataFromSchema(component.schema), ...item.data },
|
|
330
|
+
weaverse: resolvedWeaverse,
|
|
331
|
+
context: resolvedContext,
|
|
332
|
+
commerce: resolvedCommerce
|
|
333
|
+
});
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.warn("\u274C Item loader run failed.", item.type, item.id, error);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
let children = item.children;
|
|
339
|
+
if (Array.isArray(children)) {
|
|
340
|
+
let inlineChildren = children.filter(
|
|
341
|
+
isItemNode
|
|
342
|
+
);
|
|
343
|
+
if (inlineChildren.length) {
|
|
344
|
+
await walk(inlineChildren);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
await walk(items);
|
|
350
|
+
return data;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/request-info.ts
|
|
354
|
+
function toUrl2(value) {
|
|
355
|
+
return typeof value === "string" ? new URL(value, "http://localhost") : value;
|
|
356
|
+
}
|
|
357
|
+
function getSearchParamsFromContext(context) {
|
|
358
|
+
if (context?.searchParams) {
|
|
359
|
+
return new URLSearchParams(context.searchParams);
|
|
360
|
+
}
|
|
361
|
+
if (context?.url) {
|
|
362
|
+
let url = toUrl2(context.url);
|
|
363
|
+
return new URLSearchParams(url.searchParams);
|
|
364
|
+
}
|
|
365
|
+
return new URLSearchParams();
|
|
366
|
+
}
|
|
367
|
+
function getPathnameFromContext(context) {
|
|
368
|
+
if (context?.pathname) {
|
|
369
|
+
return context.pathname;
|
|
370
|
+
}
|
|
371
|
+
if (context?.url) {
|
|
372
|
+
let url = toUrl2(context.url);
|
|
373
|
+
return url.pathname || "/";
|
|
374
|
+
}
|
|
375
|
+
return "/";
|
|
376
|
+
}
|
|
377
|
+
function coerceQueryValue(value) {
|
|
378
|
+
if (value === "true") {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
if (value === "false") {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
function toQueryRecord(searchParams) {
|
|
387
|
+
let queries = {};
|
|
388
|
+
for (let [key, value] of searchParams.entries()) {
|
|
389
|
+
queries[key] = coerceQueryValue(value);
|
|
390
|
+
}
|
|
391
|
+
return queries;
|
|
392
|
+
}
|
|
393
|
+
var TRANSIENT_STUDIO_PARAMS = /* @__PURE__ */ new Set([
|
|
394
|
+
"weaverseDraftItem",
|
|
395
|
+
// Legacy name from the original design-mode draft-item spike. Keep it out of
|
|
396
|
+
// runtime requestInfo too, in case an older Builder bundle is in front of a
|
|
397
|
+
// newer Next storefront.
|
|
398
|
+
"__weaverseDraftItem"
|
|
399
|
+
]);
|
|
400
|
+
function getRequestInfoSearchParams(context) {
|
|
401
|
+
let searchParams = new URLSearchParams(getSearchParamsFromContext(context));
|
|
402
|
+
for (let key of TRANSIENT_STUDIO_PARAMS) {
|
|
403
|
+
searchParams.delete(key);
|
|
404
|
+
}
|
|
405
|
+
return searchParams;
|
|
406
|
+
}
|
|
407
|
+
function buildWeaverseNextRequestInfo(context) {
|
|
408
|
+
let searchParams = getRequestInfoSearchParams(context);
|
|
409
|
+
let search = searchParams.toString();
|
|
410
|
+
return {
|
|
411
|
+
pathname: getPathnameFromContext(context),
|
|
412
|
+
search: search ? `?${search}` : "",
|
|
413
|
+
queries: toQueryRecord(searchParams),
|
|
414
|
+
...context?.i18n ? { i18n: context.i18n } : {}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/server/server-client.ts
|
|
419
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
420
|
+
var RANDOM_ID_RADIX = 36;
|
|
421
|
+
var RANDOM_ID_SLICE_START = 2;
|
|
422
|
+
var MAX_CUSTOM_PAGE_PAGES = 100;
|
|
423
|
+
var JSON_HEADERS = {
|
|
424
|
+
"Content-Type": "application/json",
|
|
425
|
+
Accept: "application/json",
|
|
426
|
+
"Accept-Encoding": "gzip, deflate, br"
|
|
427
|
+
};
|
|
428
|
+
function asThemeSchema(schema) {
|
|
429
|
+
return schema && typeof schema === "object" ? schema : void 0;
|
|
430
|
+
}
|
|
431
|
+
function generateUuid() {
|
|
432
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
433
|
+
return crypto.randomUUID();
|
|
434
|
+
}
|
|
435
|
+
return `id-${Math.random().toString(RANDOM_ID_RADIX).slice(RANDOM_ID_SLICE_START)}`;
|
|
436
|
+
}
|
|
437
|
+
function normalizeWeaverseNextPageAssignment(input) {
|
|
438
|
+
if (!input || typeof input !== "object") {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
let { projectId, type, handle, locale, meta } = input;
|
|
442
|
+
if (typeof projectId !== "string" || typeof type !== "string" || typeof handle !== "string") {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
let normalized = {
|
|
446
|
+
projectId,
|
|
447
|
+
type,
|
|
448
|
+
handle,
|
|
449
|
+
locale: typeof locale === "string" ? locale : ""
|
|
450
|
+
};
|
|
451
|
+
if (meta && typeof meta === "object") {
|
|
452
|
+
normalized.meta = meta;
|
|
453
|
+
}
|
|
454
|
+
return normalized;
|
|
455
|
+
}
|
|
456
|
+
var NextServerClient = class {
|
|
457
|
+
components;
|
|
458
|
+
commerce;
|
|
459
|
+
requestContext;
|
|
460
|
+
themeSchema;
|
|
461
|
+
themeSettings;
|
|
462
|
+
data = null;
|
|
463
|
+
dataContext = {};
|
|
464
|
+
configs;
|
|
465
|
+
_projectIdInput;
|
|
466
|
+
_baseConfigs;
|
|
467
|
+
_fetch;
|
|
468
|
+
_cache;
|
|
469
|
+
_fetchTimeoutMs;
|
|
470
|
+
_resolvedProjectId;
|
|
471
|
+
constructor(config) {
|
|
472
|
+
this.components = config.components;
|
|
473
|
+
this.commerce = config.commerce;
|
|
474
|
+
this.requestContext = config.requestContext;
|
|
475
|
+
this.themeSchema = config.themeSchema;
|
|
476
|
+
this.themeSettings = {
|
|
477
|
+
...generateDataFromSchema(config.themeSchema),
|
|
478
|
+
...config.themeSettings
|
|
479
|
+
};
|
|
480
|
+
this._projectIdInput = config.projectId;
|
|
481
|
+
this._fetch = config.fetch ?? globalThis.fetch;
|
|
482
|
+
this._cache = config.cache;
|
|
483
|
+
this._fetchTimeoutMs = config.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
484
|
+
this._baseConfigs = getWeaverseNextConfigs(config.requestContext, {
|
|
485
|
+
env: config.env,
|
|
486
|
+
weaverseHost: config.weaverseHost,
|
|
487
|
+
weaverseApiBase: config.weaverseApiBase,
|
|
488
|
+
weaverseVersion: config.weaverseVersion
|
|
489
|
+
});
|
|
490
|
+
this.configs = this._buildPublicConfigs(
|
|
491
|
+
this._baseConfigs.queryProjectId || (typeof this._projectIdInput === "string" ? this._projectIdInput.trim() : "") || this._baseConfigs.envProjectId
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
get projectId() {
|
|
495
|
+
return this.configs.projectId;
|
|
496
|
+
}
|
|
497
|
+
/** Compatibility alias so loaders can call `weaverse.storefront.query(...)`. */
|
|
498
|
+
get storefront() {
|
|
499
|
+
return this.commerce?.storefront;
|
|
500
|
+
}
|
|
501
|
+
_buildPublicConfigs(projectId) {
|
|
502
|
+
let base = this._baseConfigs;
|
|
503
|
+
return {
|
|
504
|
+
projectId,
|
|
505
|
+
weaverseHost: base.weaverseHost,
|
|
506
|
+
weaverseApiBase: base.weaverseApiBase,
|
|
507
|
+
weaversePublicApiBase: base.weaverseApiBase,
|
|
508
|
+
weaverseVersion: base.weaverseVersion,
|
|
509
|
+
isDesignMode: base.isDesignMode,
|
|
510
|
+
isPreviewMode: base.isPreviewMode,
|
|
511
|
+
isRevisionPreview: base.isRevisionPreview,
|
|
512
|
+
sectionType: base.sectionType,
|
|
513
|
+
publicEnv: base.publicEnv
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
resolveProjectId = async () => {
|
|
517
|
+
if (this._resolvedProjectId !== void 0) {
|
|
518
|
+
return this._resolvedProjectId;
|
|
519
|
+
}
|
|
520
|
+
let resolved = this._baseConfigs.queryProjectId;
|
|
521
|
+
if (!resolved && typeof this._projectIdInput === "function") {
|
|
522
|
+
try {
|
|
523
|
+
let value = await this._projectIdInput(this.requestContext);
|
|
524
|
+
if (typeof value === "string" && value.trim()) {
|
|
525
|
+
resolved = value.trim();
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.warn(
|
|
529
|
+
"[WeaverseNextServerClient] projectId resolver threw, falling back.",
|
|
530
|
+
error
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (!resolved && typeof this._projectIdInput === "string") {
|
|
535
|
+
resolved = this._projectIdInput.trim();
|
|
536
|
+
}
|
|
537
|
+
if (!resolved) {
|
|
538
|
+
resolved = this._baseConfigs.envProjectId;
|
|
539
|
+
}
|
|
540
|
+
this._resolvedProjectId = resolved;
|
|
541
|
+
this.configs = this._buildPublicConfigs(resolved);
|
|
542
|
+
return resolved;
|
|
543
|
+
};
|
|
544
|
+
fetchWithCache = async (url, options = {}) => {
|
|
545
|
+
let { revalidate, tags, ...init } = options;
|
|
546
|
+
let requestInit = { ...init };
|
|
547
|
+
let { isDesignMode, isRevisionPreview } = this._baseConfigs;
|
|
548
|
+
if (isDesignMode || isRevisionPreview) {
|
|
549
|
+
requestInit.cache = "no-store";
|
|
550
|
+
} else if (requestInit.cache === void 0) {
|
|
551
|
+
let effectiveRevalidate = revalidate ?? this._cache?.revalidate;
|
|
552
|
+
let effectiveTags = tags ?? this._cache?.tags;
|
|
553
|
+
if (effectiveRevalidate !== void 0 || effectiveTags?.length) {
|
|
554
|
+
requestInit.next = {
|
|
555
|
+
...effectiveRevalidate === void 0 ? {} : { revalidate: effectiveRevalidate },
|
|
556
|
+
...effectiveTags?.length ? { tags: effectiveTags } : {}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
let controller = new AbortController();
|
|
561
|
+
let timeoutId = setTimeout(() => controller.abort(), this._fetchTimeoutMs);
|
|
562
|
+
try {
|
|
563
|
+
let response = await this._fetch(url, {
|
|
564
|
+
...requestInit,
|
|
565
|
+
signal: requestInit.signal ?? controller.signal
|
|
566
|
+
});
|
|
567
|
+
if (!response.ok) {
|
|
568
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
569
|
+
}
|
|
570
|
+
return await response.json();
|
|
571
|
+
} finally {
|
|
572
|
+
clearTimeout(timeoutId);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
_buildCustomPagesUrl(projectId, options, cursor) {
|
|
576
|
+
let params = new URLSearchParams();
|
|
577
|
+
if (options.locale) {
|
|
578
|
+
params.set("locale", options.locale);
|
|
579
|
+
}
|
|
580
|
+
if (options.limit) {
|
|
581
|
+
params.set("limit", String(options.limit));
|
|
582
|
+
}
|
|
583
|
+
if (cursor) {
|
|
584
|
+
params.set("cursor", cursor);
|
|
585
|
+
}
|
|
586
|
+
let qs = params.toString();
|
|
587
|
+
return `${this._baseConfigs.weaverseApiBase}/api/public/v1/projects/${projectId}/custom-pages${qs ? `?${qs}` : ""}`;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Fetch all published custom pages for sitemap generation. Mirrors Hydrogen's
|
|
591
|
+
* helper but uses Next's fetch cache knobs (`revalidate` / `tags`). It returns
|
|
592
|
+
* accumulated results on partial pagination failure so a single API hiccup
|
|
593
|
+
* does not break sitemap generation entirely.
|
|
594
|
+
*/
|
|
595
|
+
fetchCustomPages = async (options = {}) => {
|
|
596
|
+
let projectId = await this.resolveProjectId();
|
|
597
|
+
let entries = [];
|
|
598
|
+
if (!projectId) {
|
|
599
|
+
console.warn(
|
|
600
|
+
"[WeaverseNext] Failed to fetch custom pages: missing projectId"
|
|
601
|
+
);
|
|
602
|
+
return entries;
|
|
603
|
+
}
|
|
604
|
+
let cursor = null;
|
|
605
|
+
for (let page = 0; page < MAX_CUSTOM_PAGE_PAGES; page += 1) {
|
|
606
|
+
let url = this._buildCustomPagesUrl(projectId, options, cursor);
|
|
607
|
+
try {
|
|
608
|
+
let result = await this.fetchWithCache(url, {
|
|
609
|
+
revalidate: options.revalidate,
|
|
610
|
+
tags: options.tags
|
|
611
|
+
});
|
|
612
|
+
entries.push(...result.data ?? []);
|
|
613
|
+
if (!result.nextCursor) {
|
|
614
|
+
return entries;
|
|
615
|
+
}
|
|
616
|
+
cursor = result.nextCursor;
|
|
617
|
+
} catch (error) {
|
|
618
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
619
|
+
console.warn(
|
|
620
|
+
entries.length === 0 ? `[WeaverseNext] Failed to fetch custom pages: ${message}` : `[WeaverseNext] Custom pages pagination interrupted: ${message}`
|
|
621
|
+
);
|
|
622
|
+
return entries;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
console.warn("[WeaverseNext] Custom pages pagination cap reached");
|
|
626
|
+
return entries;
|
|
627
|
+
};
|
|
628
|
+
_generateFallbackPage(message) {
|
|
629
|
+
let rootId = generateUuid();
|
|
630
|
+
return {
|
|
631
|
+
id: `fallback_page_${rootId}`,
|
|
632
|
+
rootId,
|
|
633
|
+
name: "Default Page",
|
|
634
|
+
items: [
|
|
635
|
+
{
|
|
636
|
+
type: "main",
|
|
637
|
+
id: rootId,
|
|
638
|
+
data: { dangerouslySetInnerHTML: { __html: message } }
|
|
639
|
+
}
|
|
640
|
+
]
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Recursively materialize a section preset (and its preset children) into a
|
|
645
|
+
* flat list of page items, returning the new item id. Ported from Hydrogen's
|
|
646
|
+
* `recursivelyAddDataItem`. Returns `undefined` when the type isn't registered.
|
|
647
|
+
*/
|
|
648
|
+
_buildPreviewItems(type, items, initData = {}) {
|
|
649
|
+
let component = this.components.find((c) => c.schema?.type === type);
|
|
650
|
+
if (!component) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
let { children, ...data } = component.schema?.presets ?? {};
|
|
654
|
+
let childIds = [];
|
|
655
|
+
if (children) {
|
|
656
|
+
for (let child of children) {
|
|
657
|
+
let { type: childType, children: _children, ...childData } = child;
|
|
658
|
+
let childId = this._buildPreviewItems(childType, items, childData);
|
|
659
|
+
if (childId) {
|
|
660
|
+
childIds.push({ id: childId });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
let id = generateUuid();
|
|
665
|
+
items.push({ id, type, data: { ...data, ...initData }, children: childIds });
|
|
666
|
+
return id;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Synthetic loader data for Studio "section preview" mode. Mirrors Hydrogen's
|
|
670
|
+
* `getPreviewData`: it builds a single-section page from the requested
|
|
671
|
+
* `sectionType`'s presets without hitting `/api/public/project`, so preview
|
|
672
|
+
* works even without a resolved `projectId`.
|
|
673
|
+
*/
|
|
674
|
+
_generatePreviewData(projectId) {
|
|
675
|
+
let { sectionType, weaverseHost } = this._baseConfigs;
|
|
676
|
+
let items = [];
|
|
677
|
+
let sectionId = sectionType ? this._buildPreviewItems(sectionType, items) : void 0;
|
|
678
|
+
let configs = {
|
|
679
|
+
...this._buildPublicConfigs(projectId),
|
|
680
|
+
isPreviewMode: true,
|
|
681
|
+
weaverseHost,
|
|
682
|
+
requestInfo: buildWeaverseNextRequestInfo(this.requestContext)
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
project: {
|
|
686
|
+
id: projectId,
|
|
687
|
+
weaverseShopId: "shop-id",
|
|
688
|
+
name: "Section Preview"
|
|
689
|
+
},
|
|
690
|
+
configs,
|
|
691
|
+
page: {
|
|
692
|
+
id: "weaverse-preview-page",
|
|
693
|
+
name: "Preview section",
|
|
694
|
+
rootId: "1",
|
|
695
|
+
items: [
|
|
696
|
+
{
|
|
697
|
+
id: "1",
|
|
698
|
+
type: "main",
|
|
699
|
+
data: {},
|
|
700
|
+
children: sectionId ? [{ id: sectionId }] : []
|
|
701
|
+
},
|
|
702
|
+
...items
|
|
703
|
+
]
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
_getLoadPageInput(input) {
|
|
708
|
+
let {
|
|
709
|
+
cache,
|
|
710
|
+
projectId: routeProjectId,
|
|
711
|
+
...rest
|
|
712
|
+
} = input;
|
|
713
|
+
let params = {};
|
|
714
|
+
for (let [key, value] of Object.entries(rest)) {
|
|
715
|
+
if (value !== void 0) {
|
|
716
|
+
params[key] = value;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return { cache, params, routeProjectId };
|
|
720
|
+
}
|
|
721
|
+
async _resolveLoadPageProjectId(routeProjectId) {
|
|
722
|
+
let effectiveProjectId = typeof routeProjectId === "string" && routeProjectId.trim() || await this.resolveProjectId();
|
|
723
|
+
if (effectiveProjectId) {
|
|
724
|
+
this.configs = this._buildPublicConfigs(effectiveProjectId);
|
|
725
|
+
}
|
|
726
|
+
return effectiveProjectId;
|
|
727
|
+
}
|
|
728
|
+
_fetchPagePayload(projectId, params, cache) {
|
|
729
|
+
let i18n = this.requestContext?.i18n ?? this.commerce?.storefront?.i18n;
|
|
730
|
+
let url = normalizeNextPageUrl(resolveRequestUrl(this.requestContext));
|
|
731
|
+
let apiUrl = `${this._baseConfigs.weaverseApiBase}/api/public/project`;
|
|
732
|
+
let userAgent = this.requestContext?.headers?.get("user-agent") ?? "";
|
|
733
|
+
return this.fetchWithCache(apiUrl, {
|
|
734
|
+
method: "POST",
|
|
735
|
+
body: JSON.stringify({
|
|
736
|
+
projectId,
|
|
737
|
+
url,
|
|
738
|
+
i18n,
|
|
739
|
+
params,
|
|
740
|
+
isDesignMode: this._baseConfigs.isDesignMode
|
|
741
|
+
}),
|
|
742
|
+
headers: { ...JSON_HEADERS, "X-Visitor-UA": userAgent },
|
|
743
|
+
...cache ?? {}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
_assertNoPageError(payload) {
|
|
747
|
+
if ("error" in payload && typeof payload.error === "string") {
|
|
748
|
+
throw new Error(payload.error);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
_createLoaderData(payload, projectId) {
|
|
752
|
+
this._assertNoPageError(payload);
|
|
753
|
+
let page = payload.page;
|
|
754
|
+
let project = payload.project;
|
|
755
|
+
let pageAssignment = normalizeWeaverseNextPageAssignment(
|
|
756
|
+
payload.pageAssignment
|
|
757
|
+
);
|
|
758
|
+
page ??= this._generateFallbackPage(
|
|
759
|
+
'<div style="text-align: center;">Please add new section to start.</div>'
|
|
760
|
+
);
|
|
761
|
+
project ??= {
|
|
762
|
+
id: projectId,
|
|
763
|
+
name: "Weaverse project not found.",
|
|
764
|
+
weaverseShopId: ""
|
|
765
|
+
};
|
|
766
|
+
if (Array.isArray(page.items) && page.items.length === 0) {
|
|
767
|
+
page.items.push({
|
|
768
|
+
type: "main",
|
|
769
|
+
id: page.rootId || generateUuid(),
|
|
770
|
+
data: {}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
page,
|
|
775
|
+
project,
|
|
776
|
+
pageAssignment,
|
|
777
|
+
configs: {
|
|
778
|
+
...this._buildPublicConfigs(projectId),
|
|
779
|
+
requestInfo: buildWeaverseNextRequestInfo(this.requestContext)
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
async _runLoaders(data) {
|
|
784
|
+
this.data = data;
|
|
785
|
+
let withLoaders = await runWeaverseComponentLoaders({ client: this, data });
|
|
786
|
+
this.data = withLoaders ?? data;
|
|
787
|
+
return this.data;
|
|
788
|
+
}
|
|
789
|
+
loadPage = async (input = {}) => {
|
|
790
|
+
try {
|
|
791
|
+
let { cache, params, routeProjectId } = this._getLoadPageInput(input);
|
|
792
|
+
let projectId = await this._resolveLoadPageProjectId(routeProjectId);
|
|
793
|
+
if (this._baseConfigs.isPreviewMode) {
|
|
794
|
+
let previewData = this._generatePreviewData(projectId || "x");
|
|
795
|
+
this.data = previewData;
|
|
796
|
+
return previewData;
|
|
797
|
+
}
|
|
798
|
+
if (!projectId) {
|
|
799
|
+
throw new Error("Missing Weaverse projectId!");
|
|
800
|
+
}
|
|
801
|
+
let payload = await this._fetchPagePayload(projectId, params, cache);
|
|
802
|
+
let data = this._createLoaderData(payload, projectId);
|
|
803
|
+
return await this._runLoaders(data);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
console.error("\u274C Page load failed", {
|
|
806
|
+
url: this.requestContext?.url,
|
|
807
|
+
projectId: input.projectId || this.configs.projectId,
|
|
808
|
+
message: error instanceof Error ? error.message : String(error)
|
|
809
|
+
});
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
loadThemeSettings = async (optionsInput = {}) => {
|
|
814
|
+
let options = optionsInput;
|
|
815
|
+
let themeSchema = asThemeSchema(this.themeSchema);
|
|
816
|
+
let defaultThemeSettings = generateDataFromSchema(this.themeSchema);
|
|
817
|
+
let staticContent = themeSchema?.i18n?.staticContent;
|
|
818
|
+
try {
|
|
819
|
+
let projectId = await this.resolveProjectId();
|
|
820
|
+
if (!projectId) {
|
|
821
|
+
throw new Error("Missing Weaverse projectId!");
|
|
822
|
+
}
|
|
823
|
+
let { isDesignMode } = this._baseConfigs;
|
|
824
|
+
let url = `${this._baseConfigs.weaverseApiBase}/api/public/project_configs`;
|
|
825
|
+
let result = await this.fetchWithCache(
|
|
826
|
+
url,
|
|
827
|
+
{
|
|
828
|
+
method: "POST",
|
|
829
|
+
body: JSON.stringify({ projectId, isDesignMode }),
|
|
830
|
+
headers: JSON_HEADERS,
|
|
831
|
+
revalidate: options.revalidate,
|
|
832
|
+
tags: options.tags
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
if (typeof result !== "object" || result === null) {
|
|
836
|
+
result = { theme: defaultThemeSettings };
|
|
837
|
+
}
|
|
838
|
+
if (themeSchema?.settings || themeSchema?.inspector) {
|
|
839
|
+
if (!result.theme || typeof result.theme !== "object") {
|
|
840
|
+
result.theme = defaultThemeSettings;
|
|
841
|
+
} else {
|
|
842
|
+
result.theme = { ...defaultThemeSettings, ...result.theme };
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (staticContent) {
|
|
846
|
+
result.staticContent = staticContent;
|
|
847
|
+
}
|
|
848
|
+
if (isDesignMode) {
|
|
849
|
+
let serializableSchema = themeSchema ? {
|
|
850
|
+
...themeSchema,
|
|
851
|
+
settings: themeSchema.settings?.map((group) => ({
|
|
852
|
+
...group,
|
|
853
|
+
inputs: group?.inputs?.map(
|
|
854
|
+
(inputItem) => typeof inputItem?.condition === "function" ? {
|
|
855
|
+
...inputItem,
|
|
856
|
+
condition: inputItem.condition.toString()
|
|
857
|
+
} : inputItem
|
|
858
|
+
)
|
|
859
|
+
}))
|
|
860
|
+
} : void 0;
|
|
861
|
+
result = {
|
|
862
|
+
...result,
|
|
863
|
+
schema: serializableSchema,
|
|
864
|
+
publicEnv: this._baseConfigs.publicEnv
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
return result;
|
|
868
|
+
} catch (error) {
|
|
869
|
+
let errorDetails = error instanceof Error ? error.message : String(error);
|
|
870
|
+
console.error("Unable to load theme settings", errorDetails);
|
|
871
|
+
return {
|
|
872
|
+
theme: defaultThemeSettings,
|
|
873
|
+
staticContent,
|
|
874
|
+
_error: errorDetails,
|
|
875
|
+
_loadFailed: true
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
function createWeaverseNextServerClient(config) {
|
|
881
|
+
return new NextServerClient(config);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
exports.createWeaverseNextRevalidateHandler = createWeaverseNextRevalidateHandler;
|
|
885
|
+
exports.createWeaverseNextServerClient = createWeaverseNextServerClient;
|
|
886
|
+
exports.formatWeaverseNextSeoMetadata = formatWeaverseNextSeoMetadata;
|
|
887
|
+
exports.getWeaverseNextConfigs = getWeaverseNextConfigs;
|
|
888
|
+
exports.getWeaverseNextSeoMetadata = getWeaverseNextSeoMetadata;
|
|
889
|
+
exports.normalizeNextPageUrl = normalizeNextPageUrl;
|
|
890
|
+
exports.resolveRequestUrl = resolveRequestUrl;
|
|
891
|
+
//# sourceMappingURL=server.js.map
|
|
892
|
+
//# sourceMappingURL=server.js.map
|