@uniweb/runtime 0.19.5 → 0.20.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 +70 -64
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/default-fetcher.js +83 -44
- package/src/prefetch.js +49 -41
package/dist/ssr.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, substitutePlaceholders, resolveServiceUrl, matchWhere, sortRecords, sortToWire } from "@uniweb/core";
|
|
1
|
+
import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, substitutePlaceholders, resolveServiceUrl, applyScope, matchWhere, 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";
|
|
5
5
|
import { applyBasePath } from "@uniweb/core/base-path";
|
|
6
|
-
import { routePatternToRegex, decodeRouteValue,
|
|
6
|
+
import { routePatternToRegex, isDynamicRoute, decodeRouteValue, routeBinding, parentRouteOf } from "@uniweb/core/route-match";
|
|
7
7
|
import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
|
|
8
8
|
import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
|
|
9
9
|
import "@uniweb/core/services";
|
|
10
10
|
import "@uniweb/core/tracker";
|
|
11
|
-
import { resolveFetchConfigs } from "@uniweb/core/fetch-config";
|
|
11
|
+
import { routeQuery, sectionFetches, resolveFetchConfigs } from "@uniweb/core/fetch-config";
|
|
12
12
|
import { deriveCacheKey as deriveCacheKey$1 } from "@uniweb/core/datastore";
|
|
13
13
|
import { buildDetailConfig } from "@uniweb/core/detail-url";
|
|
14
14
|
import { resolveDefaultLocale as resolveDefaultLocale$1 } from "@uniweb/core/locale-config";
|
|
@@ -861,14 +861,22 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
861
861
|
queue.push({ request, ctx, resolve });
|
|
862
862
|
});
|
|
863
863
|
};
|
|
864
|
+
const reads = /* @__PURE__ */ new Map();
|
|
865
|
+
const readOnce = (target, init) => {
|
|
866
|
+
const key = `${init.method} ${target} ${init.body ?? ""}`;
|
|
867
|
+
let pending = reads.get(key);
|
|
868
|
+
if (!pending) {
|
|
869
|
+
pending = readResponse(doFetch, target, init).finally(() => reads.delete(key));
|
|
870
|
+
reads.set(key, pending);
|
|
871
|
+
}
|
|
872
|
+
return pending;
|
|
873
|
+
};
|
|
864
874
|
return {
|
|
865
875
|
/**
|
|
866
|
-
* The cache identity is the request's ADDRESS
|
|
867
|
-
* records service, the QUESTION (`deriveCacheKey`
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
* against the same path share one entry — the file is fetched once and
|
|
871
|
-
* each page filters its own copy.
|
|
876
|
+
* The cache identity is the request's ADDRESS and the view it takes of it —
|
|
877
|
+
* or, when asked of the records service, the QUESTION (`deriveCacheKey`).
|
|
878
|
+
* Two pages declaring different `where:` clauses against one path are two
|
|
879
|
+
* entries, each cut from a read of the file (`readOnce`, above).
|
|
872
880
|
*/
|
|
873
881
|
cacheKey(request) {
|
|
874
882
|
return deriveCacheKey(request);
|
|
@@ -890,7 +898,7 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
890
898
|
} else {
|
|
891
899
|
return { data: [], error: "No path, url or ask specified" };
|
|
892
900
|
}
|
|
893
|
-
const init = {
|
|
901
|
+
const init = { method };
|
|
894
902
|
if (method === "POST") {
|
|
895
903
|
const dc = request.dynamicContext;
|
|
896
904
|
const body = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
|
|
@@ -900,21 +908,18 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
900
908
|
}
|
|
901
909
|
}
|
|
902
910
|
try {
|
|
903
|
-
const response = await
|
|
911
|
+
const response = await readOnce(target, init);
|
|
912
|
+
if (ctx.signal?.aborted) return { data: [], error: "aborted" };
|
|
904
913
|
const envelope = request.envelope && typeof request.envelope === "object" ? request.envelope : {};
|
|
905
914
|
if (!response.ok) {
|
|
906
915
|
let extracted;
|
|
907
|
-
if (envelope.error) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
if (typeof candidate === "string" && candidate.length) {
|
|
914
|
-
extracted = candidate;
|
|
915
|
-
}
|
|
916
|
+
if (envelope.error && typeof response.text === "string") {
|
|
917
|
+
const body = safeParseJSON(response.text);
|
|
918
|
+
if (body !== void 0) {
|
|
919
|
+
const candidate = getNestedValue(body, envelope.error);
|
|
920
|
+
if (typeof candidate === "string" && candidate.length) {
|
|
921
|
+
extracted = candidate;
|
|
916
922
|
}
|
|
917
|
-
} catch {
|
|
918
923
|
}
|
|
919
924
|
}
|
|
920
925
|
return {
|
|
@@ -922,18 +927,7 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
922
927
|
error: extracted ?? `HTTP ${response.status}: ${response.statusText}`
|
|
923
928
|
};
|
|
924
929
|
}
|
|
925
|
-
|
|
926
|
-
let data;
|
|
927
|
-
if (contentType.includes("application/json")) {
|
|
928
|
-
data = await response.json();
|
|
929
|
-
} else {
|
|
930
|
-
const text = await response.text();
|
|
931
|
-
try {
|
|
932
|
-
data = JSON.parse(text);
|
|
933
|
-
} catch {
|
|
934
|
-
data = text;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
930
|
+
let data = response.body;
|
|
937
931
|
const isDetailRequest = !!request.dynamicContext;
|
|
938
932
|
const effectiveTransform = transform || (isDetailRequest ? envelope.item : envelope.list);
|
|
939
933
|
if (effectiveTransform && data !== null && data !== void 0) {
|
|
@@ -953,15 +947,11 @@ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = n
|
|
|
953
947
|
}
|
|
954
948
|
function toQuestion(request) {
|
|
955
949
|
const q = { schema: request.schema };
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (where && !scope && where.path && typeof where.path === "object" && typeof where.path.under === "string" && where.path.under) {
|
|
959
|
-
const { path, ...rest } = where;
|
|
960
|
-
scope = path.under;
|
|
961
|
-
where = Object.keys(rest).length ? rest : null;
|
|
962
|
-
}
|
|
950
|
+
const where = request.where && typeof request.where === "object" ? request.where : null;
|
|
951
|
+
const scope = typeof request.scope === "string" && request.scope ? request.scope : null;
|
|
963
952
|
if (scope) q.scope = scope;
|
|
964
953
|
if (where) q.where = renameOperators(where);
|
|
954
|
+
if (request.match && typeof request.match === "object") q.match = request.match;
|
|
965
955
|
const sort = sortToWire(request.sort);
|
|
966
956
|
if (sort) q.sort = sort;
|
|
967
957
|
if (typeof request.limit === "number" && request.limit > 0) q.limit = request.limit;
|
|
@@ -1086,6 +1076,25 @@ async function flushAsked(url, queue, doFetch) {
|
|
|
1086
1076
|
return flushAsked(url, [next], doFetch);
|
|
1087
1077
|
}));
|
|
1088
1078
|
}
|
|
1079
|
+
async function readResponse(doFetch, target, init) {
|
|
1080
|
+
const response = await doFetch(target, init);
|
|
1081
|
+
if (!response.ok) {
|
|
1082
|
+
let text2 = null;
|
|
1083
|
+
try {
|
|
1084
|
+
text2 = await response.text();
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
return { ok: false, status: response.status, statusText: response.statusText, text: text2 };
|
|
1088
|
+
}
|
|
1089
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1090
|
+
if (contentType.includes("application/json")) return { ok: true, body: await response.json() };
|
|
1091
|
+
const text = await response.text();
|
|
1092
|
+
try {
|
|
1093
|
+
return { ok: true, body: JSON.parse(text) };
|
|
1094
|
+
} catch {
|
|
1095
|
+
return { ok: true, body: text };
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1089
1098
|
function withMeta(fields) {
|
|
1090
1099
|
const out = {};
|
|
1091
1100
|
for (const [k, v] of Object.entries(fields)) if (v !== void 0) out[k] = v;
|
|
@@ -1094,6 +1103,7 @@ function withMeta(fields) {
|
|
|
1094
1103
|
function applyOperators(data, request, { dev = false } = {}) {
|
|
1095
1104
|
if (!Array.isArray(data)) return data;
|
|
1096
1105
|
let result = data;
|
|
1106
|
+
if (typeof request.scope === "string" && request.scope) result = applyScope(result, request.scope);
|
|
1097
1107
|
if (request.where) result = matchWhere(request.where, result);
|
|
1098
1108
|
if (request.sort) result = applySort(result, request.sort, dev);
|
|
1099
1109
|
if (typeof request.limit === "number" && request.limit > 0) result = result.slice(0, request.limit);
|
|
@@ -1135,7 +1145,7 @@ function findPageForRoute(content, route) {
|
|
|
1135
1145
|
const exact = pages.find((p) => p.route === route);
|
|
1136
1146
|
if (exact) return { page: exact, params: {} };
|
|
1137
1147
|
for (const page of pages) {
|
|
1138
|
-
if (!page.
|
|
1148
|
+
if (!page.route || !isDynamicRoute(page.route)) continue;
|
|
1139
1149
|
const compiled = routePatternToRegex(page.route);
|
|
1140
1150
|
const m = compiled?.regex ? compiled.regex.exec(route) : null;
|
|
1141
1151
|
if (m) {
|
|
@@ -1149,22 +1159,17 @@ function findPageForRoute(content, route) {
|
|
|
1149
1159
|
}
|
|
1150
1160
|
return { page: null, params: {} };
|
|
1151
1161
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const paramName2 = page.paramName || "slug";
|
|
1157
|
-
return { paramName: paramName2, paramValue: parts.slug, variables: { ...params, ...parts } };
|
|
1158
|
-
}
|
|
1159
|
-
const paramName = page.paramName || Object.keys(params)[0];
|
|
1160
|
-
return { paramName, paramValue: params[paramName], variables: { ...params } };
|
|
1162
|
+
function parentPageOf(page, pages) {
|
|
1163
|
+
const byRoute = new Map(pages.filter((p) => p?.route).map((p) => [p.route, p]));
|
|
1164
|
+
const route = parentRouteOf(page.route, { declared: page.parent ?? null, has: (r) => byRoute.has(r) });
|
|
1165
|
+
return route ? byRoute.get(route) : null;
|
|
1161
1166
|
}
|
|
1162
1167
|
function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
1163
1168
|
const { page, params } = findPageForRoute(content, route);
|
|
1164
1169
|
if (!page) return [];
|
|
1165
1170
|
const pages = content?.pages || [];
|
|
1166
|
-
const parent = page
|
|
1167
|
-
const binding = page.
|
|
1171
|
+
const parent = parentPageOf(page, pages);
|
|
1172
|
+
const binding = isDynamicRoute(page.route) && Object.keys(params).length ? routeBinding(page.route, params, page.paramName ?? null) : null;
|
|
1168
1173
|
const options = {
|
|
1169
1174
|
locale,
|
|
1170
1175
|
defaultLocale: resolveDefaultLocale$1(content?.config) ?? null,
|
|
@@ -1172,29 +1177,30 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
|
1172
1177
|
services: content?.config?.services ?? null,
|
|
1173
1178
|
variables: binding?.variables ?? null
|
|
1174
1179
|
};
|
|
1180
|
+
const siteFetch = content?.config?.fetch ?? null;
|
|
1181
|
+
const routeKey = binding ? routeQuery({ page: page.fetch, parent: parent?.fetch, site: siteFetch, sections: sectionFetches(page.sections) })?.key ?? null : null;
|
|
1175
1182
|
const out = /* @__PURE__ */ new Map();
|
|
1183
|
+
const put = (cfg) => {
|
|
1184
|
+
const key = deriveCacheKey$1(cfg);
|
|
1185
|
+
if (!out.has(key)) out.set(key, cfg);
|
|
1186
|
+
};
|
|
1176
1187
|
const add = (sources) => {
|
|
1177
1188
|
for (const cfg of resolveFetchConfigs(sources, options).values()) {
|
|
1178
|
-
|
|
1179
|
-
if (
|
|
1189
|
+
put(cfg);
|
|
1190
|
+
if (routeKey && cfg.as === routeKey && cfg.detail && binding.paramValue !== void 0) {
|
|
1191
|
+
const detailCfg = buildDetailConfig(cfg, { paramName: binding.paramName, paramValue: String(binding.paramValue) });
|
|
1192
|
+
if (detailCfg) put(detailCfg);
|
|
1193
|
+
}
|
|
1180
1194
|
}
|
|
1181
1195
|
};
|
|
1182
|
-
add([page.fetch ?? null, parent?.fetch ?? null,
|
|
1196
|
+
add([page.fetch ?? null, parent?.fetch ?? null, siteFetch]);
|
|
1183
1197
|
const walk = (sections) => {
|
|
1184
1198
|
for (const s of sections || []) {
|
|
1185
|
-
if (s?.fetch && !isRefinement(s.fetch)) add([s.fetch, page.fetch ?? null, parent?.fetch ?? null,
|
|
1199
|
+
if (s?.fetch && !isRefinement(s.fetch)) add([s.fetch, page.fetch ?? null, parent?.fetch ?? null, siteFetch]);
|
|
1186
1200
|
if (s?.subsections) walk(s.subsections);
|
|
1187
1201
|
}
|
|
1188
1202
|
};
|
|
1189
1203
|
walk(page.sections);
|
|
1190
|
-
if (binding && binding.paramValue !== void 0 && page.parentSchema) {
|
|
1191
|
-
const listCfg = [...out.values()].find((cfg) => cfg.as === page.parentSchema && cfg.detail);
|
|
1192
|
-
const detailCfg = listCfg ? buildDetailConfig(listCfg, { paramName: binding.paramName, paramValue: String(binding.paramValue) }) : null;
|
|
1193
|
-
if (detailCfg) {
|
|
1194
|
-
const key = deriveCacheKey$1(detailCfg);
|
|
1195
|
-
if (!out.has(key)) out.set(key, detailCfg);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
1204
|
return [...out.values()];
|
|
1199
1205
|
}
|
|
1200
1206
|
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always" } = {}) {
|