@finesoft/front 0.1.31 → 0.1.33

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.
Files changed (33) hide show
  1. package/dist/app-OOLQDVXA.js +10 -0
  2. package/dist/browser.cjs +13 -9
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.cts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/browser.js +2 -2
  7. package/dist/{chunk-ZMOE42LB.js → chunk-AYO3UUQC.js} +39 -37
  8. package/dist/chunk-AYO3UUQC.js.map +1 -0
  9. package/dist/{chunk-H3RNYNSD.js → chunk-OE5BU5MR.js} +2 -2
  10. package/dist/{chunk-BUYWNNNQ.js → chunk-OXKFPW4U.js} +14 -10
  11. package/dist/chunk-OXKFPW4U.js.map +1 -0
  12. package/dist/{chunk-FYP2ZYYV.js → chunk-PSPVIVC2.js} +2 -2
  13. package/dist/chunk-PSPVIVC2.js.map +1 -0
  14. package/dist/{chunk-M7VITIMR.js → chunk-UHQBKSHL.js} +3 -3
  15. package/dist/index.cjs +265 -49
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +75 -6
  18. package/dist/index.d.ts +75 -6
  19. package/dist/index.js +220 -12
  20. package/dist/index.js.map +1 -1
  21. package/dist/locale-CAZ4INCX.js +7 -0
  22. package/dist/{src-B5YAJXIM.js → src-MVACJWBF.js} +3 -3
  23. package/package.json +1 -1
  24. package/dist/app-IA6JQC7V.js +0 -10
  25. package/dist/chunk-BUYWNNNQ.js.map +0 -1
  26. package/dist/chunk-FYP2ZYYV.js.map +0 -1
  27. package/dist/chunk-ZMOE42LB.js.map +0 -1
  28. package/dist/locale-YK3THSI6.js +0 -7
  29. /package/dist/{app-IA6JQC7V.js.map → app-OOLQDVXA.js.map} +0 -0
  30. /package/dist/{chunk-H3RNYNSD.js.map → chunk-OE5BU5MR.js.map} +0 -0
  31. /package/dist/{chunk-M7VITIMR.js.map → chunk-UHQBKSHL.js.map} +0 -0
  32. /package/dist/{locale-YK3THSI6.js.map → locale-CAZ4INCX.js.map} +0 -0
  33. /package/dist/{src-B5YAJXIM.js.map → src-MVACJWBF.js.map} +0 -0
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,44 +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 (e) {
1003
- console.error(
1004
- `[SSR] dispatch failed for intent "${match.intent.id}":`,
1005
- e
1006
- );
1007
- page = getErrorPage(500, "Internal error");
1008
- }
1009
- } else {
1010
- page = getErrorPage(404, "Page not found");
1011
- }
1012
- const result = renderApp(page, framework);
1013
- framework.dispose();
1014
- return {
1015
- html: result.html,
1016
- head: result.head,
1017
- css: result.css,
1018
- serverData,
1019
- renderMode: match?.renderMode
1020
- };
1021
1027
  }
