@finesoft/front 0.1.30 → 0.1.32

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/index.cjs CHANGED
@@ -397,13 +397,14 @@ var init_router = __esm({
397
397
  /** 添加路由规则 */
398
398
  add(pattern, intentId, renderMode) {
399
399
  const paramNames = [];
400
- const regexStr = pattern.replace(
401
- /\/:([\w]+)(\?)?/g,
402
- (_, name, optional) => {
403
- paramNames.push(name);
404
- return optional ? "(?:/([^/]+))?" : "/([^/]+)";
400
+ const regexStr = pattern.split(/(\/:[\w]+\??)/).map((segment) => {
401
+ const paramMatch = segment.match(/^\/:(\w+)(\?)?$/);
402
+ if (paramMatch) {
403
+ paramNames.push(paramMatch[1]);
404
+ return paramMatch[2] ? "(?:/([^/]+))?" : "/([^/]+)";
405
405
  }
406
- );
406
+ return segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
407
+ }).join("");
407
408
  this.routes.push({
408
409
  pattern,
409
410
  intentId,
@@ -507,15 +508,18 @@ var init_composite = __esm({
507
508
  });
508
509
 
509
510
  // ../core/src/prefetched-intents/stable-stringify.ts
510
- function stableStringify(obj) {
511
+ function stableStringify(obj, _seen) {
511
512
  if (obj === null || obj === void 0) return String(obj);
512
513
  if (typeof obj !== "object") return JSON.stringify(obj);
514
+ const seen = _seen ?? /* @__PURE__ */ new Set();
515
+ if (seen.has(obj)) return '"[Circular]"';
516
+ seen.add(obj);
513
517
  if (Array.isArray(obj)) {
514
- return "[" + obj.map(stableStringify).join(",") + "]";
518
+ return "[" + obj.map((v) => stableStringify(v, seen)).join(",") + "]";
515
519
  }
516
520
  const keys = Object.keys(obj).sort();
517
521
  const parts = keys.filter((k) => obj[k] !== void 0).map(
518
- (k) => JSON.stringify(k) + ":" + stableStringify(obj[k])
522
+ (k) => JSON.stringify(k) + ":" + stableStringify(obj[k], seen)
519
523
  );
520
524
  return "{" + parts.join(",") + "}";
521
525
  }
@@ -980,40 +984,46 @@ async function ssrRender(options) {
980
984
  const mergedConfig = ssrContext?.fetch ? { ...frameworkConfig, fetch: ssrContext.fetch } : frameworkConfig;
981
985
  const framework = Framework.create(mergedConfig);
982
986
  bootstrap(framework);
983
- const parsed = new URL(url, "http://localhost");
984
- const fullPath = parsed.pathname + parsed.search;
985
- const match = framework.routeUrl(fullPath);
986
- if (match?.renderMode === "csr") {
987
- framework.dispose();
987
+ try {
988
+ const parsed = new URL(url, "http://localhost");
989
+ const fullPath = parsed.pathname + parsed.search;
990
+ const match = framework.routeUrl(fullPath);
991
+ if (match?.renderMode === "csr") {
992
+ return {
993
+ html: "",
994
+ head: "",
995
+ css: "",
996
+ serverData: [],
997
+ renderMode: "csr"
998
+ };
999
+ }
1000
+ let page;
1001
+ let serverData = [];
1002
+ if (match) {
1003
+ try {
1004
+ page = await framework.dispatch(match.intent);
1005
+ serverData = [{ intent: match.intent, data: page }];
1006
+ } catch (e) {
1007
+ console.error(
1008
+ `[SSR] dispatch failed for intent "${match.intent.id}":`,
1009
+ e
1010
+ );
1011
+ page = getErrorPage(500, "Internal error");
1012
+ }
1013
+ } else {
1014
+ page = getErrorPage(404, "Page not found");
1015
+ }
1016
+ const result = renderApp(page, framework);
988
1017
  return {
989
- html: "",
990
- head: "",
991
- css: "",
992
- serverData: [],
993
- renderMode: "csr"
1018
+ html: result.html,
1019
+ head: result.head,
1020
+ css: result.css,
1021
+ serverData,
1022
+ renderMode: match?.renderMode
994
1023
  };
1024
+ } finally {
1025
+ framework.dispose();
995
1026
  }
996
- let page;
997
- let serverData = [];
998
- if (match) {
999
- try {
1000
- page = await framework.dispatch(match.intent);
1001
- serverData = [{ intent: match.intent, data: page }];
1002
- } catch {
1003
- page = getErrorPage(500, "Internal error");
1004
- }
1005
- } else {
1006
- page = getErrorPage(404, "Page not found");
1007
- }
1008
- const result = renderApp(page, framework);
1009
- framework.dispose();
1010
- return {
1011
- html: result.html,
1012
- head: result.head,
1013
- css: result.css,
1014
- serverData,
1015
- renderMode: match?.renderMode
1016
- };
1017
1027
  }
1018
1028
  var init_render = __esm({
1019
1029
  "../ssr/src/render.ts"() {
@@ -1113,32 +1123,22 @@ var init_src2 = __esm({
1113
1123
  });
1114
1124
 
1115
1125
  // ../server/src/internal-fetch.ts
1116
- function createInternalFetch(appFetch) {
1117
- let depth = 0;
1126
+ function createInternalFetch(appFetch, depth = 1) {
1118
1127
  return ((input, init) => {
1119
1128
  if (typeof input === "string" && input.startsWith("/")) {
1120
- if (depth >= MAX_INTERNAL_FETCH_DEPTH) {
1121
- return Promise.resolve(
1122
- new Response("Internal fetch loop detected", {
1123
- status: 508
1124
- })
1125
- );
1126
- }
1127
- depth++;
1128
- return Promise.resolve(
1129
- appFetch(new Request(`http://localhost${input}`, init))
1130
- ).finally(() => {
1131
- depth--;
1132
- });
1129
+ const request = new Request(`http://localhost${input}`, init);
1130
+ request.headers.set(SSR_DEPTH_HEADER, String(depth));
1131
+ return Promise.resolve(appFetch(request));
1133
1132
  }
1134
1133
  return globalThis.fetch(input, init);
1135
1134
  });
1136
1135
  }
1137
- var MAX_INTERNAL_FETCH_DEPTH;
1136
+ var SSR_DEPTH_HEADER, MAX_SSR_DEPTH;
1138
1137
  var init_internal_fetch = __esm({
1139
1138
  "../server/src/internal-fetch.ts"() {
1140
1139
  "use strict";
1141
- MAX_INTERNAL_FETCH_DEPTH = 5;
1140
+ SSR_DEPTH_HEADER = "x-ssr-depth";
1141
+ MAX_SSR_DEPTH = 5;
1142
1142
  }
1143
1143
  });
1144
1144
 
@@ -1155,7 +1155,7 @@ function parseAcceptLanguage(header, supported, fallback) {
1155
1155
  const [lang, q] = part.trim().split(";q=");
1156
1156
  return {
1157
1157
  lang: lang.trim().toLowerCase(),
1158
- q: q ? parseFloat(q) : 1
1158
+ q: q ? parseFloat(q) || 0 : 1
1159
1159
  };
1160
1160
  }).sort((a, b) => b.q - a.q);
1161
1161
  for (const { lang } of langs) {
@@ -1189,7 +1189,6 @@ function createSSRApp(options) {
1189
1189
  parentFetch
1190
1190
  } = options;
1191
1191
  const app = new import_hono.Hono();
1192
- const internalFetch = parentFetch ? createInternalFetch(parentFetch) : void 0;
1193
1192
  const ISR_CACHE_MAX = 1e3;
1194
1193
  const isrCache = /* @__PURE__ */ new Map();
1195
1194
  function isrSet(key, val) {
@@ -1199,6 +1198,7 @@ function createSSRApp(options) {
1199
1198
  }
1200
1199
  isrCache.set(key, val);
1201
1200
  }
1201
+ let templateCache;
1202
1202
  async function readTemplate(url) {
1203
1203
  if (!isProduction && vite) {
1204
1204
  const { readFileSync: readFileSync2 } = await import(
@@ -1212,11 +1212,13 @@ function createSSRApp(options) {
1212
1212
  const raw = readFileSync2(resolve2(root, "index.html"), "utf-8");
1213
1213
  return vite.transformIndexHtml(url, raw);
1214
1214
  }
1215
+ if (templateCache) return templateCache;
1215
1216
  const isDeno = typeof globalThis.Deno !== "undefined";
1216
1217
  if (isDeno) {
1217
- return globalThis.Deno.readTextFileSync(
1218
+ templateCache = globalThis.Deno.readTextFileSync(
1218
1219
  new URL("../dist/client/index.html", import_meta.url)
1219
1220
  );
1221
+ return templateCache;
1220
1222
  }
1221
1223
  const { readFileSync } = await import(
1222
1224
  /* @vite-ignore */
@@ -1226,7 +1228,11 @@ function createSSRApp(options) {
1226
1228
  /* @vite-ignore */
1227
1229
  "path"
1228
1230
  );
1229
- return readFileSync(resolve(root, "dist/client/index.html"), "utf-8");
1231
+ templateCache = readFileSync(
1232
+ resolve(root, "dist/client/index.html"),
1233
+ "utf-8"
1234
+ );
1235
+ return templateCache;
1230
1236
  }
1231
1237
  async function loadSSRModule() {
1232
1238
  if (!isProduction && vite) {
@@ -1253,11 +1259,12 @@ function createSSRApp(options) {
1253
1259
  );
1254
1260
  }
1255
1261
  app.get("*", async (c) => {
1262
+ const ssrDepth = parseInt(c.req.header(SSR_DEPTH_HEADER) ?? "0", 10);
1263
+ if (ssrDepth >= MAX_SSR_DEPTH) {
1264
+ return c.text("SSR recursion loop detected", 508);
1265
+ }
1256
1266
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
1257
1267
  try {
1258
- const cacheKey = url;
1259
- const cached = isrCache.get(cacheKey);
1260
- if (cached) return c.html(cached);
1261
1268
  const template = await readTemplate(url);
1262
1269
  const { render, serializeServerData: serializeServerData2 } = await loadSSRModule();
1263
1270
  const locale = parseAcceptLanguage(
@@ -1265,6 +1272,10 @@ function createSSRApp(options) {
1265
1272
  supportedLocales,
1266
1273
  defaultLocale
1267
1274
  );
1275
+ const cacheKey = `${locale}:${url}`;
1276
+ const cached = isrCache.get(cacheKey);
1277
+ if (cached) return c.html(cached);
1278
+ const requestFetch = parentFetch ? createInternalFetch(parentFetch, ssrDepth + 1) : void 0;
1268
1279
  const {
1269
1280
  html: appHtml,
1270
1281
  head,
@@ -1274,7 +1285,7 @@ function createSSRApp(options) {
1274
1285
  } = await render(
1275
1286
  url,
1276
1287
  locale,
1277
- internalFetch ? { fetch: internalFetch } : void 0
1288
+ requestFetch ? { fetch: requestFetch } : void 0
1278
1289
  );
1279
1290
  if (renderMode === "csr") {
1280
1291
  return c.html(injectCSRShell(template, locale));
@@ -1764,7 +1775,7 @@ function parseAcceptLanguage(header) {
1764
1775
  if (!header) return DEFAULT_LOCALE;
1765
1776
  const langs = header.split(",").map(p => {
1766
1777
  const [l, q] = p.trim().split(";q=");
1767
- return { l: l.trim().toLowerCase(), q: q ? +q : 1 };
1778
+ return { l: l.trim().toLowerCase(), q: q ? (+q || 0) : 1 };
1768
1779
  }).sort((a, b) => b.q - a.q);
1769
1780
  for (const { l } of langs) {
1770
1781
  const prefix = l.split("-")[0];
@@ -1794,7 +1805,8 @@ function matchRenderMode(url) {
1794
1805
  if (RENDER_MODES[path]) return RENDER_MODES[path];
1795
1806
  for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
1796
1807
  if (pattern.includes("*")) {
1797
- const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
1808
+ const escaped = pattern.replace(/[.+?^\${}()|[\\]\\\\]/g, "\\\\$&");
1809
+ const re = new RegExp("^" + escaped.replace(/\\*/g, ".*") + "$");
1798
1810
  if (re.test(path)) return mode;
1799
1811
  }
1800
1812
  }
@@ -1806,20 +1818,28 @@ ${setupCall}
1806
1818
  ${opts.platformMiddleware ?? ""}
1807
1819
 
1808
1820
  // \u5185\u90E8 fetch \u56DE\u73AF\uFF1ASSR \u63A7\u5236\u5668\u7684 fetch \u8BF7\u6C42\u76F4\u63A5\u8D70 Hono \u5185\u5B58\u8DEF\u7531
1809
- // \u53EA\u62E6\u622A\u76F8\u5BF9\u8DEF\u5F84\uFF08/api/* \u7B49\uFF09\uFF0C\u7EDD\u5BF9 URL \u8D70\u6B63\u5E38\u7F51\u7EDC\uFF1B\u542B\u9012\u5F52\u4FDD\u62A4
1810
- let _fetchDepth = 0;
1811
- function _internalFetch(input, init) {
1812
- if (typeof input === "string" && input.startsWith("/")) {
1813
- if (_fetchDepth >= 5) {
1814
- return Promise.resolve(new Response("Internal fetch loop detected", { status: 508 }));
1821
+ // \u6DF1\u5EA6\u901A\u8FC7\u8BF7\u6C42\u5934\u4F20\u9012\uFF0C\u5E76\u53D1\u5B89\u5168\u4E14\u80FD\u8DE8\u6E32\u67D3\u6B63\u786E\u8FFD\u8E2A\u9012\u5F52
1822
+ const _SSR_DEPTH_HEADER = "x-ssr-depth";
1823
+ const _MAX_SSR_DEPTH = 5;
1824
+
1825
+ function _createInternalFetch(depth) {
1826
+ return function(input, init) {
1827
+ if (typeof input === "string" && input.startsWith("/")) {
1828
+ const req = new Request("http://localhost" + input, init);
1829
+ req.headers.set(_SSR_DEPTH_HEADER, String(depth));
1830
+ return app.fetch(req);
1815
1831
  }
1816
- _fetchDepth++;
1817
- return app.fetch(new Request("http://localhost" + input, init)).finally(() => { _fetchDepth--; });
1818
- }
1819
- return globalThis.fetch(input, init);
1832
+ return globalThis.fetch(input, init);
1833
+ };
1820
1834
  }
1821
1835
 
1822
1836
  app.get("*", async (c) => {
1837
+ // \u9012\u5F52\u6DF1\u5EA6\u4FDD\u62A4\uFF1A\u4ECE\u8BF7\u6C42\u5934\u8BFB\u53D6 SSR \u6DF1\u5EA6
1838
+ const _ssrDepth = parseInt(c.req.header(_SSR_DEPTH_HEADER) || "0", 10);
1839
+ if (_ssrDepth >= _MAX_SSR_DEPTH) {
1840
+ return c.text("SSR recursion loop detected", 508);
1841
+ }
1842
+
1823
1843
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
1824
1844
  try {
1825
1845
  const locale = parseAcceptLanguage(c.req.header("accept-language"));
@@ -1830,11 +1850,12 @@ app.get("*", async (c) => {
1830
1850
  return c.html(injectCSRShell(TEMPLATE, locale));
1831
1851
  }
1832
1852
 
1833
- // ISR \u7F13\u5B58\u547D\u4E2D
1834
- const cached = await platformCacheGet(url);
1853
+ // ISR \u7F13\u5B58\u547D\u4E2D\uFF08key \u542B locale\uFF0C\u907F\u514D\u8DE8\u8BED\u8A00\u7F13\u5B58\u6C61\u67D3\uFF09
1854
+ const _cacheKey = locale + ":" + url;
1855
+ const cached = await platformCacheGet(_cacheKey);
1835
1856
  if (cached) return c.html(cached);
1836
1857
 
1837
- const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale, { fetch: _internalFetch });
1858
+ const { html: appHtml, head, css, serverData, renderMode } = await render(url, locale, { fetch: _createInternalFetch(_ssrDepth + 1) });
1838
1859
 
1839
1860
  // \u8DEF\u7531\u7EA7 CSR
1840
1861
  if (renderMode === "csr") {
@@ -1846,7 +1867,7 @@ app.get("*", async (c) => {
1846
1867
 
1847
1868
  // Prerender ISR \u7F13\u5B58\uFF08\u5305\u62EC Vite \u914D\u7F6E\u8986\u76D6\u548C\u8DEF\u7531\u7EA7\uFF09
1848
1869
  if (renderMode === "prerender" || overrideMode === "prerender") {
1849
- await platformCacheSet(url, finalHtml);
1870
+ await platformCacheSet(_cacheKey, finalHtml);
1850
1871
  ${opts.platformPrerenderResponseHook ?? ""}
1851
1872
  }
1852
1873
 
@@ -2345,7 +2366,8 @@ function resolveRenderMode(routePath, routeRenderMode, renderModes) {
2345
2366
  if (renderModes[routePath]) return renderModes[routePath];
2346
2367
  for (const [pattern, mode] of Object.entries(renderModes)) {
2347
2368
  if (pattern.includes("*")) {
2348
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2369
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2370
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
2349
2371
  if (re.test(routePath)) return mode;
2350
2372
  }
2351
2373
  }
@@ -2730,7 +2752,8 @@ function matchRenderModeConfig(url, renderModes) {
2730
2752
  if (renderModes[path]) return renderModes[path];
2731
2753
  for (const [pattern, mode] of Object.entries(renderModes)) {
2732
2754
  if (pattern.includes("*")) {
2733
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2755
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2756
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
2734
2757
  if (re.test(path)) return mode;
2735
2758
  }
2736
2759
  }
@@ -2855,6 +2878,13 @@ function finesoftFrontViteConfig(options = {}) {
2855
2878
  ssrPath
2856
2879
  );
2857
2880
  app.get("*", async (c) => {
2881
+ const ssrDepth = parseInt(
2882
+ c.req.header(SSR_DEPTH_HEADER) ?? "0",
2883
+ 10
2884
+ );
2885
+ if (ssrDepth >= MAX_SSR_DEPTH) {
2886
+ return c.text("SSR recursion loop detected", 508);
2887
+ }
2858
2888
  const url = c.req.path + (c.req.url.includes("?") ? "?" + c.req.url.split("?")[1] : "");
2859
2889
  try {
2860
2890
  const locale = parseAcceptLanguage2(
@@ -2869,7 +2899,8 @@ function finesoftFrontViteConfig(options = {}) {
2869
2899
  if (overrideMode === "csr") {
2870
2900
  return c.html(injectCSRShell2(template, locale));
2871
2901
  }
2872
- const cached = isrCache.get(url);
2902
+ const cacheKey = `${locale}:${url}`;
2903
+ const cached = isrCache.get(cacheKey);
2873
2904
  if (cached) return c.html(cached);
2874
2905
  const {
2875
2906
  html: appHtml,
@@ -2878,7 +2909,10 @@ function finesoftFrontViteConfig(options = {}) {
2878
2909
  serverData,
2879
2910
  renderMode
2880
2911
  } = await ssrModule.render(url, locale, {
2881
- fetch: createInternalFetch(app.fetch.bind(app))
2912
+ fetch: createInternalFetch(
2913
+ app.fetch.bind(app),
2914
+ ssrDepth + 1
2915
+ )
2882
2916
  });
2883
2917
  if (renderMode === "csr") {
2884
2918
  return c.html(injectCSRShell2(template, locale));
@@ -2893,7 +2927,7 @@ function finesoftFrontViteConfig(options = {}) {
2893
2927
  serializedData
2894
2928
  });
2895
2929
  if (renderMode === "prerender" || overrideMode === "prerender") {
2896
- isrCache.set(url, finalHtml);
2930
+ isrCache.set(cacheKey, finalHtml);
2897
2931
  }
2898
2932
  return c.html(finalHtml);
2899
2933
  } catch (e) {