@uniweb/runtime 0.14.2 → 0.16.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 CHANGED
@@ -1,14 +1,15 @@
1
- import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, resolveRequestStyle, resolveServiceUrl, substitutePlaceholders, matchWhere } from "@uniweb/core";
1
+ import { isRichSchema, deriveCacheKey, resolveDefaultLocale, hasDarkScheme, createUniweb, substitutePlaceholders, resolveServiceUrl, 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
- import { routePatternToRegex } from "@uniweb/core/route-match";
5
+ import { routePatternToRegex, decodeRouteValue, splitPathCapture } from "@uniweb/core/route-match";
6
6
  import { DEFAULT_ICON_BASE, iconUrl } from "@uniweb/core/icon-corpus";
7
7
  import { buildTheme, FONT_LINKS_MARKER, buildSectionOverrides } from "@uniweb/theming";
8
8
  import "@uniweb/core/services";
9
9
  import "@uniweb/core/tracker";
10
10
  import { resolveFetchConfigs } from "@uniweb/core/fetch-config";
11
11
  import { deriveCacheKey as deriveCacheKey$1 } from "@uniweb/core/datastore";
12
+ import { buildDetailConfig } from "@uniweb/core/detail-url";
12
13
  import { resolveDefaultLocale as resolveDefaultLocale$1 } from "@uniweb/core/locale-config";
13
14
  function guaranteeItemStructure(item) {
14
15
  return {
@@ -285,7 +286,7 @@ function hydrateDataStore(website, fetchedData) {
285
286
  if (!website?.dataStore || !fetchedData?.length) return;
286
287
  for (const entry of fetchedData) {
287
288
  if (entry.outcome && entry.outcome !== "fetched") continue;
288
- website.dataStore.set(deriveCacheKey(entry.config), { data: entry.data });
289
+ website.dataStore.set(deriveCacheKey(entry.config), entry.meta ? { data: entry.data, meta: entry.meta } : { data: entry.data });
289
290
  }
290
291
  }
291
292
  function ensureThemeCss(uniweb, foundation) {
@@ -838,108 +839,80 @@ function generate404Html({ baseHtml, website, siteContent }) {
838
839
  }
839
840
  return { html, hasNotFoundPage: !!notFoundPage };
840
841
  }
841
- const KNOWN_OPERATORS = /* @__PURE__ */ new Set(["where", "limit", "sort"]);
842
- function createDefaultFetcher({ basePath = "", config = {}, dev = false, records = null, fetch: fetchImpl = null } = {}) {
842
+ function createDefaultFetcher({ basePath = "", dev = false, fetch: fetchImpl = null } = {}) {
843
843
  const doFetch = (input, init) => (fetchImpl || globalThis.fetch)(input, init);
844
844
  const pathPrefix = basePath && basePath !== "/" ? basePath.replace(/\/$/, "") : "";
845
- const baseUrl = typeof config?.baseUrl === "string" ? config.baseUrl.replace(/\/$/, "") : "";
846
- const staticHeaders = buildStaticHeaders(config?.headers);
847
- const supports = normalizeSupports(config?.supports);
848
- const requestConfig = config?.request && typeof config.request === "object" ? config.request : {};
849
- const styleName = typeof requestConfig.style === "string" ? requestConfig.style : null;
850
- const style = resolveRequestStyle(styleName, { dev });
851
- const rename = normalizeRename(requestConfig.rename, style, { dev });
852
- const siteEnvelope = config?.envelope && typeof config.envelope === "object" ? config.envelope : null;
853
- const envelope = { ...style.defaultEnvelope || {}, ...siteEnvelope || {} };
854
- const stampedArrayKey = records?.envelope && typeof records.envelope === "object" && typeof records.envelope.records === "string" && records.envelope.records.length ? records.envelope.records : null;
855
- const laneEnvelope = stampedArrayKey ? { list: stampedArrayKey } : null;
845
+ const doorQueues = /* @__PURE__ */ new Map();
846
+ const askDoor = (request, ctx) => {
847
+ if (typeof request.schema !== "string" || !request.schema) {
848
+ return Promise.resolve({
849
+ data: null,
850
+ error: `the payload stamps a records door but carries no Model ref for query "${request.query ?? request.as}" (config.queries) — the door cannot be asked`
851
+ });
852
+ }
853
+ return new Promise((resolve) => {
854
+ const url = resolveServiceUrl(request.door, pathPrefix);
855
+ let queue = doorQueues.get(url);
856
+ if (!queue) {
857
+ queue = [];
858
+ doorQueues.set(url, queue);
859
+ queueMicrotask(() => {
860
+ doorQueues.delete(url);
861
+ flushDoor(url, queue, doFetch);
862
+ });
863
+ }
864
+ queue.push({ request, ctx, resolve });
865
+ });
866
+ };
856
867
  return {
857
868
  /**
858
- * Cache-key function. The default-fetcher's cache key includes only
859
- * the operators it pushes down (because they affect what the source
860
- * sees). Operators applied as runtime fallback operate on a shared
861
- * cached value and therefore must NOT split the cache.
862
- *
863
- * Example: with `supports: []`, two pages declaring different
864
- * `where:` clauses against the same path share one cache entry —
865
- * the file is fetched once and each page filters its own copy. With
866
- * `supports: [where]`, the same two pages fire two requests because
867
- * the predicate travels in the request.
869
+ * The cache identity is the request's ADDRESS or, on a question door,
870
+ * the QUESTION (`deriveCacheKey` hashes every operator of an address-less
871
+ * request). Operators evaluated here run over a shared cached value and
872
+ * must NOT split the cache: two pages declaring different `where:` clauses
873
+ * against the same path share one entry — the file is fetched once and
874
+ * each page filters its own copy.
868
875
  */
869
876
  cacheKey(request) {
870
- const base = deriveCacheKey(request);
871
- const projected = {};
872
- for (const op of supports) {
873
- if (!style.canPush.has(op)) continue;
874
- if (request[op] !== void 0) projected[op] = request[op];
875
- }
876
- if (Object.keys(projected).length === 0 && style.name === "json-body") {
877
- return base;
878
- }
879
- return base + "::style=" + style.name + "::" + JSON.stringify(projected);
877
+ return deriveCacheKey(request);
880
878
  },
881
879
  async resolve(request, ctx = {}) {
882
880
  if (!request) return { data: null };
883
- const { path, url, endpoint, transform, body: rawBody } = request;
881
+ if (request.door) return askDoor(request, ctx);
882
+ const { path, url, transform, body: rawBody } = request;
884
883
  let method = (request.method || "GET").toUpperCase();
885
884
  if (method !== "GET" && method !== "POST") {
886
885
  console.warn(`[default-fetcher] method "${request.method}" is not supported — falling back to GET.`);
887
886
  method = "GET";
888
887
  }
889
888
  let target;
890
- let isRemote;
891
- if (endpoint) {
892
- target = resolveServiceUrl(endpoint, pathPrefix);
893
- isRemote = true;
894
- } else if (path) {
889
+ if (path) {
895
890
  target = pathPrefix && path.startsWith("/") && !path.startsWith("//") ? pathPrefix + path : path;
896
- isRemote = false;
897
891
  } else if (url) {
898
- target = isAbsoluteUrl(url) ? url : joinUrl(baseUrl, url);
899
- isRemote = true;
892
+ target = url;
900
893
  } else {
901
- return { data: [], error: "No path, url or endpoint specified" };
894
+ return { data: [], error: "No path, url or door specified" };
902
895
  }
903
896
  const init = { signal: ctx.signal, method };
904
- const headers = {};
905
- if (isRemote && staticHeaders) Object.assign(headers, staticHeaders);
906
- const pushCandidates = /* @__PURE__ */ new Set();
907
- if (isRemote) {
908
- for (const op of KNOWN_OPERATORS) {
909
- if (supports.has(op) && style.canPush.has(op) && request[op] !== void 0 && request[op] !== null) {
910
- pushCandidates.add(op);
911
- }
912
- }
913
- }
914
- const encoded = pushCandidates.size > 0 ? style.encode(request, { method, pushCandidates, rename }) : { queryParams: [], bodyMerge: null, pushed: /* @__PURE__ */ new Set() };
915
- const pushedOperators = encoded.pushed;
916
- if (encoded.queryParams.length > 0 && method === "GET") {
917
- target = appendStyleQueryParams(target, encoded.queryParams);
918
- }
919
897
  if (method === "POST") {
920
898
  const dc = request.dynamicContext;
921
- const resolvedBody = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
922
- const finalBody = composePostBody(resolvedBody, encoded.bodyMerge);
923
- if (finalBody !== null) {
924
- if (!hasHeader(headers, "Content-Type")) {
925
- headers["Content-Type"] = "application/json";
926
- }
927
- init.body = typeof finalBody === "string" ? finalBody : JSON.stringify(finalBody);
899
+ const body = rawBody !== void 0 && rawBody !== null && dc && dc.paramName ? substitutePlaceholders(rawBody, { [dc.paramName]: dc.paramValue }, { encode: false }) : rawBody;
900
+ if (body !== void 0 && body !== null) {
901
+ init.headers = { "Content-Type": "application/json" };
902
+ init.body = typeof body === "string" ? body : JSON.stringify(body);
928
903
  }
929
904
  }
930
- if (Object.keys(headers).length) init.headers = headers;
931
905
  try {
932
906
  const response = await doFetch(target, init);
933
- const requestEnvelope = request.envelope && typeof request.envelope === "object" ? request.envelope : null;
934
- const effectiveEnvelope = requestEnvelope ?? (endpoint && laneEnvelope ? { ...envelope, ...laneEnvelope } : envelope);
907
+ const envelope = request.envelope && typeof request.envelope === "object" ? request.envelope : {};
935
908
  if (!response.ok) {
936
909
  let extracted;
937
- if (effectiveEnvelope.error) {
910
+ if (envelope.error) {
938
911
  try {
939
912
  const text = await response.text();
940
913
  const body = safeParseJSON(text);
941
914
  if (body !== void 0) {
942
- const candidate = getNestedValue(body, effectiveEnvelope.error);
915
+ const candidate = getNestedValue(body, envelope.error);
943
916
  if (typeof candidate === "string" && candidate.length) {
944
917
  extracted = candidate;
945
918
  }
@@ -965,12 +938,13 @@ function createDefaultFetcher({ basePath = "", config = {}, dev = false, records
965
938
  }
966
939
  }
967
940
  const isDetailRequest = !!request.dynamicContext;
968
- const effectiveTransform = transform || (isDetailRequest ? effectiveEnvelope.item : effectiveEnvelope.list);
941
+ const effectiveTransform = transform || (isDetailRequest ? envelope.item : envelope.list);
969
942
  if (effectiveTransform && data !== null && data !== void 0) {
970
943
  data = getNestedValue(data, effectiveTransform);
971
944
  }
972
- data = applyFallbackOperators(data, request, pushedOperators);
973
- return { data: data ?? [] };
945
+ data = applyOperators(data, request, { dev });
946
+ const depth = request.depth === "brief" || request.depth === "full" ? request.depth : void 0;
947
+ return depth ? { data: data ?? [], meta: { depth } } : { data: data ?? [] };
974
948
  } catch (error) {
975
949
  if (error?.name === "AbortError") {
976
950
  return { data: [], error: "aborted" };
@@ -980,105 +954,109 @@ function createDefaultFetcher({ basePath = "", config = {}, dev = false, records
980
954
  }
981
955
  };
982
956
  }
983
- function normalizeRename(raw, style, { dev }) {
984
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
985
- const out = {};
986
- for (const [op, wireName] of Object.entries(raw)) {
987
- if (typeof wireName !== "string" || wireName.length === 0) continue;
988
- if (dev && !style.canPush.has(op) && !warnedRenameTargets.has(op)) {
989
- warnedRenameTargets.add(op);
990
- console.warn(
991
- `[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)"}.`
992
- );
993
- }
994
- out[op] = wireName;
995
- }
996
- return Object.keys(out).length ? out : null;
957
+ function doorQuestion(request) {
958
+ const q = { schema: request.schema };
959
+ let where = request.where && typeof request.where === "object" ? request.where : null;
960
+ let scope = typeof request.scope === "string" && request.scope ? request.scope : null;
961
+ if (where && !scope && where.path && typeof where.path === "object" && typeof where.path.under === "string" && where.path.under) {
962
+ const { path, ...rest } = where;
963
+ scope = path.under;
964
+ where = Object.keys(rest).length ? rest : null;
965
+ }
966
+ if (scope) q.scope = scope;
967
+ if (where) q.where = renameOperators(where);
968
+ const sort = sortToWire(request.sort);
969
+ if (sort) q.sort = sort;
970
+ if (typeof request.limit === "number" && request.limit > 0) q.limit = request.limit;
971
+ if (request.depth === "brief" || request.depth === "full") q.depth = request.depth;
972
+ return q;
997
973
  }
998
- const warnedRenameTargets = /* @__PURE__ */ new Set();
999
- function normalizeSupports(raw) {
1000
- const out = /* @__PURE__ */ new Set();
1001
- if (!Array.isArray(raw)) return out;
1002
- for (const op of raw) {
1003
- if (typeof op !== "string") continue;
1004
- if (KNOWN_OPERATORS.has(op)) out.add(op);
1005
- else if (!warnedUnknownOperators.has(op)) {
1006
- warnedUnknownOperators.add(op);
1007
- console.warn(`[default-fetcher] supports: unknown operator "${op}" — ignored.`);
1008
- }
974
+ const DOOR_OPERATOR = { nin: "not_in" };
975
+ function renameOperators(where) {
976
+ if (Array.isArray(where)) return where.map(renameOperators);
977
+ if (!where || typeof where !== "object") return where;
978
+ const out = {};
979
+ for (const [key, value] of Object.entries(where)) {
980
+ out[DOOR_OPERATOR[key] ?? key] = value && typeof value === "object" ? renameOperators(value) : value;
1009
981
  }
1010
982
  return out;
1011
983
  }
1012
- const warnedUnknownOperators = /* @__PURE__ */ new Set();
1013
- function appendStyleQueryParams(url, pairs) {
1014
- if (!pairs || pairs.length === 0) return url;
1015
- const params = pairs.map(
1016
- ([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v)
1017
- );
1018
- const sep = url.includes("?") ? "&" : "?";
1019
- return url + sep + params.join("&");
1020
- }
1021
- function composePostBody(authorBody, bodyMerge) {
1022
- if (!bodyMerge) {
1023
- return authorBody === void 0 ? null : authorBody;
1024
- }
1025
- if (typeof authorBody === "string") {
1026
- return authorBody;
1027
- }
1028
- const base = authorBody && typeof authorBody === "object" ? authorBody : {};
1029
- return { ...base, ...bodyMerge };
984
+ async function flushDoor(url, queue, doFetch) {
985
+ const body = {};
986
+ const keys = [];
987
+ for (const entry of queue) {
988
+ const base = entry.request.as || "q";
989
+ let key = base;
990
+ for (let n = 2; key in body; n += 1) key = `${base}#${n}`;
991
+ keys.push(key);
992
+ body[key] = doorQuestion(entry.request);
993
+ }
994
+ let parsed;
995
+ try {
996
+ const response = await doFetch(url, {
997
+ method: "POST",
998
+ headers: { "Content-Type": "application/json" },
999
+ body: JSON.stringify(body)
1000
+ });
1001
+ if (!response.ok) {
1002
+ let detail = null;
1003
+ try {
1004
+ const problem = safeParseJSON(await response.text());
1005
+ if (problem && typeof problem.detail === "string" && problem.detail) detail = problem.detail;
1006
+ } catch {
1007
+ }
1008
+ const error = detail ? `HTTP ${response.status}: ${detail}` : `HTTP ${response.status}: ${response.statusText}`;
1009
+ for (const entry of queue) entry.resolve({ data: null, error });
1010
+ return;
1011
+ }
1012
+ parsed = await response.json();
1013
+ } catch (error) {
1014
+ const message = error?.name === "AbortError" ? "aborted" : error?.message || String(error);
1015
+ for (const entry of queue) entry.resolve({ data: null, error: message });
1016
+ return;
1017
+ }
1018
+ const data = parsed && typeof parsed.data === "object" && parsed.data ? parsed.data : {};
1019
+ const errors = parsed && typeof parsed.errors === "object" && parsed.errors ? parsed.errors : {};
1020
+ const depths = parsed && typeof parsed.depths === "object" && parsed.depths ? parsed.depths : {};
1021
+ queue.forEach((entry, i) => {
1022
+ const key = keys[i];
1023
+ if (key in errors) {
1024
+ const e = errors[key];
1025
+ const detail = typeof e === "string" ? e : e?.detail || e?.message || JSON.stringify(e);
1026
+ const out = { data: null, error: detail };
1027
+ if (e && typeof e === "object" && typeof e.code === "string") out.code = e.code;
1028
+ entry.resolve(out);
1029
+ return;
1030
+ }
1031
+ if (!(key in data)) {
1032
+ entry.resolve({ data: null, error: `the records door answered without the key "${key}"` });
1033
+ return;
1034
+ }
1035
+ const depth = depths[key] === "brief" || depths[key] === "full" ? depths[key] : entry.request.depth === "brief" || entry.request.depth === "full" ? entry.request.depth : void 0;
1036
+ entry.resolve(depth ? { data: data[key], meta: { depth } } : { data: data[key] });
1037
+ });
1030
1038
  }
1031
- function applyFallbackOperators(data, request, pushedOperators) {
1039
+ function applyOperators(data, request, { dev = false } = {}) {
1032
1040
  if (!Array.isArray(data)) return data;
1033
1041
  let result = data;
1034
- if (request.where && !pushedOperators.has("where")) {
1035
- result = matchWhere(request.where, result);
1036
- }
1037
- if (request.sort && !pushedOperators.has("sort")) {
1038
- result = applySortFallback(result, request.sort);
1039
- }
1040
- if (typeof request.limit === "number" && request.limit > 0 && !pushedOperators.has("limit")) {
1041
- result = result.slice(0, request.limit);
1042
- }
1042
+ if (request.where) result = matchWhere(request.where, result);
1043
+ if (request.sort) result = applySort(result, request.sort, dev);
1044
+ if (typeof request.limit === "number" && request.limit > 0) result = result.slice(0, request.limit);
1043
1045
  return result;
1044
1046
  }
1045
- function applySortFallback(items, sortExpr) {
1046
- const sorts = String(sortExpr).split(",").map((s) => {
1047
- const [field, dir = "asc"] = s.trim().split(/\s+/);
1048
- return { field, desc: dir.toLowerCase() === "desc" };
1049
- });
1050
- return [...items].sort((a, b) => {
1051
- for (const { field, desc } of sorts) {
1052
- const av = getNestedValue(a, field) ?? "";
1053
- const bv = getNestedValue(b, field) ?? "";
1054
- if (av < bv) return desc ? 1 : -1;
1055
- if (av > bv) return desc ? -1 : 1;
1047
+ const warnedBadSorts = /* @__PURE__ */ new Set();
1048
+ function applySort(items, sortExpr, dev) {
1049
+ try {
1050
+ return sortRecords(items, sortExpr);
1051
+ } catch (err) {
1052
+ if (dev) throw err;
1053
+ const key = String(sortExpr);
1054
+ if (!warnedBadSorts.has(key)) {
1055
+ warnedBadSorts.add(key);
1056
+ console.error(`[default-fetcher] ${err.message} Records delivered unsorted.`);
1056
1057
  }
1057
- return 0;
1058
- });
1059
- }
1060
- function buildStaticHeaders(headers) {
1061
- if (!headers || typeof headers !== "object" || Array.isArray(headers)) return null;
1062
- const out = {};
1063
- for (const [k, v] of Object.entries(headers)) {
1064
- if (v === null || v === void 0) continue;
1065
- out[k] = String(v);
1058
+ return items;
1066
1059
  }
1067
- return Object.keys(out).length ? out : null;
1068
- }
1069
- function hasHeader(headers, name) {
1070
- const lower = name.toLowerCase();
1071
- return Object.keys(headers).some((k) => k.toLowerCase() === lower);
1072
- }
1073
- function isAbsoluteUrl(url) {
1074
- if (typeof url !== "string") return false;
1075
- if (url.startsWith("//")) return true;
1076
- return /^[a-z][a-z0-9+.-]*:\/\//i.test(url);
1077
- }
1078
- function joinUrl(baseUrl, url) {
1079
- if (!baseUrl) return url;
1080
- if (url.startsWith("/")) return baseUrl + url;
1081
- return baseUrl + "/" + url;
1082
1060
  }
1083
1061
  function getNestedValue(obj, path) {
1084
1062
  if (!obj || !path) return obj;
@@ -1105,20 +1083,39 @@ function findPageForRoute(content, route) {
1105
1083
  if (!page.isDynamic || !page.route) continue;
1106
1084
  const compiled = routePatternToRegex(page.route);
1107
1085
  const m = compiled?.regex ? compiled.regex.exec(route) : null;
1108
- if (m) return { page, params: Object.fromEntries((compiled.paramNames || []).map((n, i) => [n, m[i + 1]])) };
1086
+ if (m) {
1087
+ const params = {};
1088
+ (compiled.paramNames || []).forEach((n, i) => {
1089
+ const raw = m[i + 1];
1090
+ params[n] = n === compiled.catchAll ? raw.split("/").map(decodeRouteValue).join("/") : decodeRouteValue(raw);
1091
+ });
1092
+ return { page, params };
1093
+ }
1109
1094
  }
1110
1095
  return { page: null, params: {} };
1111
1096
  }
1097
+ function routeBinding(page, params) {
1098
+ const { catchAll } = routePatternToRegex(page.route);
1099
+ if (catchAll && params[catchAll] !== void 0) {
1100
+ const parts = splitPathCapture(params[catchAll]);
1101
+ const paramName2 = page.paramName || "slug";
1102
+ return { paramName: paramName2, paramValue: parts.slug, variables: { ...params, ...parts } };
1103
+ }
1104
+ const paramName = page.paramName || Object.keys(params)[0];
1105
+ return { paramName, paramValue: params[paramName], variables: { ...params } };
1106
+ }
1112
1107
  function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
1113
- const { page } = findPageForRoute(content, route);
1108
+ const { page, params } = findPageForRoute(content, route);
1114
1109
  if (!page) return [];
1115
1110
  const pages = content?.pages || [];
1116
1111
  const parent = page.parent ? pages.find((p) => p.route === page.parent) : null;
1112
+ const binding = page.isDynamic && Object.keys(params).length ? routeBinding(page, params) : null;
1117
1113
  const options = {
1118
1114
  locale,
1119
1115
  defaultLocale: resolveDefaultLocale$1(content?.config) ?? null,
1120
1116
  queries: content?.config?.queries ?? null,
1121
- records: content?.config?.records ?? null
1117
+ records: content?.config?.records ?? null,
1118
+ variables: binding?.variables ?? null
1122
1119
  };
1123
1120
  const out = /* @__PURE__ */ new Map();
1124
1121
  const add = (sources) => {
@@ -1135,6 +1132,14 @@ function resolvePageFetchConfigs(content, route, { locale = null } = {}) {
1135
1132
  }
1136
1133
  };
1137
1134
  walk(page.sections);
1135
+ if (binding && binding.paramValue !== void 0 && page.parentSchema) {
1136
+ const listCfg = [...out.values()].find((cfg) => cfg.as === page.parentSchema && cfg.detail);
1137
+ const detailCfg = listCfg ? buildDetailConfig(listCfg, { paramName: binding.paramName, paramValue: String(binding.paramValue) }) : null;
1138
+ if (detailCfg) {
1139
+ const key = deriveCacheKey$1(detailCfg);
1140
+ if (!out.has(key)) out.set(key, detailCfg);
1141
+ }
1142
+ }
1138
1143
  return [...out.values()];
1139
1144
  }
1140
1145
  async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev = false, prerender = "always" } = {}) {
@@ -1143,24 +1148,18 @@ async function executeFetchConfigs(configs, { content, fetch: fetch2 = null, dev
1143
1148
  }
1144
1149
  const fetcher = createDefaultFetcher({
1145
1150
  basePath: content?.config?.base || "",
1146
- config: content?.config?.fetcher ?? {},
1147
- records: content?.config?.records ?? null,
1148
1151
  dev,
1149
1152
  fetch: fetch2
1150
1153
  });
1151
1154
  const ctx = { website: null };
1152
- const out = [];
1153
- for (const config of configs || []) {
1154
- if (!config) continue;
1155
+ return Promise.all((configs || []).filter(Boolean).map(async (config) => {
1155
1156
  if (prerender === "author" && config.prerender === false) {
1156
- out.push({ config, outcome: "skipped", data: null });
1157
- continue;
1157
+ return { config, outcome: "skipped", data: null };
1158
1158
  }
1159
1159
  const result = await fetcher.resolve(config, ctx);
1160
- if (result?.error) out.push({ config, outcome: "failed", data: null, error: result.error });
1161
- else out.push({ config, outcome: "fetched", data: result?.data ?? null });
1162
- }
1163
- return out;
1160
+ if (result?.error) return { config, outcome: "failed", data: null, error: result.error };
1161
+ return { config, outcome: "fetched", data: result?.data ?? null, ...result?.meta ? { meta: result.meta } : {} };
1162
+ }));
1164
1163
  }
1165
1164
  async function prefetchPageData({ content, route, locale = null, fetch: fetch2 = null, dev = false, prerender = "always" }) {
1166
1165
  const configs = resolvePageFetchConfigs(content, route, { locale });