@uniweb/runtime 0.14.0 → 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 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
- website.dataStore.set(deriveCacheKey(entry.config), { data: entry.data });
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