@uniweb/runtime 0.22.0 → 0.24.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 +73 -69
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/collect-records.js +4 -3
- package/src/default-fetcher.js +74 -109
- package/src/prefetch.js +73 -18
package/dist/ssr.js
CHANGED
|
@@ -9,10 +9,11 @@ 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 {
|
|
12
|
+
import { pageRouteQuery, siteReaches, resolveFetchConfigs, currentOf, 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";
|
|
16
|
+
import { findLayoutEntry } from "@uniweb/core/layout-name";
|
|
16
17
|
import { resolveRecordsService } from "@uniweb/core/records-service";
|
|
17
18
|
function guaranteeItemStructure(item) {
|
|
18
19
|
return {
|
|
@@ -911,30 +912,14 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
911
912
|
try {
|
|
912
913
|
const response = await readOnce(target, init);
|
|
913
914
|
if (ctx.signal?.aborted) return { data: [], error: "aborted" };
|
|
914
|
-
const envelope = request.envelope && typeof request.envelope === "object" ? request.envelope : {};
|
|
915
915
|
if (!response.ok) {
|
|
916
|
-
|
|
917
|
-
if (envelope.error && typeof response.text === "string") {
|
|
918
|
-
const body = safeParseJSON(response.text);
|
|
919
|
-
if (body !== void 0) {
|
|
920
|
-
const candidate = getNestedValue(body, envelope.error);
|
|
921
|
-
if (typeof candidate === "string" && candidate.length) {
|
|
922
|
-
extracted = candidate;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
return {
|
|
927
|
-
data: [],
|
|
928
|
-
error: extracted ?? `HTTP ${response.status}: ${response.statusText}`
|
|
929
|
-
};
|
|
916
|
+
return { data: [], error: `HTTP ${response.status}: ${response.statusText}` };
|
|
930
917
|
}
|
|
931
918
|
let data = response.body;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (effectiveTransform && data !== null && data !== void 0) {
|
|
935
|
-
data = getNestedValue(data, effectiveTransform);
|
|
919
|
+
if (transform && data !== null && data !== void 0) {
|
|
920
|
+
data = getNestedValue(data, transform);
|
|
936
921
|
}
|
|
937
|
-
data = applyOperators(data, request, { dev });
|
|
922
|
+
data = applyOperators(data, request, { dev, locale: sortLocale(request, ctx) });
|
|
938
923
|
const whole = typeof request.whole === "boolean" ? request.whole : void 0;
|
|
939
924
|
return whole === void 0 ? { data: data ?? [] } : { data: data ?? [], meta: { whole } };
|
|
940
925
|
} catch (error) {
|
|
@@ -951,7 +936,7 @@ function toQuestion(request) {
|
|
|
951
936
|
const where = request.where && typeof request.where === "object" ? request.where : null;
|
|
952
937
|
const scope = typeof request.scope === "string" && request.scope ? request.scope : null;
|
|
953
938
|
if (scope) q.scope = scope;
|
|
954
|
-
if (where) q.where =
|
|
939
|
+
if (where) q.where = where;
|
|
955
940
|
if (request.match && typeof request.match === "object") q.match = request.match;
|
|
956
941
|
const sort = sortToWire(request.sort);
|
|
957
942
|
if (sort) q.sort = sort;
|
|
@@ -960,16 +945,6 @@ function toQuestion(request) {
|
|
|
960
945
|
if (typeof request.cursor === "string" && request.cursor) q.cursor = request.cursor;
|
|
961
946
|
return q;
|
|
962
947
|
}
|
|
963
|
-
const OPERATOR_ALIAS = { nin: "not_in" };
|
|
964
|
-
function renameOperators(where) {
|
|
965
|
-
if (Array.isArray(where)) return where.map(renameOperators);
|
|
966
|
-
if (!where || typeof where !== "object") return where;
|
|
967
|
-
const out = {};
|
|
968
|
-
for (const [key, value] of Object.entries(where)) {
|
|
969
|
-
out[OPERATOR_ALIAS[key] ?? key] = value && typeof value === "object" ? renameOperators(value) : value;
|
|
970
|
-
}
|
|
971
|
-
return out;
|
|
972
|
-
}
|
|
973
948
|
const DEFAULT_MAX_PAGES = 20;
|
|
974
949
|
async function flushAsked(url, queue, doFetch) {
|
|
975
950
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -1010,58 +985,47 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1010
985
|
return;
|
|
1011
986
|
}
|
|
1012
987
|
const data = parsed && typeof parsed.data === "object" && parsed.data ? parsed.data : {};
|
|
1013
|
-
const errors = parsed && typeof parsed.errors === "object" && parsed.errors ? parsed.errors : {};
|
|
1014
988
|
const wholes = parsed && typeof parsed.whole === "object" && parsed.whole ? parsed.whole : {};
|
|
1015
989
|
const cursors = parsed && typeof parsed.cursors === "object" && parsed.cursors ? parsed.cursors : {};
|
|
1016
|
-
const
|
|
990
|
+
const degraded = parsed && typeof parsed.partial === "object" && parsed.partial ? parsed.partial : {};
|
|
1017
991
|
queue.forEach((entry, i) => {
|
|
1018
992
|
const key = keys[i];
|
|
1019
993
|
const held = Array.isArray(entry.collected) && entry.collected.length ? entry.collected : null;
|
|
1020
|
-
if (key in errors) {
|
|
1021
|
-
const e = errors[key];
|
|
1022
|
-
const detail = typeof e === "string" ? e : e?.detail || e?.message || JSON.stringify(e);
|
|
1023
|
-
const out = held ? { data: held, error: detail, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error: detail };
|
|
1024
|
-
if (e && typeof e === "object" && typeof e.code === "string") out.code = e.code;
|
|
1025
|
-
entry.resolve(out);
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
994
|
if (!(key in data)) {
|
|
1029
|
-
|
|
995
|
+
const error = `the records service answered without the key "${key}"`;
|
|
996
|
+
entry.resolve(held ? { data: held, error, meta: withMeta({ partial: true, pages: entry.page }) } : { data: null, error });
|
|
1030
997
|
return;
|
|
1031
998
|
}
|
|
1032
999
|
const whole = typeof wholes[key] === "boolean" ? wholes[key] : typeof entry.request.whole === "boolean" ? entry.request.whole : void 0;
|
|
1033
1000
|
const cursor = typeof cursors[key] === "string" && cursors[key] ? cursors[key] : null;
|
|
1034
|
-
const bound = typeof limits[key] === "number" ? limits[key] : void 0;
|
|
1035
1001
|
const rows = Array.isArray(data[key]) ? data[key] : data[key];
|
|
1036
1002
|
if (cursor && entry.request.exhaustive && Array.isArray(rows)) {
|
|
1037
1003
|
const acc = entry.collected ? entry.collected.concat(rows) : rows.slice();
|
|
1038
1004
|
const page = (entry.page || 1) + 1;
|
|
1039
1005
|
const cap = typeof entry.request.maxPages === "number" && entry.request.maxPages > 0 ? entry.request.maxPages : DEFAULT_MAX_PAGES;
|
|
1040
1006
|
if (page <= cap) {
|
|
1041
|
-
pending.set(entry, { cursor, collected: acc, page, whole
|
|
1007
|
+
pending.set(entry, { cursor, collected: acc, page, whole });
|
|
1042
1008
|
return;
|
|
1043
1009
|
}
|
|
1044
|
-
entry.resolve({ data: acc, meta: withMeta({ whole,
|
|
1010
|
+
entry.resolve({ data: acc, meta: withMeta({ whole, partial: true, pages: cap }) });
|
|
1045
1011
|
return;
|
|
1046
1012
|
}
|
|
1047
1013
|
const collected = entry.collected ? entry.collected.concat(Array.isArray(rows) ? rows : []) : rows;
|
|
1048
1014
|
const meta = withMeta({
|
|
1049
1015
|
whole,
|
|
1050
|
-
bound,
|
|
1051
1016
|
// ⭐ ONE FLAG, MEANING **NOT THE WHOLE POPULATION** — asked for by name, so
|
|
1052
|
-
// a caller has one boolean to branch on rather than
|
|
1017
|
+
// a caller has one boolean to branch on rather than several signals to
|
|
1053
1018
|
// combine. It is set in every case that means it:
|
|
1054
1019
|
// · a cursor came back and this caller does not page (a page render);
|
|
1055
1020
|
// · a cursor came back and the caller's `maxPages` stopped the walk;
|
|
1056
1021
|
// · the walk failed or aborted with pages already in hand;
|
|
1057
|
-
// ·
|
|
1058
|
-
//
|
|
1059
|
-
//
|
|
1060
|
-
//
|
|
1061
|
-
//
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
partial: cursor || bound !== void 0 ? true : void 0,
|
|
1022
|
+
// · a source of the key did not answer (`partial[key]`), and `unavailable`
|
|
1023
|
+
// carries the service's own account of which one.
|
|
1024
|
+
// ⛔ Named `truncated` when it shipped in 0.17.0; renamed the same day, before
|
|
1025
|
+
// any consumer adopted it, because "truncated" says something was cut and this
|
|
1026
|
+
// also means "there is more you did not ask for".
|
|
1027
|
+
partial: cursor || key in degraded ? true : void 0,
|
|
1028
|
+
unavailable: key in degraded ? degraded[key] : void 0,
|
|
1065
1029
|
pages: entry.page && entry.page > 1 ? entry.page : void 0
|
|
1066
1030
|
});
|
|
1067
1031
|
entry.resolve(meta ? { data: collected, meta } : { data: collected });
|
|
@@ -1101,19 +1065,25 @@ function withMeta(fields) {
|
|
|
1101
1065
|
for (const [k, v] of Object.entries(fields)) if (v !== void 0) out[k] = v;
|
|
1102
1066
|
return Object.keys(out).length ? out : void 0;
|
|
1103
1067
|
}
|
|
1104
|
-
function
|
|
1068
|
+
function sortLocale(request, ctx) {
|
|
1069
|
+
if (typeof request?.locale === "string" && request.locale) return request.locale;
|
|
1070
|
+
if (typeof ctx?.locale === "string" && ctx.locale) return ctx.locale;
|
|
1071
|
+
const active = ctx?.website?.getActiveLocale?.();
|
|
1072
|
+
return typeof active === "string" && active ? active : null;
|
|
1073
|
+
}
|
|
1074
|
+
function applyOperators(data, request, { dev = false, locale = null } = {}) {
|
|
1105
1075
|
if (!Array.isArray(data)) return data;
|
|
1106
1076
|
let result = data;
|
|
1107
1077
|
if (typeof request.scope === "string" && request.scope) result = applyScope(result, request.scope);
|
|
1108
1078
|
if (request.where) result = matchWhere(request.where, result);
|
|
1109
|
-
if (request.sort) result = applySort(result, request.sort, dev);
|
|
1079
|
+
if (request.sort) result = applySort(result, request.sort, dev, locale);
|
|
1110
1080
|
if (typeof request.limit === "number" && request.limit > 0) result = result.slice(0, request.limit);
|
|
1111
1081
|
return result;
|
|
1112
1082
|
}
|
|
1113
1083
|
const warnedBadSorts = /* @__PURE__ */ new Set();
|
|
1114
|
-
function applySort(items, sortExpr, dev) {
|
|
1084
|
+
function applySort(items, sortExpr, dev, locale) {
|
|
1115
1085
|
try {
|
|
1116
|
-
return sortRecords(items, sortExpr);
|
|
1086
|
+
return sortRecords(items, sortExpr, { locale });
|
|
1117
1087
|
} catch (err) {
|
|
1118
1088
|
if (dev) throw err;
|
|
1119
1089
|
const key = String(sortExpr);
|
|
@@ -1140,12 +1110,27 @@ function safeParseJSON(text) {
|
|
|
1140
1110
|
return void 0;
|
|
1141
1111
|
}
|
|
1142
1112
|
}
|
|
1143
|
-
const isRefinement = (f) => f && typeof f === "object" && f.refine === true;
|
|
1144
1113
|
function parentPageOf(page, pages) {
|
|
1145
1114
|
const byRoute = new Map(pages.filter((p) => p?.route).map((p) => [p.route, p]));
|
|
1146
1115
|
const route = parentRouteOf(page.route, { declared: page.parent ?? null, has: (r) => byRoute.has(r) });
|
|
1147
1116
|
return route ? byRoute.get(route) : null;
|
|
1148
1117
|
}
|
|
1118
|
+
function layoutSectionsFor(content, page) {
|
|
1119
|
+
const sets = content?.layouts && typeof content.layouts === "object" ? content.layouts : {};
|
|
1120
|
+
const name = typeof page?.layout?.name === "string" && page.layout.name ? page.layout.name : null;
|
|
1121
|
+
const named = name ? findLayoutEntry(sets, name) : void 0;
|
|
1122
|
+
const chosen = name ? named ? [named] : [] : Object.values(sets);
|
|
1123
|
+
const out = [];
|
|
1124
|
+
const walk = (sections) => {
|
|
1125
|
+
for (const s of sections || []) {
|
|
1126
|
+
if (!s || typeof s !== "object") continue;
|
|
1127
|
+
out.push(s);
|
|
1128
|
+
walk(s.subsections);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
for (const set of chosen) for (const area of Object.values(set || {})) walk(area?.sections);
|
|
1132
|
+
return out;
|
|
1133
|
+
}
|
|
1149
1134
|
function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
1150
1135
|
const { page, params } = findPageForRoute(content, route);
|
|
1151
1136
|
if (!page) return [];
|
|
@@ -1160,7 +1145,15 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1160
1145
|
variables: binding?.variables ?? null
|
|
1161
1146
|
};
|
|
1162
1147
|
const siteFetch = content?.config?.fetch ?? null;
|
|
1163
|
-
const
|
|
1148
|
+
const chosen = binding ? pageRouteQuery(page, {
|
|
1149
|
+
routeOf: (p) => p.route,
|
|
1150
|
+
parentOf: (p) => parentPageOf(p, pages),
|
|
1151
|
+
fetchOf: (p) => p.fetch ?? null,
|
|
1152
|
+
sectionsOf: (p) => p.sections,
|
|
1153
|
+
site: siteFetch
|
|
1154
|
+
}) : null;
|
|
1155
|
+
const routeKey = chosen?.key ?? null;
|
|
1156
|
+
const routeSource = chosen?.nested ? chosen.config : null;
|
|
1164
1157
|
const out = /* @__PURE__ */ new Map();
|
|
1165
1158
|
const put = (cfg) => {
|
|
1166
1159
|
const key = deriveCacheKey$1(cfg);
|
|
@@ -1168,24 +1161,35 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1168
1161
|
};
|
|
1169
1162
|
const add = (sources) => {
|
|
1170
1163
|
for (const cfg of resolveFetchConfigs(sources, options).values()) {
|
|
1171
|
-
|
|
1172
|
-
if (
|
|
1164
|
+
const current = routeKey && cfg.as === routeKey ? currentOf(cfg) : null;
|
|
1165
|
+
if (current === "exclude") {
|
|
1166
|
+
put(othersView(cfg));
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (current !== "only") {
|
|
1170
|
+
put(cfg);
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (cfg.detail && binding.paramValue !== void 0) {
|
|
1173
1174
|
const detailCfg = buildDetailConfig(cfg, { paramName: binding.paramName, paramValue: String(binding.paramValue) });
|
|
1174
1175
|
if (detailCfg) put(detailCfg);
|
|
1175
1176
|
}
|
|
1177
|
+
put(cfg.ask ? cfg : routeSelection(cfg));
|
|
1176
1178
|
}
|
|
1177
1179
|
};
|
|
1178
|
-
|
|
1180
|
+
const siteForPage = siteReaches(parent) ? siteFetch : null;
|
|
1181
|
+
add([page.fetch ?? null, parent?.fetch ?? null, routeSource, siteForPage]);
|
|
1179
1182
|
const walk = (sections) => {
|
|
1180
1183
|
for (const s of sections || []) {
|
|
1181
|
-
if (s?.fetch
|
|
1184
|
+
if (s?.fetch) add([s.fetch, page.fetch ?? null, parent?.fetch ?? null, routeSource, siteForPage]);
|
|
1182
1185
|
if (s?.subsections) walk(s.subsections);
|
|
1183
1186
|
}
|
|
1184
1187
|
};
|
|
1185
1188
|
walk(page.sections);
|
|
1189
|
+
for (const s of layoutSectionsFor(content, page)) add([s.fetch ?? null, siteFetch]);
|
|
1186
1190
|
return [...out.values()];
|
|
1187
1191
|
}
|
|
1188
|
-
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always" } = {}) {
|
|
1192
|
+
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always", locale = null } = {}) {
|
|
1189
1193
|
if (prerender !== "author" && prerender !== "always") {
|
|
1190
1194
|
throw new Error(`executeFetchConfigs: prerender must be 'author' or 'always', got ${JSON.stringify(prerender)}`);
|
|
1191
1195
|
}
|
|
@@ -1194,7 +1198,7 @@ async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev
|
|
|
1194
1198
|
dev,
|
|
1195
1199
|
fetch: fetch2
|
|
1196
1200
|
});
|
|
1197
|
-
const ctx = { website: null };
|
|
1201
|
+
const ctx = { website: null, locale: locale ?? content?.config?.activeLocale ?? resolveDefaultLocale$1(content?.config) ?? null };
|
|
1198
1202
|
return Promise.all((configs || []).filter(Boolean).map(async (config) => {
|
|
1199
1203
|
if (prerender === "author" && config.prerender === false) {
|
|
1200
1204
|
return { config, outcome: "skipped", data: null };
|
|
@@ -1206,7 +1210,7 @@ async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev
|
|
|
1206
1210
|
}
|
|
1207
1211
|
async function prefetchPageData({ content, route, locale = null, fetch: fetch2 = null, dev = false, prerender = "always" }) {
|
|
1208
1212
|
const configs = resolvePageFetchConfigs(content, route, { locale });
|
|
1209
|
-
return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender });
|
|
1213
|
+
return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender, locale });
|
|
1210
1214
|
}
|
|
1211
1215
|
function createPageRenderer({ website, shell }) {
|
|
1212
1216
|
if (!website) throw new Error("createPageRenderer: `website` is required");
|