@uniweb/runtime 0.17.0 → 0.17.2
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/ssr.js +51 -39
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/background-shared.js +72 -0
- package/src/collect-records.js +48 -15
- package/src/components/Background.jsx +4 -39
- package/src/default-fetcher.js +50 -12
- package/src/isolate-api.js +1 -1
- package/src/ssr-renderer.js +2 -32
package/dist/ssr.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, crea
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { renderToString } from "react-dom/server";
|
|
4
4
|
import { sectionDomId } from "@uniweb/core/section-id";
|
|
5
|
+
import { applyBasePath } from "@uniweb/core/base-path";
|
|
5
6
|
import { routePatternToRegex, decodeRouteValue, splitPathCapture } from "@uniweb/core/route-match";
|
|
6
7
|
import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
|
|
7
8
|
import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
|
|
@@ -249,6 +250,23 @@ function getComponentMeta(componentName) {
|
|
|
249
250
|
function getComponentDefaults(componentName) {
|
|
250
251
|
return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
|
|
251
252
|
}
|
|
253
|
+
function siteUrl(url) {
|
|
254
|
+
return applyBasePath(url, globalThis.uniweb?.activeWebsite?.basePath || "");
|
|
255
|
+
}
|
|
256
|
+
function withOpacity(color, opacity) {
|
|
257
|
+
if (typeof color !== "string" || !color) return color;
|
|
258
|
+
if (color.startsWith("#")) {
|
|
259
|
+
const r = parseInt(color.slice(1, 3), 16);
|
|
260
|
+
const g = parseInt(color.slice(3, 5), 16);
|
|
261
|
+
const b = parseInt(color.slice(5, 7), 16);
|
|
262
|
+
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
263
|
+
}
|
|
264
|
+
if (color.startsWith("rgb")) {
|
|
265
|
+
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
266
|
+
if (match) return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`;
|
|
267
|
+
}
|
|
268
|
+
return color;
|
|
269
|
+
}
|
|
252
270
|
function default404Html(basePath = "") {
|
|
253
271
|
const homeHref = basePath ? `${basePath}/` : "/";
|
|
254
272
|
return `<div class="page-not-found" style="min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center"><h1 style="font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem">404</h1><p style="color:#64748b;margin-bottom:2rem">Page not found</p><a href="${homeHref}" style="color:#3b82f6;text-decoration:underline">Go to homepage</a></div>`;
|
|
@@ -395,28 +413,6 @@ function getWrapperProps(block) {
|
|
|
395
413
|
}
|
|
396
414
|
return { id: sectionDomId(block), style, className, background };
|
|
397
415
|
}
|
|
398
|
-
function withOpacity(color, opacity) {
|
|
399
|
-
if (color.startsWith("#")) {
|
|
400
|
-
const r = parseInt(color.slice(1, 3), 16);
|
|
401
|
-
const g = parseInt(color.slice(3, 5), 16);
|
|
402
|
-
const b = parseInt(color.slice(5, 7), 16);
|
|
403
|
-
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
404
|
-
}
|
|
405
|
-
if (color.startsWith("rgb")) {
|
|
406
|
-
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
407
|
-
if (match) {
|
|
408
|
-
return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return color;
|
|
412
|
-
}
|
|
413
|
-
function resolveUrl(url) {
|
|
414
|
-
if (!url || !url.startsWith("/")) return url;
|
|
415
|
-
const basePath = globalThis.uniweb?.activeWebsite?.basePath || "";
|
|
416
|
-
if (!basePath) return url;
|
|
417
|
-
if (url.startsWith(basePath + "/") || url === basePath) return url;
|
|
418
|
-
return basePath + url;
|
|
419
|
-
}
|
|
420
416
|
function renderBackground(background) {
|
|
421
417
|
if (!background?.mode) return null;
|
|
422
418
|
const containerStyle = {
|
|
@@ -473,7 +469,7 @@ function renderBackground(background) {
|
|
|
473
469
|
style: {
|
|
474
470
|
position: "absolute",
|
|
475
471
|
inset: "0",
|
|
476
|
-
backgroundImage: `url(${
|
|
472
|
+
backgroundImage: `url(${siteUrl(img.src)})`,
|
|
477
473
|
backgroundPosition: img.position || "center",
|
|
478
474
|
backgroundSize: img.size || "cover",
|
|
479
475
|
backgroundRepeat: "no-repeat"
|
|
@@ -983,7 +979,7 @@ function renameOperators(where) {
|
|
|
983
979
|
}
|
|
984
980
|
return out;
|
|
985
981
|
}
|
|
986
|
-
const
|
|
982
|
+
const DEFAULT_MAX_PAGES = 20;
|
|
987
983
|
async function flushAsked(url, queue, doFetch) {
|
|
988
984
|
const pending = /* @__PURE__ */ new Map();
|
|
989
985
|
const body = {};
|
|
@@ -1016,7 +1012,10 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1016
1012
|
parsed = await response.json();
|
|
1017
1013
|
} catch (error) {
|
|
1018
1014
|
const message = error?.name === "AbortError" ? "aborted" : error?.message || String(error);
|
|
1019
|
-
for (const entry of queue)
|
|
1015
|
+
for (const entry of queue) {
|
|
1016
|
+
const held = Array.isArray(entry.collected) && entry.collected.length ? entry.collected : null;
|
|
1017
|
+
entry.resolve(held ? { data: held, error: message, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error: message });
|
|
1018
|
+
}
|
|
1020
1019
|
return;
|
|
1021
1020
|
}
|
|
1022
1021
|
const data = parsed && typeof parsed.data === "object" && parsed.data ? parsed.data : {};
|
|
@@ -1026,10 +1025,11 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1026
1025
|
const limits = parsed && typeof parsed.limits === "object" && parsed.limits ? parsed.limits : {};
|
|
1027
1026
|
queue.forEach((entry, i) => {
|
|
1028
1027
|
const key = keys[i];
|
|
1028
|
+
const held = Array.isArray(entry.collected) && entry.collected.length ? entry.collected : null;
|
|
1029
1029
|
if (key in errors) {
|
|
1030
1030
|
const e = errors[key];
|
|
1031
1031
|
const detail = typeof e === "string" ? e : e?.detail || e?.message || JSON.stringify(e);
|
|
1032
|
-
const out = { data: null, error: detail };
|
|
1032
|
+
const out = held ? { data: held, error: detail, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error: detail };
|
|
1033
1033
|
if (e && typeof e === "object" && typeof e.code === "string") out.code = e.code;
|
|
1034
1034
|
entry.resolve(out);
|
|
1035
1035
|
return;
|
|
@@ -1045,21 +1045,32 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1045
1045
|
if (cursor && entry.request.exhaustive && Array.isArray(rows)) {
|
|
1046
1046
|
const acc = entry.collected ? entry.collected.concat(rows) : rows.slice();
|
|
1047
1047
|
const page = (entry.page || 1) + 1;
|
|
1048
|
-
|
|
1048
|
+
const cap = typeof entry.request.maxPages === "number" && entry.request.maxPages > 0 ? entry.request.maxPages : DEFAULT_MAX_PAGES;
|
|
1049
|
+
if (page <= cap) {
|
|
1049
1050
|
pending.set(entry, { cursor, collected: acc, page, depth, bound });
|
|
1050
1051
|
return;
|
|
1051
1052
|
}
|
|
1052
|
-
entry.resolve({ data: acc, meta: withMeta({ depth, bound,
|
|
1053
|
+
entry.resolve({ data: acc, meta: withMeta({ depth, bound, partial: true, pages: cap }) });
|
|
1053
1054
|
return;
|
|
1054
1055
|
}
|
|
1055
1056
|
const collected = entry.collected ? entry.collected.concat(Array.isArray(rows) ? rows : []) : rows;
|
|
1056
1057
|
const meta = withMeta({
|
|
1057
1058
|
depth,
|
|
1058
1059
|
bound,
|
|
1059
|
-
// ⭐
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
|
|
1060
|
+
// ⭐ ONE FLAG, MEANING **NOT THE WHOLE POPULATION** — asked for by name, so
|
|
1061
|
+
// a caller has one boolean to branch on rather than three signals to
|
|
1062
|
+
// combine. It is set in every case that means it:
|
|
1063
|
+
// · a cursor came back and this caller does not page (a page render);
|
|
1064
|
+
// · a cursor came back and the caller's `maxPages` stopped the walk;
|
|
1065
|
+
// · the walk failed or aborted with pages already in hand;
|
|
1066
|
+
// · the service reported it BOUNDED the answer and offered no cursor.
|
|
1067
|
+
// ⚠️ The last is why `limits` is read at all: a cursor is the signal for a
|
|
1068
|
+
// query that declared no `limit`, and `limits` is the signal for one whose
|
|
1069
|
+
// author limit was clamped (the records contract §5). Neither alone covers
|
|
1070
|
+
// both. ⛔ Named `truncated` when it shipped in 0.17.0 this morning; renamed
|
|
1071
|
+
// the same day, before any consumer adopted it, because "truncated" says
|
|
1072
|
+
// something was cut and this also means "there is more you did not ask for".
|
|
1073
|
+
partial: cursor || bound !== void 0 ? true : void 0,
|
|
1063
1074
|
pages: entry.page && entry.page > 1 ? entry.page : void 0
|
|
1064
1075
|
});
|
|
1065
1076
|
entry.resolve(meta ? { data: collected, meta } : { data: collected });
|
|
@@ -1243,7 +1254,7 @@ async function prefetchAndHydrate({ website, content, route, locale = null, fetc
|
|
|
1243
1254
|
hydrateDataStore(website, fetched);
|
|
1244
1255
|
return fetched;
|
|
1245
1256
|
}
|
|
1246
|
-
async function collectSiteRecords(content, { locale, fetch: fetch2, signal, only = null } = {}) {
|
|
1257
|
+
async function collectSiteRecords(content, { locale, fetch: fetch2, signal, only = null, depth = "brief", maxPages } = {}) {
|
|
1247
1258
|
const config = content?.config;
|
|
1248
1259
|
const services = config?.services ?? null;
|
|
1249
1260
|
const queries = config?.queries ?? null;
|
|
@@ -1261,12 +1272,13 @@ async function collectSiteRecords(content, { locale, fetch: fetch2, signal, only
|
|
|
1261
1272
|
const meta = {};
|
|
1262
1273
|
await Promise.all([...configs].map(async ([name, cfg]) => {
|
|
1263
1274
|
if (!cfg.ask) return;
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1275
|
+
const { limit, ...population } = cfg;
|
|
1276
|
+
const asked = { ...population, depth, exhaustive: true };
|
|
1277
|
+
if (typeof maxPages === "number" && maxPages > 0) asked.maxPages = maxPages;
|
|
1278
|
+
const result = await fetcher.resolve(asked, { signal });
|
|
1279
|
+
if (result?.error) errors[name] = result.error;
|
|
1280
|
+
if (Array.isArray(result?.data)) records[name] = result.data;
|
|
1281
|
+
else if (!result?.error) records[name] = [];
|
|
1270
1282
|
if (result?.meta) meta[name] = result.meta;
|
|
1271
1283
|
}));
|
|
1272
1284
|
return { records, errors: Object.keys(errors).length ? errors : null, meta };
|