@uniweb/runtime 0.13.7 → 0.14.1
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 +500 -4
- package/dist/ssr.js.map +1 -1
- package/package.json +3 -3
- package/src/default-fetcher.js +47 -23
- package/src/prefetch.js +146 -0
- package/src/setup.js +4 -1
- package/src/ssr.js +10 -0
- package/src/wire-foundation.js +3 -0
package/dist/ssr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb } from "@uniweb/core";
|
|
1
|
+
import { isRichSchema, deriveCacheKey as deriveCacheKey$1, resolveDefaultLocale as resolveDefaultLocale$1, hasDarkScheme, createUniweb, resolveRequestStyle, resolveServiceUrl, substitutePlaceholders as substitutePlaceholders$1, matchWhere } 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";
|
|
@@ -282,7 +282,7 @@ function wireFoundationCapabilities(uniweb, foundation) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
function sliceContentForLocale(content, locale) {
|
|
285
|
-
const defaultLang = resolveDefaultLocale(content?.config);
|
|
285
|
+
const defaultLang = resolveDefaultLocale$1(content?.config);
|
|
286
286
|
const locData = content?.locales?.[locale];
|
|
287
287
|
if (!locale || locale === defaultLang || !locData) return content;
|
|
288
288
|
return {
|
|
@@ -298,7 +298,8 @@ function sliceContentForLocale(content, locale) {
|
|
|
298
298
|
function hydrateDataStore(website, fetchedData) {
|
|
299
299
|
if (!website?.dataStore || !fetchedData?.length) return;
|
|
300
300
|
for (const entry of fetchedData) {
|
|
301
|
-
|
|
301
|
+
if (entry.outcome && entry.outcome !== "fetched") continue;
|
|
302
|
+
website.dataStore.set(deriveCacheKey$1(entry.config), { data: entry.data });
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
function ensureThemeCss(uniweb, foundation) {
|
|
@@ -627,7 +628,7 @@ function renderLayout(page, website) {
|
|
|
627
628
|
function initPrerenderForLocale(content, foundation, locale, extensionsOrOptions, maybeOptions) {
|
|
628
629
|
const localeContent = sliceContentForLocale(content, locale);
|
|
629
630
|
const uniweb = initPrerender(localeContent, foundation, extensionsOrOptions, maybeOptions);
|
|
630
|
-
const defaultLang = resolveDefaultLocale(content?.config);
|
|
631
|
+
const defaultLang = resolveDefaultLocale$1(content?.config);
|
|
631
632
|
if (locale && locale !== defaultLang && uniweb.activeWebsite?.setActiveLocale) {
|
|
632
633
|
uniweb.activeWebsite.setActiveLocale(locale);
|
|
633
634
|
}
|
|
@@ -851,11 +852,504 @@ function generate404Html({ baseHtml, website, siteContent }) {
|
|
|
851
852
|
}
|
|
852
853
|
return { html, hasNotFoundPage: !!notFoundPage };
|
|
853
854
|
}
|
|
855
|
+
const DATA_DIR = "data";
|
|
856
|
+
const DATA_URL_PREFIX = `/${DATA_DIR}/`;
|
|
857
|
+
function queryDataUrl(name) {
|
|
858
|
+
return `${DATA_URL_PREFIX}${name}.json`;
|
|
859
|
+
}
|
|
860
|
+
function recordDataUrl(query, slug) {
|
|
861
|
+
return `${DATA_URL_PREFIX}${query}/${slug}.json`;
|
|
862
|
+
}
|
|
863
|
+
function isDataUrl(path) {
|
|
864
|
+
return typeof path === "string" && path.startsWith(DATA_URL_PREFIX);
|
|
865
|
+
}
|
|
866
|
+
const PLACEHOLDER_RE = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
867
|
+
function substitutePlaceholders(value, context, options = {}) {
|
|
868
|
+
const { encode = true } = options;
|
|
869
|
+
if (typeof value === "string") {
|
|
870
|
+
return value.replace(PLACEHOLDER_RE, (literal, key) => {
|
|
871
|
+
if (!(key in (context || {}))) return literal;
|
|
872
|
+
const raw = context[key];
|
|
873
|
+
if (raw === void 0 || raw === null) return literal;
|
|
874
|
+
return encode ? encodeURIComponent(String(raw)) : String(raw);
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
if (Array.isArray(value)) {
|
|
878
|
+
return value.map((item) => substitutePlaceholders(item, context, options));
|
|
879
|
+
}
|
|
880
|
+
if (value && typeof value === "object") {
|
|
881
|
+
const result = {};
|
|
882
|
+
for (const key of Object.keys(value)) {
|
|
883
|
+
result[key] = substitutePlaceholders(value[key], context, options);
|
|
884
|
+
}
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
const PATH_SLOT = "{path}";
|
|
890
|
+
const PARAM_SLOT = "{param}";
|
|
891
|
+
const warnedPatterns = /* @__PURE__ */ new Set();
|
|
892
|
+
function warnOnce(key, message) {
|
|
893
|
+
if (warnedPatterns.has(key)) return;
|
|
894
|
+
warnedPatterns.add(key);
|
|
895
|
+
console.warn(`[query-address] ${message}`);
|
|
896
|
+
}
|
|
897
|
+
function readPattern(lane, key) {
|
|
898
|
+
if (!lane || typeof lane !== "object" || Array.isArray(lane)) return null;
|
|
899
|
+
const pattern = lane[key];
|
|
900
|
+
return typeof pattern === "string" && pattern.length > 0 ? pattern : null;
|
|
901
|
+
}
|
|
902
|
+
function resolveQueryAddress(query, lane) {
|
|
903
|
+
if (typeof query !== "string" || query.length === 0) return null;
|
|
904
|
+
const pattern = readPattern(lane, "list");
|
|
905
|
+
if (!pattern) return null;
|
|
906
|
+
if (!pattern.includes(PATH_SLOT)) {
|
|
907
|
+
warnOnce(
|
|
908
|
+
`list:${pattern}`,
|
|
909
|
+
`config.records.list carries no ${PATH_SLOT} placeholder, so every query would resolve to the same address. Ignoring it and reading the compiled file instead.`
|
|
910
|
+
);
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
return substitutePlaceholders(pattern, { path: query });
|
|
914
|
+
}
|
|
915
|
+
function resolveRecordAddressPattern(query, lane) {
|
|
916
|
+
if (typeof query !== "string" || query.length === 0) return null;
|
|
917
|
+
const pattern = readPattern(lane, "record");
|
|
918
|
+
if (!pattern) return null;
|
|
919
|
+
if (!pattern.includes(PARAM_SLOT)) {
|
|
920
|
+
warnOnce(
|
|
921
|
+
`record:${pattern}`,
|
|
922
|
+
`config.records.record carries no ${PARAM_SLOT} placeholder, so every record would resolve to the same address. Ignoring it and reading the per-record file instead.`
|
|
923
|
+
);
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
return substitutePlaceholders(pattern, { path: query });
|
|
927
|
+
}
|
|
928
|
+
function localizeConfig(cfg, locale, defaultLocale) {
|
|
929
|
+
if (!cfg.path) return cfg;
|
|
930
|
+
if (!locale || locale === defaultLocale) return cfg;
|
|
931
|
+
if (!isDataUrl(cfg.path)) return cfg;
|
|
932
|
+
return { ...cfg, path: `/${locale}${cfg.path}` };
|
|
933
|
+
}
|
|
934
|
+
function applyDeferredDetail(cfg, queries, records) {
|
|
935
|
+
if (cfg.detail !== void 0) return cfg;
|
|
936
|
+
if (cfg.endpoint) {
|
|
937
|
+
const recordPattern = resolveRecordAddressPattern(cfg.query, records);
|
|
938
|
+
if (recordPattern) return { ...cfg, detail: recordPattern };
|
|
939
|
+
}
|
|
940
|
+
const queryName = cfg.query || bindingKey(cfg);
|
|
941
|
+
if (!queryName || !queries) return cfg;
|
|
942
|
+
const collConfig = queries[queryName];
|
|
943
|
+
if (!collConfig || typeof collConfig !== "object") return cfg;
|
|
944
|
+
const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null;
|
|
945
|
+
if (!deferred || deferred.length === 0) return cfg;
|
|
946
|
+
const pattern = typeof collConfig.detailUrl === "string" ? collConfig.detailUrl : recordDataUrl(queryName, "{slug}");
|
|
947
|
+
return { ...cfg, detail: pattern };
|
|
948
|
+
}
|
|
949
|
+
function resolveQuerySource(cfg, records) {
|
|
950
|
+
if (typeof cfg.query !== "string" || cfg.query.length === 0) return cfg;
|
|
951
|
+
const endpoint = resolveQueryAddress(cfg.query, records);
|
|
952
|
+
if (endpoint) {
|
|
953
|
+
const { path, url, ...rest } = cfg;
|
|
954
|
+
return { ...rest, endpoint };
|
|
955
|
+
}
|
|
956
|
+
return { ...cfg, path: queryDataUrl(cfg.query) };
|
|
957
|
+
}
|
|
958
|
+
function bindingKey(cfg) {
|
|
959
|
+
return cfg?.as;
|
|
960
|
+
}
|
|
961
|
+
function resolveFetchConfigs(sources, options = {}) {
|
|
962
|
+
const {
|
|
963
|
+
schemas = [],
|
|
964
|
+
locale = null,
|
|
965
|
+
defaultLocale = null,
|
|
966
|
+
queries = null,
|
|
967
|
+
records = null
|
|
968
|
+
} = options;
|
|
969
|
+
const configs = /* @__PURE__ */ new Map();
|
|
970
|
+
const collectAll = schemas.length === 0;
|
|
971
|
+
for (const source of sources) {
|
|
972
|
+
if (!source) continue;
|
|
973
|
+
const configList = Array.isArray(source) ? source : [source];
|
|
974
|
+
for (const cfg of configList) {
|
|
975
|
+
const key = bindingKey(cfg);
|
|
976
|
+
if (!key) continue;
|
|
977
|
+
if (configs.has(key)) continue;
|
|
978
|
+
if (!collectAll && !schemas.includes(key)) continue;
|
|
979
|
+
const sourced = resolveQuerySource(cfg, records);
|
|
980
|
+
const localized = localizeConfig(sourced, locale, defaultLocale);
|
|
981
|
+
configs.set(key, applyDeferredDetail(localized, queries, records));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return configs;
|
|
985
|
+
}
|
|
986
|
+
function deriveCacheKey(request) {
|
|
987
|
+
const { path, url, endpoint, transform } = request || {};
|
|
988
|
+
const as = request?.as;
|
|
989
|
+
const method = request?.method && request.method.toUpperCase() !== "GET" ? request.method.toUpperCase() : void 0;
|
|
990
|
+
const body = method === "POST" ? request?.body : void 0;
|
|
991
|
+
return JSON.stringify({ path, url, endpoint, as, transform, method, body });
|
|
992
|
+
}
|
|
993
|
+
function codeOf(entry) {
|
|
994
|
+
if (typeof entry === "string" && entry.trim() && entry.trim() !== "*") return entry.trim();
|
|
995
|
+
if (entry && typeof entry === "object" && typeof entry.code === "string" && entry.code.trim()) {
|
|
996
|
+
return entry.code.trim();
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
function normalizeLanguageList(value) {
|
|
1001
|
+
if (!Array.isArray(value)) return [];
|
|
1002
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1003
|
+
const codes = [];
|
|
1004
|
+
for (const entry of value) {
|
|
1005
|
+
const code = codeOf(entry);
|
|
1006
|
+
if (!code || seen.has(code)) continue;
|
|
1007
|
+
seen.add(code);
|
|
1008
|
+
codes.push(code);
|
|
1009
|
+
}
|
|
1010
|
+
return codes;
|
|
1011
|
+
}
|
|
1012
|
+
function resolveDefaultLocale(config = {}) {
|
|
1013
|
+
if (typeof config?.defaultLanguage === "string" && config.defaultLanguage.trim()) {
|
|
1014
|
+
return config.defaultLanguage.trim();
|
|
1015
|
+
}
|
|
1016
|
+
return normalizeLanguageList(config?.languages)[0] || "en";
|
|
1017
|
+
}
|
|
1018
|
+
const KNOWN_OPERATORS = /* @__PURE__ */ new Set(["where", "limit", "sort"]);
|
|
1019
|
+
function createDefaultFetcher({ basePath = "", config = {}, dev = false, records = null, fetch: fetchImpl = null } = {}) {
|
|
1020
|
+
const doFetch = (input, init) => (fetchImpl || globalThis.fetch)(input, init);
|
|
1021
|
+
const pathPrefix = basePath && basePath !== "/" ? basePath.replace(/\/$/, "") : "";
|
|
1022
|
+
const baseUrl = typeof config?.baseUrl === "string" ? config.baseUrl.replace(/\/$/, "") : "";
|
|
1023
|
+
const staticHeaders = buildStaticHeaders(config?.headers);
|
|
1024
|
+
const supports = normalizeSupports(config?.supports);
|
|
1025
|
+
const requestConfig = config?.request && typeof config.request === "object" ? config.request : {};
|
|
1026
|
+
const styleName = typeof requestConfig.style === "string" ? requestConfig.style : null;
|
|
1027
|
+
const style = resolveRequestStyle(styleName, { dev });
|
|
1028
|
+
const rename = normalizeRename(requestConfig.rename, style, { dev });
|
|
1029
|
+
const siteEnvelope = config?.envelope && typeof config.envelope === "object" ? config.envelope : null;
|
|
1030
|
+
const envelope = { ...style.defaultEnvelope || {}, ...siteEnvelope || {} };
|
|
1031
|
+
const stampedArrayKey = records?.envelope && typeof records.envelope === "object" && typeof records.envelope.records === "string" && records.envelope.records.length ? records.envelope.records : null;
|
|
1032
|
+
const laneEnvelope = stampedArrayKey ? { list: stampedArrayKey } : null;
|
|
1033
|
+
return {
|
|
1034
|
+
/**
|
|
1035
|
+
* Cache-key function. The default-fetcher's cache key includes only
|
|
1036
|
+
* the operators it pushes down (because they affect what the source
|
|
1037
|
+
* sees). Operators applied as runtime fallback operate on a shared
|
|
1038
|
+
* cached value and therefore must NOT split the cache.
|
|
1039
|
+
*
|
|
1040
|
+
* Example: with `supports: []`, two pages declaring different
|
|
1041
|
+
* `where:` clauses against the same path share one cache entry —
|
|
1042
|
+
* the file is fetched once and each page filters its own copy. With
|
|
1043
|
+
* `supports: [where]`, the same two pages fire two requests because
|
|
1044
|
+
* the predicate travels in the request.
|
|
1045
|
+
*/
|
|
1046
|
+
cacheKey(request) {
|
|
1047
|
+
const base = deriveCacheKey$1(request);
|
|
1048
|
+
const projected = {};
|
|
1049
|
+
for (const op of supports) {
|
|
1050
|
+
if (!style.canPush.has(op)) continue;
|
|
1051
|
+
if (request[op] !== void 0) projected[op] = request[op];
|
|
1052
|
+
}
|
|
1053
|
+
if (Object.keys(projected).length === 0 && style.name === "json-body") {
|
|
1054
|
+
return base;
|
|
1055
|
+
}
|
|
1056
|
+
return base + "::style=" + style.name + "::" + JSON.stringify(projected);
|
|
1057
|
+
},
|
|
1058
|
+
async resolve(request, ctx = {}) {
|
|
1059
|
+
if (!request) return { data: null };
|
|
1060
|
+
const { path, url, endpoint, transform, body: rawBody } = request;
|
|
1061
|
+
let method = (request.method || "GET").toUpperCase();
|
|
1062
|
+
if (method !== "GET" && method !== "POST") {
|
|
1063
|
+
console.warn(`[default-fetcher] method "${request.method}" is not supported — falling back to GET.`);
|
|
1064
|
+
method = "GET";
|
|
1065
|
+
}
|
|
1066
|
+
let target;
|
|
1067
|
+
let isRemote;
|
|
1068
|
+
if (endpoint) {
|
|
1069
|
+
target = resolveServiceUrl(endpoint, pathPrefix);
|
|
1070
|
+
isRemote = true;
|
|
1071
|
+
} else if (path) {
|
|
1072
|
+
target = pathPrefix && path.startsWith("/") && !path.startsWith("//") ? pathPrefix + path : path;
|
|
1073
|
+
isRemote = false;
|
|
1074
|
+
} else if (url) {
|
|
1075
|
+
target = isAbsoluteUrl(url) ? url : joinUrl(baseUrl, url);
|
|
1076
|
+
isRemote = true;
|
|
1077
|
+
} else {
|
|
1078
|
+
return { data: [], error: "No path, url or endpoint specified" };
|
|
1079
|
+
}
|
|
1080
|
+
const init = { signal: ctx.signal, method };
|
|
1081
|
+
const headers = {};
|
|
1082
|
+
if (isRemote && staticHeaders) Object.assign(headers, staticHeaders);
|
|
1083
|
+
const pushCandidates = /* @__PURE__ */ new Set();
|
|
1084
|
+
if (isRemote) {
|
|
1085
|
+
for (const op of KNOWN_OPERATORS) {
|
|
1086
|
+
if (supports.has(op) && style.canPush.has(op) && request[op] !== void 0 && request[op] !== null) {
|
|
1087
|
+
pushCandidates.add(op);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const encoded = pushCandidates.size > 0 ? style.encode(request, { method, pushCandidates, rename }) : { queryParams: [], bodyMerge: null, pushed: /* @__PURE__ */ new Set() };
|
|
1092
|
+
const pushedOperators = encoded.pushed;
|
|
1093
|
+
if (encoded.queryParams.length > 0 && method === "GET") {
|
|
1094
|
+
target = appendStyleQueryParams(target, encoded.queryParams);
|
|
1095
|
+
}
|
|
1096
|
+
if (method === "POST") {
|
|
1097
|
+
const dc = request.dynamicContext;
|
|
1098
|
+
const resolvedBody = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders$1(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
|
|
1099
|
+
const finalBody = composePostBody(resolvedBody, encoded.bodyMerge);
|
|
1100
|
+
if (finalBody !== null) {
|
|
1101
|
+
if (!hasHeader(headers, "Content-Type")) {
|
|
1102
|
+
headers["Content-Type"] = "application/json";
|
|
1103
|
+
}
|
|
1104
|
+
init.body = typeof finalBody === "string" ? finalBody : JSON.stringify(finalBody);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (Object.keys(headers).length) init.headers = headers;
|
|
1108
|
+
try {
|
|
1109
|
+
const response = await doFetch(target, init);
|
|
1110
|
+
const requestEnvelope = request.envelope && typeof request.envelope === "object" ? request.envelope : null;
|
|
1111
|
+
const effectiveEnvelope = requestEnvelope ?? (endpoint && laneEnvelope ? { ...envelope, ...laneEnvelope } : envelope);
|
|
1112
|
+
if (!response.ok) {
|
|
1113
|
+
let extracted;
|
|
1114
|
+
if (effectiveEnvelope.error) {
|
|
1115
|
+
try {
|
|
1116
|
+
const text = await response.text();
|
|
1117
|
+
const body = safeParseJSON(text);
|
|
1118
|
+
if (body !== void 0) {
|
|
1119
|
+
const candidate = getNestedValue(body, effectiveEnvelope.error);
|
|
1120
|
+
if (typeof candidate === "string" && candidate.length) {
|
|
1121
|
+
extracted = candidate;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
} catch {
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
data: [],
|
|
1129
|
+
error: extracted ?? `HTTP ${response.status}: ${response.statusText}`
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
const contentType = response.headers.get("content-type") || "";
|
|
1133
|
+
let data;
|
|
1134
|
+
if (contentType.includes("application/json")) {
|
|
1135
|
+
data = await response.json();
|
|
1136
|
+
} else {
|
|
1137
|
+
const text = await response.text();
|
|
1138
|
+
try {
|
|
1139
|
+
data = JSON.parse(text);
|
|
1140
|
+
} catch {
|
|
1141
|
+
data = text;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const isDetailRequest = !!request.dynamicContext;
|
|
1145
|
+
const effectiveTransform = transform || (isDetailRequest ? effectiveEnvelope.item : effectiveEnvelope.list);
|
|
1146
|
+
if (effectiveTransform && data !== null && data !== void 0) {
|
|
1147
|
+
data = getNestedValue(data, effectiveTransform);
|
|
1148
|
+
}
|
|
1149
|
+
data = applyFallbackOperators(data, request, pushedOperators);
|
|
1150
|
+
return { data: data ?? [] };
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
if (error?.name === "AbortError") {
|
|
1153
|
+
return { data: [], error: "aborted" };
|
|
1154
|
+
}
|
|
1155
|
+
return { data: [], error: error?.message || String(error) };
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function normalizeRename(raw, style, { dev }) {
|
|
1161
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
1162
|
+
const out = {};
|
|
1163
|
+
for (const [op, wireName] of Object.entries(raw)) {
|
|
1164
|
+
if (typeof wireName !== "string" || wireName.length === 0) continue;
|
|
1165
|
+
if (dev && !style.canPush.has(op) && !warnedRenameTargets.has(op)) {
|
|
1166
|
+
warnedRenameTargets.add(op);
|
|
1167
|
+
console.warn(
|
|
1168
|
+
`[default-fetcher] request.rename: operator "${op}" is not pushed by style "${style.name}" — rename has no effect. Known operators for this style: ${[...style.canPush].join(", ") || "(none)"}.`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
out[op] = wireName;
|
|
1172
|
+
}
|
|
1173
|
+
return Object.keys(out).length ? out : null;
|
|
1174
|
+
}
|
|
1175
|
+
const warnedRenameTargets = /* @__PURE__ */ new Set();
|
|
1176
|
+
function normalizeSupports(raw) {
|
|
1177
|
+
const out = /* @__PURE__ */ new Set();
|
|
1178
|
+
if (!Array.isArray(raw)) return out;
|
|
1179
|
+
for (const op of raw) {
|
|
1180
|
+
if (typeof op !== "string") continue;
|
|
1181
|
+
if (KNOWN_OPERATORS.has(op)) out.add(op);
|
|
1182
|
+
else if (!warnedUnknownOperators.has(op)) {
|
|
1183
|
+
warnedUnknownOperators.add(op);
|
|
1184
|
+
console.warn(`[default-fetcher] supports: unknown operator "${op}" — ignored.`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
const warnedUnknownOperators = /* @__PURE__ */ new Set();
|
|
1190
|
+
function appendStyleQueryParams(url, pairs) {
|
|
1191
|
+
if (!pairs || pairs.length === 0) return url;
|
|
1192
|
+
const params = pairs.map(
|
|
1193
|
+
([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v)
|
|
1194
|
+
);
|
|
1195
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
1196
|
+
return url + sep + params.join("&");
|
|
1197
|
+
}
|
|
1198
|
+
function composePostBody(authorBody, bodyMerge) {
|
|
1199
|
+
if (!bodyMerge) {
|
|
1200
|
+
return authorBody === void 0 ? null : authorBody;
|
|
1201
|
+
}
|
|
1202
|
+
if (typeof authorBody === "string") {
|
|
1203
|
+
return authorBody;
|
|
1204
|
+
}
|
|
1205
|
+
const base = authorBody && typeof authorBody === "object" ? authorBody : {};
|
|
1206
|
+
return { ...base, ...bodyMerge };
|
|
1207
|
+
}
|
|
1208
|
+
function applyFallbackOperators(data, request, pushedOperators) {
|
|
1209
|
+
if (!Array.isArray(data)) return data;
|
|
1210
|
+
let result = data;
|
|
1211
|
+
if (request.where && !pushedOperators.has("where")) {
|
|
1212
|
+
result = matchWhere(request.where, result);
|
|
1213
|
+
}
|
|
1214
|
+
if (request.sort && !pushedOperators.has("sort")) {
|
|
1215
|
+
result = applySortFallback(result, request.sort);
|
|
1216
|
+
}
|
|
1217
|
+
if (typeof request.limit === "number" && request.limit > 0 && !pushedOperators.has("limit")) {
|
|
1218
|
+
result = result.slice(0, request.limit);
|
|
1219
|
+
}
|
|
1220
|
+
return result;
|
|
1221
|
+
}
|
|
1222
|
+
function applySortFallback(items, sortExpr) {
|
|
1223
|
+
const sorts = String(sortExpr).split(",").map((s) => {
|
|
1224
|
+
const [field, dir = "asc"] = s.trim().split(/\s+/);
|
|
1225
|
+
return { field, desc: dir.toLowerCase() === "desc" };
|
|
1226
|
+
});
|
|
1227
|
+
return [...items].sort((a, b) => {
|
|
1228
|
+
for (const { field, desc } of sorts) {
|
|
1229
|
+
const av = getNestedValue(a, field) ?? "";
|
|
1230
|
+
const bv = getNestedValue(b, field) ?? "";
|
|
1231
|
+
if (av < bv) return desc ? 1 : -1;
|
|
1232
|
+
if (av > bv) return desc ? -1 : 1;
|
|
1233
|
+
}
|
|
1234
|
+
return 0;
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
function buildStaticHeaders(headers) {
|
|
1238
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) return null;
|
|
1239
|
+
const out = {};
|
|
1240
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
1241
|
+
if (v === null || v === void 0) continue;
|
|
1242
|
+
out[k] = String(v);
|
|
1243
|
+
}
|
|
1244
|
+
return Object.keys(out).length ? out : null;
|
|
1245
|
+
}
|
|
1246
|
+
function hasHeader(headers, name) {
|
|
1247
|
+
const lower = name.toLowerCase();
|
|
1248
|
+
return Object.keys(headers).some((k) => k.toLowerCase() === lower);
|
|
1249
|
+
}
|
|
1250
|
+
function isAbsoluteUrl(url) {
|
|
1251
|
+
if (typeof url !== "string") return false;
|
|
1252
|
+
if (url.startsWith("//")) return true;
|
|
1253
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(url);
|
|
1254
|
+
}
|
|
1255
|
+
function joinUrl(baseUrl, url) {
|
|
1256
|
+
if (!baseUrl) return url;
|
|
1257
|
+
if (url.startsWith("/")) return baseUrl + url;
|
|
1258
|
+
return baseUrl + "/" + url;
|
|
1259
|
+
}
|
|
1260
|
+
function getNestedValue(obj, path) {
|
|
1261
|
+
if (!obj || !path) return obj;
|
|
1262
|
+
let current = obj;
|
|
1263
|
+
for (const part of path.split(".")) {
|
|
1264
|
+
if (current === null || current === void 0) return void 0;
|
|
1265
|
+
current = current[part];
|
|
1266
|
+
}
|
|
1267
|
+
return current;
|
|
1268
|
+
}
|
|
1269
|
+
function safeParseJSON(text) {
|
|
1270
|
+
try {
|
|
1271
|
+
return JSON.parse(text);
|
|
1272
|
+
} catch {
|
|
1273
|
+
return void 0;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const isRefinement = (f) => f && typeof f === "object" && f.refine === true;
|
|
1277
|
+
function findPageForRoute(content, route) {
|
|
1278
|
+
const pages = content?.pages || [];
|
|
1279
|
+
const exact = pages.find((p) => p.route === route);
|
|
1280
|
+
if (exact) return { page: exact, params: {} };
|
|
1281
|
+
for (const page of pages) {
|
|
1282
|
+
if (!page.isDynamic || !page.route) continue;
|
|
1283
|
+
const compiled = routePatternToRegex(page.route);
|
|
1284
|
+
const m = compiled?.regex ? compiled.regex.exec(route) : null;
|
|
1285
|
+
if (m) return { page, params: Object.fromEntries((compiled.paramNames || []).map((n, i) => [n, m[i + 1]])) };
|
|
1286
|
+
}
|
|
1287
|
+
return { page: null, params: {} };
|
|
1288
|
+
}
|
|
1289
|
+
function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
|
|
1290
|
+
const { page } = findPageForRoute(content, route);
|
|
1291
|
+
if (!page) return [];
|
|
1292
|
+
const pages = content?.pages || [];
|
|
1293
|
+
const parent = page.parent ? pages.find((p) => p.route === page.parent) : null;
|
|
1294
|
+
const options = {
|
|
1295
|
+
locale,
|
|
1296
|
+
defaultLocale: resolveDefaultLocale(content?.config) ?? null,
|
|
1297
|
+
queries: content?.config?.queries ?? null,
|
|
1298
|
+
records: content?.config?.records ?? null
|
|
1299
|
+
};
|
|
1300
|
+
const out = /* @__PURE__ */ new Map();
|
|
1301
|
+
const add = (sources) => {
|
|
1302
|
+
for (const cfg of resolveFetchConfigs(sources, options).values()) {
|
|
1303
|
+
const key = deriveCacheKey(cfg);
|
|
1304
|
+
if (!out.has(key)) out.set(key, cfg);
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
add([page.fetch ?? null, parent?.fetch ?? null, content?.config?.fetch ?? null]);
|
|
1308
|
+
const walk = (sections) => {
|
|
1309
|
+
for (const s of sections || []) {
|
|
1310
|
+
if (s?.fetch && !isRefinement(s.fetch)) add([s.fetch, page.fetch ?? null, parent?.fetch ?? null, content?.config?.fetch ?? null]);
|
|
1311
|
+
if (s?.subsections) walk(s.subsections);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
walk(page.sections);
|
|
1315
|
+
return [...out.values()];
|
|
1316
|
+
}
|
|
1317
|
+
async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always" } = {}) {
|
|
1318
|
+
if (prerender !== "author" && prerender !== "always") {
|
|
1319
|
+
throw new Error(`executeFetchConfigs: prerender must be 'author' or 'always', got ${JSON.stringify(prerender)}`);
|
|
1320
|
+
}
|
|
1321
|
+
const fetcher = createDefaultFetcher({
|
|
1322
|
+
basePath: content?.config?.base || "",
|
|
1323
|
+
config: content?.config?.fetcher ?? {},
|
|
1324
|
+
records: content?.config?.records ?? null,
|
|
1325
|
+
dev,
|
|
1326
|
+
fetch: fetch2
|
|
1327
|
+
});
|
|
1328
|
+
const ctx = { website: null };
|
|
1329
|
+
const out = [];
|
|
1330
|
+
for (const config of configs || []) {
|
|
1331
|
+
if (!config) continue;
|
|
1332
|
+
if (prerender === "author" && config.prerender === false) {
|
|
1333
|
+
out.push({ config, outcome: "skipped", data: null });
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const result = await fetcher.resolve(config, ctx);
|
|
1337
|
+
if (result?.error) out.push({ config, outcome: "failed", data: null, error: result.error });
|
|
1338
|
+
else out.push({ config, outcome: "fetched", data: result?.data ?? null });
|
|
1339
|
+
}
|
|
1340
|
+
return out;
|
|
1341
|
+
}
|
|
1342
|
+
async function prefetchPageData({ content, route, locale = null, fetch: fetch2 = null, dev = false, prerender = "always" }) {
|
|
1343
|
+
const configs = resolvePageFetchConfigs(content, route, { locale });
|
|
1344
|
+
return executeFetchConfigs(configs, { content, fetch: fetch2, dev, prerender });
|
|
1345
|
+
}
|
|
854
1346
|
export {
|
|
855
1347
|
applyDefaults,
|
|
856
1348
|
applySchemas,
|
|
857
1349
|
classifyRenderError,
|
|
858
1350
|
escapeHtml,
|
|
1351
|
+
executeFetchConfigs,
|
|
1352
|
+
findPageForRoute,
|
|
859
1353
|
generate404Html,
|
|
860
1354
|
getComponentDefaults,
|
|
861
1355
|
getComponentMeta,
|
|
@@ -866,6 +1360,7 @@ export {
|
|
|
866
1360
|
initPrerenderForLocale,
|
|
867
1361
|
injectPageContent,
|
|
868
1362
|
prefetchIcons,
|
|
1363
|
+
prefetchPageData,
|
|
869
1364
|
prepareProps,
|
|
870
1365
|
renderAppearanceBootScript,
|
|
871
1366
|
renderBackground,
|
|
@@ -874,6 +1369,7 @@ export {
|
|
|
874
1369
|
renderLayout,
|
|
875
1370
|
renderPage,
|
|
876
1371
|
resolvePage,
|
|
1372
|
+
resolvePageFetchConfigs,
|
|
877
1373
|
sliceContentForLocale
|
|
878
1374
|
};
|
|
879
1375
|
//# sourceMappingURL=ssr.js.map
|