1022
1028
  var init_render = __esm({
1023
1029
  "../ssr/src/render.ts"() {
@@ -1149,7 +1155,7 @@ function parseAcceptLanguage(header, supported, fallback) {
1149
1155
  const [lang, q] = part.trim().split(";q=");
1150
1156
  return {
1151
1157
  lang: lang.trim().toLowerCase(),
1152
- q: q ? parseFloat(q) : 1
1158
+ q: q ? parseFloat(q) || 0 : 1
1153
1159
  };
1154
1160
  }).sort((a, b) => b.q - a.q);
1155
1161
  for (const { lang } of langs) {
@@ -1352,6 +1358,7 @@ __export(index_exports, {
1352
1358
  deserializeServerData: () => deserializeServerData,
1353
1359
  detectRuntime: () => detectRuntime,
1354
1360
  finesoftFrontViteConfig: () => finesoftFrontViteConfig,
1361
+ generateProxyCode: () => generateProxyCode,
1355
1362
  generateUuid: () => generateUuid,
1356
1363
  getBaseUrl: () => getBaseUrl,
1357
1364
  injectCSRShell: () => injectCSRShell,
@@ -1373,6 +1380,7 @@ __export(index_exports, {
1373
1380
  registerActionHandlers: () => registerActionHandlers,
1374
1381
  registerExternalUrlHandler: () => registerExternalUrlHandler,
1375
1382
  registerFlowActionHandler: () => registerFlowActionHandler,
1383
+ registerProxyRoutes: () => registerProxyRoutes,
1376
1384
  removeHost: () => removeHost,
1377
1385
  removeQueryParams: () => removeQueryParams,
1378
1386
  removeScheme: () => removeScheme,
@@ -1726,6 +1734,120 @@ init_src();
1726
1734
  // src/index.ts
1727
1735
  init_src2();
1728
1736
 
1737
+ // ../server/src/proxy.ts
1738
+ function sanitizeProxyPath(raw) {
1739
+ if (raw.startsWith("//")) return null;
1740
+ return raw.startsWith("/") ? raw : `/${raw}`;
1741
+ }
1742
+ function validateConfig(config) {
1743
+ if (!config.prefix.startsWith("/")) {
1744
+ throw new Error(
1745
+ `[proxy] prefix must start with "/": "${config.prefix}"`
1746
+ );
1747
+ }
1748
+ if (!config.target.startsWith("https://")) {
1749
+ throw new Error(`[proxy] target must use HTTPS: "${config.target}"`);
1750
+ }
1751
+ }
1752
+ function registerProxyRoutes(app, configs) {
1753
+ for (const config of configs) {
1754
+ validateConfig(config);
1755
+ const methods = config.methods ?? ["all"];
1756
+ const pattern = `${config.prefix}/*`;
1757
+ const handler = async (c) => {
1758
+ const subPath = sanitizeProxyPath(
1759
+ c.req.path.replace(config.prefix, "")
1760
+ );
1761
+ if (!subPath) return c.text("Invalid path", 400);
1762
+ const targetUrl = new URL(subPath, config.target);
1763
+ const reqUrl = new URL(c.req.url);
1764
+ reqUrl.searchParams.forEach(
1765
+ (v, k) => targetUrl.searchParams.set(k, v)
1766
+ );
1767
+ const headers = { ...config.headers };
1768
+ if (config.auth) {
1769
+ const token = process.env[config.auth.envKey] ?? "";
1770
+ if (token) {
1771
+ headers.Authorization = config.auth.type === "bearer" ? `Bearer ${token}` : `Basic ${token}`;
1772
+ }
1773
+ }
1774
+ try {
1775
+ const resp = await fetch(targetUrl.toString(), {
1776
+ headers,
1777
+ redirect: config.followRedirects ? "follow" : "manual"
1778
+ });
1779
+ const body = await resp.text();
1780
+ const respHeaders = {
1781
+ "Content-Type": resp.headers.get("Content-Type") ?? "application/json"
1782
+ };
1783
+ if (config.cache) {
1784
+ respHeaders["Cache-Control"] = config.cache;
1785
+ }
1786
+ return c.newResponse(body, resp.status, respHeaders);
1787
+ } catch (e) {
1788
+ console.error(`[Proxy ${config.prefix}]`, e);
1789
+ return c.json({ error: "Proxy request failed" }, 502);
1790
+ }
1791
+ };
1792
+ for (const method of methods) {
1793
+ app[method](pattern, handler);
1794
+ }
1795
+ }
1796
+ }
1797
+ function generateProxyCode(configs) {
1798
+ if (!configs || configs.length === 0) return "";
1799
+ for (const config of configs) {
1800
+ validateConfig(config);
1801
+ }
1802
+ const blocks = [];
1803
+ blocks.push(`
1804
+ // \u2500\u2500\u2500 \u6846\u67B6\u58F0\u660E\u5F0F\u4EE3\u7406\u8DEF\u7531 \u2500\u2500\u2500
1805
+ function _sanitizeProxyPath(raw) {
1806
+ if (raw.startsWith("//")) return null;
1807
+ return raw.startsWith("/") ? raw : "/" + raw;
1808
+ }
1809
+ `);
1810
+ for (const config of configs) {
1811
+ const methods = config.methods ?? ["all"];
1812
+ const pattern = `"${config.prefix}/*"`;
1813
+ const headersJson = JSON.stringify(config.headers ?? {});
1814
+ const cacheStr = config.cache ? JSON.stringify(config.cache) : "null";
1815
+ const redirect = config.followRedirects ? '"follow"' : '"manual"';
1816
+ let authCode = "";
1817
+ if (config.auth) {
1818
+ const envKey = JSON.stringify(config.auth.envKey);
1819
+ const prefix = config.auth.type === "bearer" ? "Bearer " : "Basic ";
1820
+ authCode = `
1821
+ const _token = (typeof process !== "undefined" && process.env && process.env[${envKey}]) || "";
1822
+ if (_token) _headers.Authorization = "${prefix}" + _token;`;
1823
+ }
1824
+ const handlerCode = `async (c) => {
1825
+ const _sub = _sanitizeProxyPath(c.req.path.replace(${JSON.stringify(
1826
+ config.prefix
1827
+ )}, ""));
1828
+ if (!_sub) return c.text("Invalid path", 400);
1829
+ const _target = new URL(_sub, ${JSON.stringify(config.target)});
1830
+ const _reqUrl = new URL(c.req.url);
1831
+ _reqUrl.searchParams.forEach((v, k) => _target.searchParams.set(k, v));
1832
+ const _headers = ${headersJson};${authCode}
1833
+ try {
1834
+ const _resp = await fetch(_target.toString(), { headers: _headers, redirect: ${redirect} });
1835
+ const _body = await _resp.text();
1836
+ const _rh = { "Content-Type": _resp.headers.get("Content-Type") || "application/json" };
1837
+ if (${cacheStr}) _rh["Cache-Control"] = ${cacheStr};
1838
+ return c.newResponse(_body, _resp.status, _rh);
1839
+ } catch (_e) {
1840
+ console.error("[Proxy ${config.prefix}]", _e);
1841
+ return c.json({ error: "Proxy request failed" }, 502);
1842
+ }
1843
+ }`;
1844
+ for (const method of methods) {
1845
+ blocks.push(`app.${method}(${pattern}, ${handlerCode});`);
1846
+ }
1847
+ }
1848
+ return blocks.join("\n");
1849
+ }
1850
+
1729
1851
  // ../server/src/adapters/shared.ts
1730
1852
  var BUILD_TOOL_EXTERNALS = [
1731
1853
  "vite",
@@ -1769,7 +1891,7 @@ function parseAcceptLanguage(header) {
1769
1891
  if (!header) return DEFAULT_LOCALE;
1770
1892
  const langs = header.split(",").map(p => {
1771
1893
  const [l, q] = p.trim().split(";q=");
1772
- return { l: l.trim().toLowerCase(), q: q ? +q : 1 };
1894
+ return { l: l.trim().toLowerCase(), q: q ? (+q || 0) : 1 };
1773
1895
  }).sort((a, b) => b.q - a.q);
1774
1896
  for (const { l } of langs) {
1775
1897
  const prefix = l.split("-")[0];
@@ -1799,7 +1921,8 @@ function matchRenderMode(url) {
1799
1921
  if (RENDER_MODES[path]) return RENDER_MODES[path];
1800
1922
  for (const [pattern, mode] of Object.entries(RENDER_MODES)) {
1801
1923
  if (pattern.includes("*")) {
1802
- const re = new RegExp("^" + pattern.replace(/\\*/g, ".*") + "$");
1924
+ const escaped = pattern.replace(/[.+?^\${}()|[\\]\\\\]/g, "\\\\$&");
1925
+ const re = new RegExp("^" + escaped.replace(/\\*/g, ".*") + "$");
1803
1926
  if (re.test(path)) return mode;
1804
1927
  }
1805
1928
  }
@@ -1807,6 +1930,7 @@ function matchRenderMode(url) {
1807
1930
  }
1808
1931
 
1809
1932
  const app = new Hono();
1933
+ ${generateProxyCode(ctx.proxies ?? [])}
1810
1934
  ${setupCall}
1811
1935
  ${opts.platformMiddleware ?? ""}
1812
1936
 
@@ -2359,7 +2483,8 @@ function resolveRenderMode(routePath, routeRenderMode, renderModes) {
2359
2483
  if (renderModes[routePath]) return renderModes[routePath];
2360
2484
  for (const [pattern, mode] of Object.entries(renderModes)) {
2361
2485
  if (pattern.includes("*")) {
2362
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2486
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2487
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
2363
2488
  if (re.test(routePath)) return mode;
2364
2489
  }
2365
2490
  }
@@ -2665,6 +2790,7 @@ async function createServer(config = {}) {
2665
2790
  defaultLocale,
2666
2791
  port = Number(process.env.PORT) || 3e3,
2667
2792
  setup,
2793
+ proxies,
2668
2794
  ssr
2669
2795
  } = config;
2670
2796
  const root = rootOverride ?? process.cwd();
@@ -2701,6 +2827,9 @@ async function createServer(config = {}) {
2701
2827
  });
2702
2828
  }
2703
2829
  const app = new import_hono3.Hono();
2830
+ if (proxies?.length) {
2831
+ registerProxyRoutes(app, proxies);
2832
+ }
2704
2833
  if (setup) {
2705
2834
  await setup(app);
2706
2835
  }
@@ -2744,7 +2873,8 @@ function matchRenderModeConfig(url, renderModes) {
2744
2873
  if (renderModes[path]) return renderModes[path];
2745
2874
  for (const [pattern, mode] of Object.entries(renderModes)) {
2746
2875
  if (pattern.includes("*")) {
2747
- const re = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
2876
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
2877
+ const re = new RegExp("^" + escaped.replace(/\*/g, ".*") + "$");
2748
2878
  if (re.test(path)) return mode;
2749
2879
  }
2750
2880
  }
@@ -2756,6 +2886,7 @@ function finesoftFrontViteConfig(options = {}) {
2756
2886
  let resolvedCommand;
2757
2887
  let resolvedResolve;
2758
2888
  let resolvedCss;
2889
+ const CSS_EXTENSIONS = /\.(css|scss|less|sass|styl|stylus|pcss|postcss)($|\?)/;
2759
2890
  return {
2760
2891
  name: "finesoft-front",
2761
2892
  config(userConfig, env) {
@@ -2775,6 +2906,82 @@ function finesoftFrontViteConfig(options = {}) {
2775
2906
  resolvedCss = config.css;
2776
2907
  root = config.root;
2777
2908
  },
2909
+ /**
2910
+ * Dev 模式 CSS 内联 — 消除 SSR 首屏布局抖动
2911
+ *
2912
+ * Vite dev 模式下,global.scss 等非组件 CSS 通过 JS 模块系统异步加载,
2913
+ * 导致 SSR HTML 初次渲染缺少布局关键样式(box-sizing、flex 布局、padding-top 等)。
2914
+ *
2915
+ * 此 hook 在 HTML 模板变换阶段(SSR 渲染之前):
2916
+ * 1. 找到浏览器入口脚本(排除 /@vite/client 等内部脚本)
2917
+ * 2. 编译入口脚本,填充 Vite 模块图
2918
+ * 3. 遍历模块图收集所有 CSS 依赖(排除 .svelte 组件 CSS,由 SSR 渲染自行处理)
2919
+ * 4. 通过 ssrLoadModule 获取编译后 CSS(SCSS→CSS)
2920
+ * 5. 注入 <style data-vite-dev-id> 标签到 <head>
2921
+ *
2922
+ * data-vite-dev-id 确保 Vite HMR 客户端复用已有标签,避免重复注入。
2923
+ */
2924
+ transformIndexHtml: {
2925
+ order: "pre",
2926
+ async handler(html, ctx) {
2927
+ const server = ctx.server;
2928
+ if (!server) return;
2929
+ const urlPath = (ctx.originalUrl || ctx.path || "").split(
2930
+ "?"
2931
+ )[0];
2932
+ if (/\.\w+$/.test(urlPath) && !urlPath.endsWith(".html")) {
2933
+ return;
2934
+ }
2935
+ const scripts = [
2936
+ ...html.matchAll(
2937
+ /<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/g
2938
+ )
2939
+ ];
2940
+ const appEntry = scripts.find((m) => !m[1].startsWith("/@"));
2941
+ if (!appEntry) return;
2942
+ const browserEntry = appEntry[1];
2943
+ try {
2944
+ await server.transformRequest(browserEntry);
2945
+ } catch {
2946
+ return;
2947
+ }
2948
+ const cssUrls = [];
2949
+ const visited = /* @__PURE__ */ new Set();
2950
+ function walk(mod) {
2951
+ if (!mod?.url || visited.has(mod.url)) return;
2952
+ visited.add(mod.url);
2953
+ if (CSS_EXTENSIONS.test(mod.url) && !mod.url.includes(".svelte")) {
2954
+ cssUrls.push(mod.url);
2955
+ }
2956
+ if (mod.importedModules) {
2957
+ for (const imported of mod.importedModules) {
2958
+ walk(imported);
2959
+ }
2960
+ }
2961
+ }
2962
+ const mg = server.moduleGraph;
2963
+ const browserMod = await mg.getModuleByUrl(browserEntry);
2964
+ if (browserMod) walk(browserMod);
2965
+ if (cssUrls.length === 0) return;
2966
+ const tags = [];
2967
+ for (const url of cssUrls) {
2968
+ try {
2969
+ const mod = await server.ssrLoadModule(url);
2970
+ const css = mod?.default;
2971
+ if (typeof css === "string" && css.length > 0) {
2972
+ tags.push({
2973
+ tag: "style",
2974
+ attrs: { "data-vite-dev-id": url },
2975
+ children: css,
2976
+ injectTo: "head"
2977
+ });
2978
+ }
2979
+ } catch {
2980
+ }
2981
+ }
2982
+ return tags;
2983
+ }
2984
+ },
2778
2985
  // ─── Dev ───────────────────────────────────────────────
2779
2986
  configureServer(server) {
2780
2987
  return async () => {
@@ -2788,6 +2995,9 @@ function finesoftFrontViteConfig(options = {}) {
2788
2995
  "@hono/node-server"
2789
2996
  );
2790
2997
  const app = new HonoClass();
2998
+ if (options.proxies?.length) {
2999
+ registerProxyRoutes(app, options.proxies);
3000
+ }
2791
3001
  if (typeof options.setup === "function") {
2792
3002
  await options.setup(app);
2793
3003
  } else if (typeof options.setup === "string") {
@@ -2838,6 +3048,9 @@ function finesoftFrontViteConfig(options = {}) {
2838
3048
  );
2839
3049
  const app = new HonoClass();
2840
3050
  const isrCache = /* @__PURE__ */ new Map();
3051
+ if (options.proxies?.length) {
3052
+ registerProxyRoutes(app, options.proxies);
3053
+ }
2841
3054
  if (typeof options.setup === "function") {
2842
3055
  await options.setup(app);
2843
3056
  } else if (typeof options.setup === "string") {
@@ -2992,6 +3205,7 @@ function finesoftFrontViteConfig(options = {}) {
2992
3205
  defaultLocale,
2993
3206
  templateHtml,
2994
3207
  renderModes: options.renderModes,
3208
+ proxies: options.proxies,
2995
3209
  resolvedResolve,
2996
3210
  resolvedCss,
2997
3211
  vite,
@@ -3049,6 +3263,7 @@ function finesoftFrontViteConfig(options = {}) {
3049
3263
  deserializeServerData,
3050
3264
  detectRuntime,
3051
3265
  finesoftFrontViteConfig,
3266
+ generateProxyCode,
3052
3267
  generateUuid,
3053
3268
  getBaseUrl,
3054
3269
  injectCSRShell,
@@ -3070,6 +3285,7 @@ function finesoftFrontViteConfig(options = {}) {
3070
3285
  registerActionHandlers,
3071
3286
  registerExternalUrlHandler,
3072
3287
  registerFlowActionHandler,
3288
+ registerProxyRoutes,
3073
3289
  removeHost,
3074
3290
  removeQueryParams,
3075
3291
  removeScheme,