@uniweb/runtime 0.23.0 → 0.25.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/ssr.js +85 -64
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/collect-records.js +26 -23
- package/src/default-fetcher.js +138 -90
- package/src/prefetch.js +25 -16
- package/src/prepare-props.js +53 -21
- package/src/ssr-renderer.js +2 -2
package/dist/ssr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, substitutePlaceholders, resolveServiceUrl,
|
|
1
|
+
import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, substitutePlaceholders, resolveServiceUrl, evaluateQuery, sortRecords, sortToWire } from "@uniweb/core";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { renderToString } from "react-dom/server";
|
|
4
4
|
import { sectionDomId } from "@uniweb/core/section-id";
|
|
@@ -9,7 +9,7 @@ import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
|
|
|
9
9
|
import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
|
|
10
10
|
import "@uniweb/core/services";
|
|
11
11
|
import "@uniweb/core/tracker";
|
|
12
|
-
import { pageRouteQuery, siteReaches, resolveFetchConfigs,
|
|
12
|
+
import { pageRouteQuery, siteReaches, fetchEntries, resolveFetchConfigs, currentFor, othersView, routeSelection } from "@uniweb/core/fetch-config";
|
|
13
13
|
import { deriveCacheKey as deriveCacheKey$1 } from "@uniweb/core/datastore";
|
|
14
14
|
import { buildDetailConfig } from "@uniweb/core/detail-url";
|
|
15
15
|
import { resolveDefaultLocale as resolveDefaultLocale$1 } from "@uniweb/core/locale-config";
|
|
@@ -171,19 +171,30 @@ function applyDefaults(params, defaults) {
|
|
|
171
171
|
...params || {}
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
function assembleData(block, meta, entityData) {
|
|
175
|
+
const declared = block.website?.declaredKeys ? block.website.declaredKeys(meta) : [];
|
|
176
|
+
const held = block.heldData || {};
|
|
177
|
+
const data = {};
|
|
178
|
+
for (const [key] of declared) {
|
|
179
|
+
if (held[key] !== void 0) data[key] = held[key];
|
|
180
|
+
else if (entityData && entityData[key] !== void 0) data[key] = entityData[key];
|
|
181
|
+
else data[key] = null;
|
|
182
|
+
}
|
|
183
|
+
warnUndeclaredBlocks(block, declared);
|
|
184
|
+
block.parsedContent.data = data;
|
|
185
|
+
}
|
|
186
|
+
const warnedBlocks = /* @__PURE__ */ new Set();
|
|
187
|
+
function warnUndeclaredBlocks(block, declared) {
|
|
188
|
+
if (!block.website?.entityStore?.dev) return;
|
|
189
|
+
const keys = new Set(declared.map(([key]) => key));
|
|
190
|
+
for (const item of block.parsedContent?.sequence || []) {
|
|
191
|
+
if (item?.type !== "dataBlock" || typeof item.tag !== "string" || keys.has(item.tag)) continue;
|
|
192
|
+
const memo = `${block.type}::${item.tag}`;
|
|
193
|
+
if (warnedBlocks.has(memo)) continue;
|
|
194
|
+
warnedBlocks.add(memo);
|
|
195
|
+
console.warn(
|
|
196
|
+
`[uniweb] ${block.type}: the \`${item.tag}\` data block is not in content.data — ${block.type} does not declare \`${item.tag}\` in its meta.js \`data:\`. It stays in content.sequence.`
|
|
197
|
+
);
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
200
|
function runDataHandler(block) {
|
|
@@ -227,7 +238,8 @@ function runPropsHandler(content, params, block) {
|
|
|
227
238
|
return null;
|
|
228
239
|
}
|
|
229
240
|
function prepareProps(block, meta, entityData = null) {
|
|
230
|
-
|
|
241
|
+
assembleData(block, meta, entityData);
|
|
242
|
+
block.website?.entityStore?.linkOwnRecords?.(block);
|
|
231
243
|
runDataHandler(block);
|
|
232
244
|
runContentHandler(block);
|
|
233
245
|
const defaults = meta?.defaults || {};
|
|
@@ -919,7 +931,7 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
919
931
|
if (transform && data !== null && data !== void 0) {
|
|
920
932
|
data = getNestedValue(data, transform);
|
|
921
933
|
}
|
|
922
|
-
data = applyOperators(data, request, { dev });
|
|
934
|
+
data = applyOperators(data, request, { dev, locale: sortLocale(request, ctx) });
|
|
923
935
|
const whole = typeof request.whole === "boolean" ? request.whole : void 0;
|
|
924
936
|
return whole === void 0 ? { data: data ?? [] } : { data: data ?? [], meta: { whole } };
|
|
925
937
|
} catch (error) {
|
|
@@ -937,14 +949,32 @@ function toQuestion(request) {
|
|
|
937
949
|
const scope = typeof request.scope === "string" && request.scope ? request.scope : null;
|
|
938
950
|
if (scope) q.scope = scope;
|
|
939
951
|
if (where) q.where = where;
|
|
940
|
-
|
|
941
|
-
const sort = sortToWire(request.sort);
|
|
952
|
+
const sort = wireSort(request.sort);
|
|
942
953
|
if (sort) q.sort = sort;
|
|
943
954
|
if (typeof request.limit === "number" && request.limit > 0) q.limit = request.limit;
|
|
944
955
|
if (request.whole === true) q.whole = true;
|
|
945
|
-
|
|
956
|
+
const narrow = toNarrow(request.narrow);
|
|
957
|
+
if (narrow) q.narrow = narrow;
|
|
946
958
|
return q;
|
|
947
959
|
}
|
|
960
|
+
function toNarrow(narrow) {
|
|
961
|
+
if (!narrow || typeof narrow !== "object") return null;
|
|
962
|
+
const n = {};
|
|
963
|
+
if (narrow.where && typeof narrow.where === "object") n.where = narrow.where;
|
|
964
|
+
if (narrow.match && typeof narrow.match === "object") n.match = narrow.match;
|
|
965
|
+
const sort = wireSort(narrow.sort);
|
|
966
|
+
if (sort) n.sort = sort;
|
|
967
|
+
if (typeof narrow.cursor === "string" && narrow.cursor) n.cursor = narrow.cursor;
|
|
968
|
+
if (typeof narrow.limit === "number" && narrow.limit > 0) n.limit = narrow.limit;
|
|
969
|
+
return Object.keys(n).length > 0 ? n : null;
|
|
970
|
+
}
|
|
971
|
+
function wireSort(sort) {
|
|
972
|
+
try {
|
|
973
|
+
return sortToWire(sort);
|
|
974
|
+
} catch {
|
|
975
|
+
return typeof sort === "string" && sort.trim() ? sort : null;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
948
978
|
const DEFAULT_MAX_PAGES = 20;
|
|
949
979
|
async function flushAsked(url, queue, doFetch) {
|
|
950
980
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -985,58 +1015,47 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
985
1015
|
return;
|
|
986
1016
|
}
|
|
987
1017
|
const data = parsed && typeof parsed.data === "object" && parsed.data ? parsed.data : {};
|
|
988
|
-
const errors = parsed && typeof parsed.errors === "object" && parsed.errors ? parsed.errors : {};
|
|
989
1018
|
const wholes = parsed && typeof parsed.whole === "object" && parsed.whole ? parsed.whole : {};
|
|
990
1019
|
const cursors = parsed && typeof parsed.cursors === "object" && parsed.cursors ? parsed.cursors : {};
|
|
991
|
-
const
|
|
1020
|
+
const degraded = parsed && typeof parsed.partial === "object" && parsed.partial ? parsed.partial : {};
|
|
992
1021
|
queue.forEach((entry, i) => {
|
|
993
1022
|
const key = keys[i];
|
|
994
1023
|
const held = Array.isArray(entry.collected) && entry.collected.length ? entry.collected : null;
|
|
995
|
-
if (key in errors) {
|
|
996
|
-
const e = errors[key];
|
|
997
|
-
const detail = typeof e === "string" ? e : e?.detail || e?.message || JSON.stringify(e);
|
|
998
|
-
const out = held ? { data: held, error: detail, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error: detail };
|
|
999
|
-
if (e && typeof e === "object" && typeof e.code === "string") out.code = e.code;
|
|
1000
|
-
entry.resolve(out);
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
1024
|
if (!(key in data)) {
|
|
1004
|
-
|
|
1025
|
+
const error = `the records service answered without the key "${key}"`;
|
|
1026
|
+
entry.resolve(held ? { data: held, error, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error });
|
|
1005
1027
|
return;
|
|
1006
1028
|
}
|
|
1007
1029
|
const whole = typeof wholes[key] === "boolean" ? wholes[key] : typeof entry.request.whole === "boolean" ? entry.request.whole : void 0;
|
|
1008
1030
|
const cursor = typeof cursors[key] === "string" && cursors[key] ? cursors[key] : null;
|
|
1009
|
-
const bound = typeof limits[key] === "number" ? limits[key] : void 0;
|
|
1010
1031
|
const rows = Array.isArray(data[key]) ? data[key] : data[key];
|
|
1011
1032
|
if (cursor && entry.request.exhaustive && Array.isArray(rows)) {
|
|
1012
1033
|
const acc = entry.collected ? entry.collected.concat(rows) : rows.slice();
|
|
1013
1034
|
const page = (entry.page || 1) + 1;
|
|
1014
1035
|
const cap = typeof entry.request.maxPages === "number" && entry.request.maxPages > 0 ? entry.request.maxPages : DEFAULT_MAX_PAGES;
|
|
1015
1036
|
if (page <= cap) {
|
|
1016
|
-
pending.set(entry, { cursor, collected: acc, page, whole
|
|
1037
|
+
pending.set(entry, { cursor, collected: acc, page, whole });
|
|
1017
1038
|
return;
|
|
1018
1039
|
}
|
|
1019
|
-
entry.resolve({ data: acc, meta: withMeta({ whole,
|
|
1040
|
+
entry.resolve({ data: acc, meta: withMeta({ whole, partial: true, pages: cap }) });
|
|
1020
1041
|
return;
|
|
1021
1042
|
}
|
|
1022
1043
|
const collected = entry.collected ? entry.collected.concat(Array.isArray(rows) ? rows : []) : rows;
|
|
1023
1044
|
const meta = withMeta({
|
|
1024
1045
|
whole,
|
|
1025
|
-
bound,
|
|
1026
1046
|
// ⭐ ONE FLAG, MEANING **NOT THE WHOLE POPULATION** — asked for by name, so
|
|
1027
|
-
// a caller has one boolean to branch on rather than
|
|
1047
|
+
// a caller has one boolean to branch on rather than several signals to
|
|
1028
1048
|
// combine. It is set in every case that means it:
|
|
1029
1049
|
// · a cursor came back and this caller does not page (a page render);
|
|
1030
1050
|
// · a cursor came back and the caller's `maxPages` stopped the walk;
|
|
1031
1051
|
// · the walk failed or aborted with pages already in hand;
|
|
1032
|
-
// ·
|
|
1033
|
-
//
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
partial: cursor || bound !== void 0 ? true : void 0,
|
|
1052
|
+
// · a source of the key did not answer (`partial[key]`), and `unavailable`
|
|
1053
|
+
// carries the service's own account of which one.
|
|
1054
|
+
// ⛔ Named `truncated` when it shipped in 0.17.0; renamed the same day, before
|
|
1055
|
+
// any consumer adopted it, because "truncated" says something was cut and this
|
|
1056
|
+
// also means "there is more you did not ask for".
|
|
1057
|
+
partial: cursor || key in degraded ? true : void 0,
|
|
1058
|
+
unavailable: key in degraded ? degraded[key] : void 0,
|
|
1040
1059
|
pages: entry.page && entry.page > 1 ? entry.page : void 0
|
|
1041
1060
|
});
|
|
1042
1061
|
entry.resolve(meta ? { data: collected, meta } : { data: collected });
|
|
@@ -1045,7 +1064,7 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1045
1064
|
await Promise.all([...pending].map(([entry, state]) => {
|
|
1046
1065
|
const next = {
|
|
1047
1066
|
...entry,
|
|
1048
|
-
request: { ...entry.request, cursor: state.cursor },
|
|
1067
|
+
request: { ...entry.request, narrow: { ...entry.request.narrow || {}, cursor: state.cursor } },
|
|
1049
1068
|
collected: state.collected,
|
|
1050
1069
|
page: state.page
|
|
1051
1070
|
};
|
|
@@ -1076,19 +1095,22 @@ function withMeta(fields) {
|
|
|
1076
1095
|
for (const [k, v] of Object.entries(fields)) if (v !== void 0) out[k] = v;
|
|
1077
1096
|
return Object.keys(out).length ? out : void 0;
|
|
1078
1097
|
}
|
|
1079
|
-
function
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
return
|
|
1098
|
+
function sortLocale(request, ctx) {
|
|
1099
|
+
if (typeof request?.locale === "string" && request.locale) return request.locale;
|
|
1100
|
+
if (typeof ctx?.locale === "string" && ctx.locale) return ctx.locale;
|
|
1101
|
+
const active = ctx?.website?.getActiveLocale?.();
|
|
1102
|
+
return typeof active === "string" && active ? active : null;
|
|
1103
|
+
}
|
|
1104
|
+
function applyOperators(data, request, { dev = false, locale = null } = {}) {
|
|
1105
|
+
return evaluateQuery(data, request, {
|
|
1106
|
+
locale,
|
|
1107
|
+
sort: (items, sortExpr, options) => applySort(items, sortExpr, dev, options.locale)
|
|
1108
|
+
});
|
|
1087
1109
|
}
|
|
1088
1110
|
const warnedBadSorts = /* @__PURE__ */ new Set();
|
|
1089
|
-
function applySort(items, sortExpr, dev) {
|
|
1111
|
+
function applySort(items, sortExpr, dev, locale) {
|
|
1090
1112
|
try {
|
|
1091
|
-
return sortRecords(items, sortExpr);
|
|
1113
|
+
return sortRecords(items, sortExpr, { locale });
|
|
1092
1114
|
} catch (err) {
|
|
1093
1115
|
if (dev) throw err;
|
|
1094
1116
|
const key = String(sortExpr);
|
|
@@ -1157,7 +1179,6 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1157
1179
|
sectionsOf: (p) => p.sections,
|
|
1158
1180
|
site: siteFetch
|
|
1159
1181
|
}) : null;
|
|
1160
|
-
const routeKey = chosen?.key ?? null;
|
|
1161
1182
|
const routeSource = chosen?.nested ? chosen.config : null;
|
|
1162
1183
|
const out = /* @__PURE__ */ new Map();
|
|
1163
1184
|
const put = (cfg) => {
|
|
@@ -1165,8 +1186,9 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1165
1186
|
if (!out.has(key)) out.set(key, cfg);
|
|
1166
1187
|
};
|
|
1167
1188
|
const add = (sources) => {
|
|
1168
|
-
|
|
1169
|
-
|
|
1189
|
+
const resolved = sources.flatMap((source) => fetchEntries(source).map((entry) => resolveFetchConfigs([entry], options).get(entry.as)));
|
|
1190
|
+
for (const cfg of resolved.filter(Boolean)) {
|
|
1191
|
+
const current = currentFor(cfg, chosen);
|
|
1170
1192
|
if (current === "exclude") {
|
|
1171
1193
|
put(othersView(cfg));
|
|
1172
1194
|
continue;
|
|
@@ -1179,7 +1201,7 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1179
1201
|
const detailCfg = buildDetailConfig(cfg, { paramName: binding.paramName, paramValue: String(binding.paramValue) });
|
|
1180
1202
|
if (detailCfg) put(detailCfg);
|
|
1181
1203
|
}
|
|
1182
|
-
|
|
1204
|
+
if (!cfg.ask) put(routeSelection(cfg));
|
|
1183
1205
|
}
|
|
1184
1206
|
};
|
|
1185
1207
|
const siteForPage = siteReaches(parent) ? siteFetch : null;
|
|
@@ -1194,7 +1216,7 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1194
1216
|
for (const s of layoutSectionsFor(content, page)) add([s.fetch ?? null, siteFetch]);
|
|
1195
1217
|
return [...out.values()];
|
|
1196
1218
|
}
|
|
1197
|
-
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always" } = {}) {
|
|
1219
|
+
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always", locale = null } = {}) {
|
|
1198
1220
|
if (prerender !== "author" && prerender !== "always") {
|
|
1199
1221
|
throw new Error(`executeFetchConfigs: prerender must be 'author' or 'always', got ${JSON.stringify(prerender)}`);
|
|
1200
1222
|
}
|
|
@@ -1203,7 +1225,7 @@ async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev
|
|
|
1203
1225
|
dev,
|
|
1204
1226
|
fetch: fetch2
|
|
1205
1227
|
});
|
|
1206
|
-
const ctx = { website: null };
|
|
1228
|
+
const ctx = { website: null, locale: locale ?? content?.config?.activeLocale ?? resolveDefaultLocale$1(content?.config) ?? null };
|
|
1207
1229
|
return Promise.all((configs || []).filter(Boolean).map(async (config) => {
|
|
1208
1230
|
if (prerender === "author" && config.prerender === false) {
|
|
1209
1231
|
return { config, outcome: "skipped", data: null };
|
|
@@ -1215,7 +1237,7 @@ async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev
|
|
|
1215
1237
|
}
|
|
1216
1238
|
async function prefetchPageData({ content, route, locale = null, fetch: fetch2 = null, dev = false, prerender = "always" }) {
|
|
1217
1239
|
const configs = resolvePageFetchConfigs(content, route, { locale });
|
|
1218
|
-
return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender });
|
|
1240
|
+
return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender, locale });
|
|
1219
1241
|
}
|
|
1220
1242
|
function createPageRenderer({ website, shell }) {
|
|
1221
1243
|
if (!website) throw new Error("createPageRenderer: `website` is required");
|
|
@@ -1270,8 +1292,7 @@ async function collectSiteRecords(content, { locale, fetch: fetch2, signal, only
|
|
|
1270
1292
|
const meta = {};
|
|
1271
1293
|
await Promise.all([...configs].map(async ([name, cfg]) => {
|
|
1272
1294
|
if (!cfg.ask) return;
|
|
1273
|
-
const {
|
|
1274
|
-
const asked = { ...population, whole, exhaustive: true };
|
|
1295
|
+
const asked = { ...cfg, whole, exhaustive: true };
|
|
1275
1296
|
if (typeof maxPages === "number" && maxPages > 0) asked.maxPages = maxPages;
|
|
1276
1297
|
const result = await fetcher.resolve(asked, { signal });
|
|
1277
1298
|
if (result?.error) errors[name] = result.error;
|