@faapi/faapi 1.5.0 → 2.0.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/cli/index.js.map +1 -1
- package/dist/index.d.ts +3 -694
- package/dist/index.js +1350 -1561
- package/dist/index.js.map +1 -1
- package/dist/routeTypes-FtbRkpVF.d.ts +491 -0
- package/dist/testing.d.ts +232 -0
- package/dist/testing.js +3578 -0
- package/dist/testing.js.map +1 -0
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1219,8 +1219,8 @@ __export(generateSchemaFiles_exports, {
|
|
|
1219
1219
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1220
1220
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1221
1221
|
});
|
|
1222
|
-
import
|
|
1223
|
-
import
|
|
1222
|
+
import path7 from "path";
|
|
1223
|
+
import fs6 from "fs/promises";
|
|
1224
1224
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1225
1225
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1226
1226
|
if (rel.startsWith("src/")) {
|
|
@@ -1228,7 +1228,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1228
1228
|
}
|
|
1229
1229
|
const idx = rel.lastIndexOf("/");
|
|
1230
1230
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1231
|
-
return
|
|
1231
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
1232
1232
|
}
|
|
1233
1233
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1234
1234
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1239,7 +1239,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1239
1239
|
}
|
|
1240
1240
|
const idx = rel.lastIndexOf("/");
|
|
1241
1241
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1242
|
-
return
|
|
1242
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
1243
1243
|
}
|
|
1244
1244
|
function getHelpersImportPath(relDir) {
|
|
1245
1245
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -1289,7 +1289,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
const fileEntries = [];
|
|
1291
1291
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1292
|
-
const relFile =
|
|
1292
|
+
const relFile = path7.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1293
1293
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1294
1294
|
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
1295
1295
|
let relForDir = relFile;
|
|
@@ -1304,7 +1304,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1304
1304
|
}
|
|
1305
1305
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1306
1306
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
1307
|
-
const helpersPath =
|
|
1307
|
+
const helpersPath = path7.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1308
1308
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1309
1309
|
}
|
|
1310
1310
|
await Promise.all(
|
|
@@ -1312,8 +1312,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1312
1312
|
);
|
|
1313
1313
|
}
|
|
1314
1314
|
async function writeSchemaFile(outputPath, source) {
|
|
1315
|
-
await
|
|
1316
|
-
await
|
|
1315
|
+
await fs6.mkdir(path7.dirname(outputPath), { recursive: true });
|
|
1316
|
+
await fs6.writeFile(outputPath, source, "utf-8");
|
|
1317
1317
|
}
|
|
1318
1318
|
var init_generateSchemaFiles = __esm({
|
|
1319
1319
|
"src/cli/generateSchemaFiles.ts"() {
|
|
@@ -1646,14 +1646,14 @@ var ValidationError = class extends FaapiError {
|
|
|
1646
1646
|
issues;
|
|
1647
1647
|
};
|
|
1648
1648
|
var RouteNotFoundError = class extends FaapiError {
|
|
1649
|
-
constructor(
|
|
1650
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
1649
|
+
constructor(path14) {
|
|
1650
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path14}`, 404);
|
|
1651
1651
|
this.name = "RouteNotFoundError";
|
|
1652
1652
|
}
|
|
1653
1653
|
};
|
|
1654
1654
|
var MethodNotAllowedError = class extends FaapiError {
|
|
1655
|
-
constructor(method,
|
|
1656
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
1655
|
+
constructor(method, path14, allowedMethods) {
|
|
1656
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path14}`, 405);
|
|
1657
1657
|
this.allowedMethods = allowedMethods;
|
|
1658
1658
|
this.name = "MethodNotAllowedError";
|
|
1659
1659
|
}
|
|
@@ -1672,1358 +1672,800 @@ var ModuleLoadError = class extends FaapiError {
|
|
|
1672
1672
|
}
|
|
1673
1673
|
};
|
|
1674
1674
|
|
|
1675
|
-
// src/
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1675
|
+
// src/cli/createAppCore.ts
|
|
1676
|
+
import fs11 from "fs";
|
|
1677
|
+
import path12 from "path";
|
|
1678
|
+
import { PassThrough, Readable as Readable3 } from "stream";
|
|
1679
|
+
|
|
1680
|
+
// src/router/sortRoutes.ts
|
|
1681
|
+
function sortRoutes(routes) {
|
|
1682
|
+
return [...routes].sort((a, b) => {
|
|
1683
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
1684
|
+
return a.isDynamic ? 1 : -1;
|
|
1685
|
+
}
|
|
1686
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
1687
|
+
return a.isCatchAll ? 1 : -1;
|
|
1688
|
+
}
|
|
1689
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1690
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1691
|
+
if (aSegments !== bSegments) {
|
|
1692
|
+
return aSegments - bSegments;
|
|
1693
|
+
}
|
|
1694
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/router/detectRouteConflicts.ts
|
|
1699
|
+
function detectRouteConflicts(routes) {
|
|
1700
|
+
const map = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const route of routes) {
|
|
1702
|
+
const key = `${route.method} ${route.urlPath}`;
|
|
1703
|
+
const existing = map.get(key);
|
|
1704
|
+
if (existing) {
|
|
1705
|
+
existing.files.push(route.filePath);
|
|
1700
1706
|
} else {
|
|
1701
|
-
|
|
1707
|
+
map.set(key, {
|
|
1708
|
+
method: route.method,
|
|
1709
|
+
urlPath: route.urlPath,
|
|
1710
|
+
files: [route.filePath]
|
|
1711
|
+
});
|
|
1702
1712
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1713
|
+
}
|
|
1714
|
+
const conflicts = [];
|
|
1715
|
+
for (const conflict of map.values()) {
|
|
1716
|
+
if (conflict.files.length > 1) {
|
|
1717
|
+
conflicts.push(conflict);
|
|
1707
1718
|
}
|
|
1708
1719
|
}
|
|
1709
|
-
|
|
1710
|
-
return out;
|
|
1720
|
+
return conflicts;
|
|
1711
1721
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1722
|
+
|
|
1723
|
+
// src/server/createServer.ts
|
|
1724
|
+
import {
|
|
1725
|
+
createServer as createHttpServer
|
|
1726
|
+
} from "http";
|
|
1727
|
+
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
1728
|
+
import { readFileSync } from "fs";
|
|
1729
|
+
import { Readable as Readable2 } from "stream";
|
|
1730
|
+
import path10 from "path";
|
|
1731
|
+
|
|
1732
|
+
// src/router/matchRoute.ts
|
|
1733
|
+
function matchRoute(routes, method, path14) {
|
|
1734
|
+
for (const route of routes) {
|
|
1735
|
+
if (route.method !== method) {
|
|
1736
|
+
continue;
|
|
1725
1737
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
"Cache-Control": "no-cache",
|
|
1732
|
-
Connection: "keep-alive"
|
|
1738
|
+
if (!route.isDynamic) {
|
|
1739
|
+
if (route.urlPath === path14) {
|
|
1740
|
+
return { route, params: {} };
|
|
1741
|
+
}
|
|
1742
|
+
continue;
|
|
1733
1743
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
sendError(error) {
|
|
1747
|
-
if (closed || !controller) return;
|
|
1748
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1749
|
-
const text = encodeSseEvent({ event: "error", data: message });
|
|
1750
|
-
try {
|
|
1751
|
-
controller.enqueue(encoder.encode(text));
|
|
1752
|
-
} finally {
|
|
1753
|
-
writer.close();
|
|
1744
|
+
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
1745
|
+
if (params !== null) {
|
|
1746
|
+
return { route, params };
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
function matchWsRoute(wsRoutes, path14) {
|
|
1752
|
+
for (const route of wsRoutes) {
|
|
1753
|
+
if (!route.isDynamic) {
|
|
1754
|
+
if (route.urlPath === path14) {
|
|
1755
|
+
return { route, params: {} };
|
|
1754
1756
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
1760
|
+
if (params !== null) {
|
|
1761
|
+
return { route, params };
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
function matchDynamicPath(pattern, path14, paramNames, isCatchAll) {
|
|
1767
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
1768
|
+
const pathSegments = path14.split("/").filter(Boolean);
|
|
1769
|
+
if (isCatchAll) {
|
|
1770
|
+
const nonCatchAllCount = patternSegments.length - 1;
|
|
1771
|
+
if (pathSegments.length <= nonCatchAllCount) {
|
|
1772
|
+
return null;
|
|
1773
|
+
}
|
|
1774
|
+
const params2 = {};
|
|
1775
|
+
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
1776
|
+
const patternSeg = patternSegments[i];
|
|
1777
|
+
const pathSeg = pathSegments[i];
|
|
1778
|
+
if (patternSeg.startsWith(":")) {
|
|
1779
|
+
const paramName = patternSeg.slice(1);
|
|
1780
|
+
params2[paramName] = pathSeg;
|
|
1781
|
+
} else if (patternSeg !== pathSeg) {
|
|
1782
|
+
return null;
|
|
1765
1783
|
}
|
|
1766
|
-
},
|
|
1767
|
-
get closed() {
|
|
1768
|
-
return closed;
|
|
1769
|
-
},
|
|
1770
|
-
get aborted() {
|
|
1771
|
-
return aborted;
|
|
1772
|
-
},
|
|
1773
|
-
get response() {
|
|
1774
|
-
return response;
|
|
1775
1784
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1785
|
+
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1786
|
+
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1787
|
+
params2[catchAllParamName] = catchAllValue;
|
|
1788
|
+
if (Object.keys(params2).length !== paramNames.length) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
return params2;
|
|
1792
|
+
}
|
|
1793
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
const params = {};
|
|
1797
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
1798
|
+
const patternSeg = patternSegments[i];
|
|
1799
|
+
const pathSeg = pathSegments[i];
|
|
1800
|
+
if (patternSeg.startsWith(":")) {
|
|
1801
|
+
const paramName = patternSeg.slice(1);
|
|
1802
|
+
params[paramName] = pathSeg;
|
|
1803
|
+
} else if (patternSeg !== pathSeg) {
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (Object.keys(params).length !== paramNames.length) {
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
return params;
|
|
1778
1811
|
}
|
|
1779
1812
|
|
|
1780
|
-
// src/
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1813
|
+
// src/loader/loadRouteModule.ts
|
|
1814
|
+
import fs8 from "fs";
|
|
1815
|
+
|
|
1816
|
+
// src/loader/resolveExports.ts
|
|
1817
|
+
function resolveExport(module, exportName) {
|
|
1818
|
+
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1819
|
+
return module[exportName];
|
|
1820
|
+
}
|
|
1821
|
+
const defaultExport = module.default;
|
|
1822
|
+
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1823
|
+
const value = defaultExport[exportName];
|
|
1824
|
+
if (value !== void 0) {
|
|
1825
|
+
return value;
|
|
1789
1826
|
}
|
|
1790
1827
|
}
|
|
1791
|
-
return
|
|
1828
|
+
return void 0;
|
|
1792
1829
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
if (
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
1802
|
-
return cookie;
|
|
1830
|
+
|
|
1831
|
+
// src/loader/validateRouteModule.ts
|
|
1832
|
+
function validateRouteModule(value, method, filePath) {
|
|
1833
|
+
if (typeof value !== "function") {
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1803
1838
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
headers: { Location: url2 }
|
|
1837
|
-
});
|
|
1838
|
-
},
|
|
1839
|
-
json(data, status) {
|
|
1840
|
-
const headers = { "Content-Type": "application/json" };
|
|
1841
|
-
return new Response(JSON.stringify(data), {
|
|
1842
|
-
status: status ?? 200,
|
|
1843
|
-
headers
|
|
1844
|
-
});
|
|
1845
|
-
},
|
|
1846
|
-
html(html, status) {
|
|
1847
|
-
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
1848
|
-
return new Response(html, {
|
|
1849
|
-
status: status ?? 200,
|
|
1850
|
-
headers
|
|
1851
|
-
});
|
|
1852
|
-
},
|
|
1853
|
-
getCookie(name) {
|
|
1854
|
-
return parsedCookies.get(name);
|
|
1855
|
-
},
|
|
1856
|
-
setCookie(name, value, options) {
|
|
1857
|
-
meta.setCookies.push(formatSetCookie(name, value, options));
|
|
1858
|
-
},
|
|
1859
|
-
deleteCookie(name) {
|
|
1860
|
-
meta.setCookies.push(formatSetCookie(name, "", { maxAge: 0 }));
|
|
1861
|
-
},
|
|
1862
|
-
/**
|
|
1863
|
-
* 创建 SSE writer,用于流式推送事件
|
|
1864
|
-
*
|
|
1865
|
-
* handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response
|
|
1866
|
-
* 作为 HTTP 响应(Content-Type: text/event-stream)。
|
|
1867
|
-
*
|
|
1868
|
-
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
1869
|
-
*/
|
|
1870
|
-
sse() {
|
|
1871
|
-
const writer = createSseWriter();
|
|
1872
|
-
const ctxWithSse = ctx;
|
|
1873
|
-
ctxWithSse.__sseResponse = writer.response;
|
|
1874
|
-
ctxWithSse.__sseWriter = writer;
|
|
1875
|
-
return writer;
|
|
1876
|
-
},
|
|
1877
|
-
/**
|
|
1878
|
-
* 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
|
|
1879
|
-
*
|
|
1880
|
-
* 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
|
|
1881
|
-
* handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
|
|
1882
|
-
*/
|
|
1883
|
-
ok(data) {
|
|
1884
|
-
const responseConfig = config.response;
|
|
1885
|
-
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
1886
|
-
const body = okFn(data);
|
|
1887
|
-
return ctx.json(body);
|
|
1888
|
-
},
|
|
1889
|
-
/**
|
|
1890
|
-
* 返回错误响应(对象形式参数,status 和 code 均可省略)
|
|
1891
|
-
*
|
|
1892
|
-
* - status 省略时 HTTP 状态码默认 500
|
|
1893
|
-
* - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
|
|
1894
|
-
* - status 和 code 独立无关联
|
|
1895
|
-
*
|
|
1896
|
-
* body 用 config.response.fail(或默认实现)包装。
|
|
1897
|
-
*/
|
|
1898
|
-
fail(options) {
|
|
1899
|
-
const responseConfig = config.response;
|
|
1900
|
-
const failFn = responseConfig?.fail ?? ((e) => {
|
|
1901
|
-
const error = { message: e.message };
|
|
1902
|
-
if (e.code !== void 0) error.code = e.code;
|
|
1903
|
-
return { error };
|
|
1904
|
-
});
|
|
1905
|
-
const body = failFn({
|
|
1906
|
-
status: options.status,
|
|
1907
|
-
code: options.code,
|
|
1908
|
-
message: options.message
|
|
1909
|
-
});
|
|
1910
|
-
return ctx.json(body, options.status ?? 500);
|
|
1839
|
+
|
|
1840
|
+
// src/cli/compileOnDemand.ts
|
|
1841
|
+
import path8 from "path";
|
|
1842
|
+
import fs7 from "fs";
|
|
1843
|
+
|
|
1844
|
+
// src/cli/compileDevRoutes.ts
|
|
1845
|
+
import path6 from "path";
|
|
1846
|
+
import fs5 from "fs";
|
|
1847
|
+
import fg from "fast-glob";
|
|
1848
|
+
|
|
1849
|
+
// src/cli/aliasPlugin.ts
|
|
1850
|
+
import path5 from "path";
|
|
1851
|
+
import fs4 from "fs";
|
|
1852
|
+
|
|
1853
|
+
// src/utils/resolveAlias.ts
|
|
1854
|
+
function resolveAlias(specifier, config) {
|
|
1855
|
+
const candidates = [];
|
|
1856
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
1857
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
1858
|
+
if (wildcardIndex === -1) {
|
|
1859
|
+
if (specifier === pattern) {
|
|
1860
|
+
candidates.push(...targets);
|
|
1861
|
+
}
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
1865
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
1866
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
|
|
1867
|
+
const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
1868
|
+
for (const target of targets) {
|
|
1869
|
+
candidates.push(target.replace("*", captured));
|
|
1870
|
+
}
|
|
1911
1871
|
}
|
|
1912
|
-
};
|
|
1913
|
-
const extend = config?.extendContext;
|
|
1914
|
-
if (typeof extend === "function") {
|
|
1915
|
-
extend(ctx);
|
|
1916
1872
|
}
|
|
1917
|
-
return
|
|
1873
|
+
return candidates;
|
|
1918
1874
|
}
|
|
1919
1875
|
|
|
1920
|
-
// src/utils/
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1876
|
+
// src/utils/readTsconfig.ts
|
|
1877
|
+
import ts6 from "typescript";
|
|
1878
|
+
import path4 from "path";
|
|
1879
|
+
import fs3 from "fs";
|
|
1880
|
+
function readTsconfig(rootDir) {
|
|
1881
|
+
const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
|
|
1882
|
+
if (!fs3.existsSync(tsconfigPath)) return null;
|
|
1883
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
1884
|
+
if (configFile.error || !configFile.config) return null;
|
|
1885
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
1886
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
1887
|
+
const rawPaths = parsed.options.paths;
|
|
1888
|
+
if (!rawPaths) return null;
|
|
1889
|
+
const paths = {};
|
|
1890
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
1891
|
+
paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
|
|
1927
1892
|
}
|
|
1928
|
-
|
|
1929
|
-
return proto === null || proto === Object.prototype;
|
|
1893
|
+
return { baseUrl, paths };
|
|
1930
1894
|
}
|
|
1931
1895
|
|
|
1932
|
-
// src/
|
|
1933
|
-
|
|
1934
|
-
if (
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
status: meta.status ?? value.status,
|
|
1952
|
-
headers: headers2
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
return value;
|
|
1896
|
+
// src/cli/aliasPlugin.ts
|
|
1897
|
+
function toProdExtension(filePath) {
|
|
1898
|
+
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
1899
|
+
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
1900
|
+
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
1901
|
+
return filePath;
|
|
1902
|
+
}
|
|
1903
|
+
function toProdImportPath(sourceFile, importer) {
|
|
1904
|
+
const importerDir = path5.dirname(importer);
|
|
1905
|
+
let rel = path5.relative(importerDir, sourceFile);
|
|
1906
|
+
rel = rel.split(path5.sep).join("/");
|
|
1907
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1908
|
+
return toProdExtension(rel);
|
|
1909
|
+
}
|
|
1910
|
+
function toRealPath(p) {
|
|
1911
|
+
try {
|
|
1912
|
+
return fs4.realpathSync(p);
|
|
1913
|
+
} catch {
|
|
1914
|
+
return p;
|
|
1956
1915
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1916
|
+
}
|
|
1917
|
+
function isInsideDir(filePath, dir) {
|
|
1918
|
+
const rel = path5.relative(dir, filePath);
|
|
1919
|
+
return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
1920
|
+
}
|
|
1921
|
+
var APP_DIR = "src";
|
|
1922
|
+
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
1923
|
+
const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
|
|
1924
|
+
const sourceReal = toRealPath(sourceFile);
|
|
1925
|
+
let rel = path5.relative(appDirAbs, sourceReal);
|
|
1926
|
+
rel = rel.split(path5.sep).join("/");
|
|
1927
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1928
|
+
return toProdExtension(rel);
|
|
1929
|
+
}
|
|
1930
|
+
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
1931
|
+
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1932
|
+
var INDEX_EXTS = [
|
|
1933
|
+
"/index.ts",
|
|
1934
|
+
"/index.tsx",
|
|
1935
|
+
"/index.js",
|
|
1936
|
+
"/index.jsx",
|
|
1937
|
+
"/index.mjs",
|
|
1938
|
+
"/index.cjs"
|
|
1939
|
+
];
|
|
1940
|
+
function resolveRelativeSpecifier(importer, specifier) {
|
|
1941
|
+
const importerDir = path5.dirname(importer);
|
|
1942
|
+
const base = path5.resolve(importerDir, specifier);
|
|
1943
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1944
|
+
return fs4.existsSync(base) ? base : null;
|
|
1962
1945
|
}
|
|
1963
|
-
if (
|
|
1964
|
-
|
|
1965
|
-
applyMeta(headers2);
|
|
1966
|
-
return new Response(value, {
|
|
1967
|
-
status: meta?.status ?? 200,
|
|
1968
|
-
headers: headers2
|
|
1969
|
-
});
|
|
1946
|
+
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
1947
|
+
return fs4.existsSync(base) ? base : null;
|
|
1970
1948
|
}
|
|
1971
|
-
|
|
1972
|
-
const
|
|
1973
|
-
|
|
1974
|
-
return new Response(value, {
|
|
1975
|
-
status: meta?.status ?? 200,
|
|
1976
|
-
headers: headers2
|
|
1977
|
-
});
|
|
1949
|
+
for (const ext of SOURCE_EXTS) {
|
|
1950
|
+
const file = base + ext;
|
|
1951
|
+
if (fs4.existsSync(file)) return file;
|
|
1978
1952
|
}
|
|
1979
|
-
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
return new Response(value, {
|
|
1983
|
-
status: meta?.status ?? 200,
|
|
1984
|
-
headers: headers2
|
|
1985
|
-
});
|
|
1953
|
+
for (const indexExt of INDEX_EXTS) {
|
|
1954
|
+
const file = base + indexExt;
|
|
1955
|
+
if (fs4.existsSync(file)) return file;
|
|
1986
1956
|
}
|
|
1987
|
-
|
|
1988
|
-
const body2 = JSON.stringify(value);
|
|
1989
|
-
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
1990
|
-
applyMeta(headers2);
|
|
1991
|
-
return new Response(body2, {
|
|
1992
|
-
status: meta?.status ?? 200,
|
|
1993
|
-
headers: headers2
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
if (typeof value === "string") {
|
|
1997
|
-
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
1998
|
-
applyMeta(headers2);
|
|
1999
|
-
return new Response(value, {
|
|
2000
|
-
status: meta?.status ?? 200,
|
|
2001
|
-
headers: headers2
|
|
2002
|
-
});
|
|
2003
|
-
}
|
|
2004
|
-
if (typeof value === "number" || typeof value === "boolean") {
|
|
2005
|
-
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
2006
|
-
applyMeta(headers2);
|
|
2007
|
-
return new Response(String(value), {
|
|
2008
|
-
status: meta?.status ?? 200,
|
|
2009
|
-
headers: headers2
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
const body = JSON.stringify(value);
|
|
2013
|
-
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2014
|
-
applyMeta(headers);
|
|
2015
|
-
return new Response(body, {
|
|
2016
|
-
status: meta?.status ?? 200,
|
|
2017
|
-
headers
|
|
2018
|
-
});
|
|
1957
|
+
return null;
|
|
2019
1958
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
1959
|
+
function createAliasPlugin(config, options) {
|
|
1960
|
+
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
1961
|
+
const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
|
|
1962
|
+
return {
|
|
1963
|
+
name: "faapi-alias",
|
|
1964
|
+
setup(build) {
|
|
1965
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
1966
|
+
let source;
|
|
1967
|
+
try {
|
|
1968
|
+
source = fs4.readFileSync(args.path, "utf8");
|
|
1969
|
+
} catch {
|
|
1970
|
+
return void 0;
|
|
1971
|
+
}
|
|
1972
|
+
const importer = args.path;
|
|
1973
|
+
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
1974
|
+
let modified = false;
|
|
1975
|
+
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
1976
|
+
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
1977
|
+
return full;
|
|
1978
|
+
}
|
|
1979
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1980
|
+
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
1981
|
+
if (resolved) {
|
|
1982
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1983
|
+
return full;
|
|
1984
|
+
}
|
|
1985
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
1986
|
+
modified = true;
|
|
1987
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1988
|
+
resolved,
|
|
1989
|
+
options.rootDir
|
|
1990
|
+
)}${quote}`;
|
|
1991
|
+
}
|
|
1992
|
+
modified = true;
|
|
1993
|
+
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
1994
|
+
}
|
|
1995
|
+
return full;
|
|
1996
|
+
}
|
|
1997
|
+
const candidates = resolveAlias(specifier, config);
|
|
1998
|
+
for (const candidate of candidates) {
|
|
1999
|
+
for (const ext of SOURCE_EXTS) {
|
|
2000
|
+
const file = candidate + ext;
|
|
2001
|
+
if (fs4.existsSync(file)) {
|
|
2002
|
+
modified = true;
|
|
2003
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2004
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2005
|
+
file,
|
|
2006
|
+
options.rootDir
|
|
2007
|
+
)}${quote}`;
|
|
2008
|
+
}
|
|
2009
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
for (const indexExt of INDEX_EXTS) {
|
|
2013
|
+
const file = candidate + indexExt;
|
|
2014
|
+
if (fs4.existsSync(file)) {
|
|
2015
|
+
modified = true;
|
|
2016
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2017
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2018
|
+
file,
|
|
2019
|
+
options.rootDir
|
|
2020
|
+
)}${quote}`;
|
|
2021
|
+
}
|
|
2022
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return full;
|
|
2027
|
+
});
|
|
2028
|
+
if (!modified) return void 0;
|
|
2029
|
+
return { contents: newSource, loader: "default" };
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
function buildAliasPlugins(rootDir) {
|
|
2035
|
+
const tsconfig = readTsconfig(rootDir);
|
|
2036
|
+
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
2031
2037
|
}
|
|
2032
2038
|
|
|
2033
|
-
// src/
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
return ctx.cookies;
|
|
2046
|
-
case "ip":
|
|
2047
|
-
return ctx.ip;
|
|
2048
|
-
case "ua":
|
|
2049
|
-
return ctx.ua;
|
|
2050
|
-
case "body":
|
|
2051
|
-
return body;
|
|
2052
|
-
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
2053
|
-
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
2054
|
-
case "form":
|
|
2055
|
-
return body;
|
|
2056
|
-
case "files":
|
|
2057
|
-
if (body && typeof body === "object" && "files" in body) {
|
|
2058
|
-
return body.files;
|
|
2059
|
-
}
|
|
2060
|
-
return [];
|
|
2061
|
-
case "fields":
|
|
2062
|
-
if (body && typeof body === "object" && "fields" in body) {
|
|
2063
|
-
return body.fields;
|
|
2064
|
-
}
|
|
2065
|
-
return {};
|
|
2066
|
-
default:
|
|
2067
|
-
return void 0;
|
|
2039
|
+
// src/cli/compileDevRoutes.ts
|
|
2040
|
+
var APP_DIR2 = "src";
|
|
2041
|
+
async function compileDevRoutes(options) {
|
|
2042
|
+
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
2043
|
+
const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
|
|
2044
|
+
cwd: rootDir,
|
|
2045
|
+
onlyFiles: true,
|
|
2046
|
+
absolute: true,
|
|
2047
|
+
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
2048
|
+
});
|
|
2049
|
+
if (entryPoints.length === 0) {
|
|
2050
|
+
return { compiledFiles: [] };
|
|
2068
2051
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const
|
|
2072
|
-
|
|
2073
|
-
|
|
2052
|
+
const absDist = path6.resolve(rootDir, dist);
|
|
2053
|
+
await fs5.promises.mkdir(absDist, { recursive: true });
|
|
2054
|
+
const plugins = buildAliasPlugins(rootDir);
|
|
2055
|
+
const esbuild = await import("esbuild");
|
|
2056
|
+
const outbase = path6.resolve(rootDir, APP_DIR2);
|
|
2057
|
+
const result = await esbuild.build({
|
|
2058
|
+
entryPoints,
|
|
2059
|
+
outdir: absDist,
|
|
2060
|
+
outbase,
|
|
2061
|
+
bundle: false,
|
|
2062
|
+
platform: "node",
|
|
2063
|
+
format: "esm",
|
|
2064
|
+
sourcemap: true,
|
|
2065
|
+
packages: "external",
|
|
2066
|
+
plugins,
|
|
2067
|
+
logLevel,
|
|
2068
|
+
write: false
|
|
2069
|
+
});
|
|
2070
|
+
if (result.outputFiles) {
|
|
2071
|
+
await Promise.all(
|
|
2072
|
+
result.outputFiles.map(async (file) => {
|
|
2073
|
+
await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
|
|
2074
|
+
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2075
|
+
await fs5.promises.writeFile(tmp, file.contents);
|
|
2076
|
+
await fs5.promises.rename(tmp, file.path);
|
|
2077
|
+
})
|
|
2078
|
+
);
|
|
2074
2079
|
}
|
|
2075
|
-
|
|
2076
|
-
injections.map(async (injection) => {
|
|
2077
|
-
if (injection.type !== "unknown") {
|
|
2078
|
-
return getBuiltinInjectionValue(injection.type, ctx, body);
|
|
2079
|
-
}
|
|
2080
|
-
if (injectors && injection.name in injectors) {
|
|
2081
|
-
return await injectors[injection.name](ctx);
|
|
2082
|
-
}
|
|
2083
|
-
return void 0;
|
|
2084
|
-
})
|
|
2085
|
-
);
|
|
2086
|
-
return await handler(...args);
|
|
2080
|
+
return { compiledFiles: entryPoints };
|
|
2087
2081
|
}
|
|
2088
2082
|
|
|
2089
|
-
// src/
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2083
|
+
// src/cli/compileOnDemand.ts
|
|
2084
|
+
init_generateSchemaFiles();
|
|
2085
|
+
init_generateSchemaFiles();
|
|
2086
|
+
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
2087
|
+
try {
|
|
2088
|
+
const srcStat = fs7.statSync(sourceAbsPath);
|
|
2089
|
+
const prodStat = fs7.statSync(productAbsPath);
|
|
2090
|
+
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2091
|
+
} catch {
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2095
2094
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2095
|
+
var compiledFiles = /* @__PURE__ */ new Set();
|
|
2096
|
+
function clearCompiledFiles() {
|
|
2097
|
+
compiledFiles.clear();
|
|
2098
|
+
}
|
|
2099
|
+
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
2100
|
+
if (compiledFiles.has(sourceAbsPath)) {
|
|
2101
|
+
return false;
|
|
2102
2102
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2103
|
+
if (!fs7.existsSync(sourceAbsPath)) {
|
|
2104
|
+
return false;
|
|
2105
2105
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2106
|
+
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
2107
|
+
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
2108
|
+
compiledFiles.add(sourceAbsPath);
|
|
2109
|
+
return false;
|
|
2110
|
+
}
|
|
2111
|
+
await compileDevRoutes({
|
|
2112
|
+
rootDir,
|
|
2113
|
+
dist,
|
|
2114
|
+
files: [sourceAbsPath],
|
|
2115
|
+
logLevel: "silent"
|
|
2109
2116
|
});
|
|
2117
|
+
compiledFiles.add(sourceAbsPath);
|
|
2118
|
+
return true;
|
|
2110
2119
|
}
|
|
2111
|
-
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
}
|
|
2118
|
-
index = i;
|
|
2119
|
-
if (i >= middlewares.length) {
|
|
2120
|
-
return await finalHandler();
|
|
2121
|
-
}
|
|
2122
|
-
const mw = middlewares[i];
|
|
2123
|
-
let innerResponse;
|
|
2124
|
-
const next = async () => {
|
|
2125
|
-
innerResponse = await dispatch(i + 1);
|
|
2126
|
-
return innerResponse;
|
|
2127
|
-
};
|
|
2128
|
-
const result = await mw(ctx, next);
|
|
2129
|
-
if (result instanceof Response) {
|
|
2130
|
-
return mergeMeta(result, meta);
|
|
2131
|
-
}
|
|
2132
|
-
if (innerResponse !== void 0) {
|
|
2133
|
-
return innerResponse;
|
|
2134
|
-
}
|
|
2135
|
-
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
2136
|
-
}
|
|
2137
|
-
return await dispatch(0);
|
|
2120
|
+
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2121
|
+
const rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2122
|
+
if (!rel.startsWith("src/")) return null;
|
|
2123
|
+
const relWithoutSrc = rel.slice(4);
|
|
2124
|
+
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2125
|
+
return path8.resolve(rootDir, dist, jsRel);
|
|
2138
2126
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
const sseWriter = ctx.__sseWriter;
|
|
2143
|
-
if (!sseWriter) return null;
|
|
2144
|
-
if (!sseWriter.closed && !sseWriter.aborted) {
|
|
2145
|
-
sseWriter.close();
|
|
2146
|
-
}
|
|
2147
|
-
return mergeMeta(sseWriter.response, meta);
|
|
2148
|
-
};
|
|
2149
|
-
const autoCloseSseOnError = () => {
|
|
2150
|
-
const sseWriter = ctx.__sseWriter;
|
|
2151
|
-
if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {
|
|
2152
|
-
sseWriter.close();
|
|
2153
|
-
}
|
|
2154
|
-
};
|
|
2155
|
-
if (!middlewares || middlewares.length === 0) {
|
|
2156
|
-
try {
|
|
2157
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2158
|
-
const sseResponse = pickSseAndAutoClose();
|
|
2159
|
-
if (sseResponse) return sseResponse;
|
|
2160
|
-
return toResponse(wrapResult(result, ctx), meta);
|
|
2161
|
-
} catch (err) {
|
|
2162
|
-
autoCloseSseOnError();
|
|
2163
|
-
throw err;
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
const finalHandler = async () => {
|
|
2167
|
-
try {
|
|
2168
|
-
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2169
|
-
const sseResponse = pickSseAndAutoClose();
|
|
2170
|
-
if (sseResponse) return sseResponse;
|
|
2171
|
-
return toResponse(wrapResult(result, ctx), meta);
|
|
2172
|
-
} catch (err) {
|
|
2173
|
-
autoCloseSseOnError();
|
|
2174
|
-
throw err;
|
|
2175
|
-
}
|
|
2176
|
-
};
|
|
2177
|
-
return await compose(middlewares, ctx, finalHandler);
|
|
2127
|
+
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2128
|
+
function clearGeneratedSchemas() {
|
|
2129
|
+
generatedSchemas.clear();
|
|
2178
2130
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
import os from "os";
|
|
2183
|
-
import fs11 from "fs/promises";
|
|
2184
|
-
|
|
2185
|
-
// src/router/scanRoutes.ts
|
|
2186
|
-
import fg from "fast-glob";
|
|
2187
|
-
import path4 from "path";
|
|
2188
|
-
import fs3 from "fs";
|
|
2189
|
-
|
|
2190
|
-
// src/router/constants.ts
|
|
2191
|
-
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
2192
|
-
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
2193
|
-
|
|
2194
|
-
// src/utils/normalizePath.ts
|
|
2195
|
-
function normalizePath(path15) {
|
|
2196
|
-
if (!path15) return "";
|
|
2197
|
-
let result = path15.replace(/\\/g, "/");
|
|
2198
|
-
result = result.replace(/\/+/g, "/");
|
|
2199
|
-
result = result.replace(/\/+$/, "");
|
|
2200
|
-
if (result && !result.startsWith("/")) {
|
|
2201
|
-
result = "/" + result;
|
|
2131
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2132
|
+
if (generatedSchemas.has(schemaPath)) {
|
|
2133
|
+
return false;
|
|
2202
2134
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
function dynamicSegmentToParam(segment) {
|
|
2208
|
-
const match = segment.match(/^\[(.+)\]$/);
|
|
2209
|
-
if (match) {
|
|
2210
|
-
return ":" + match[1];
|
|
2135
|
+
const prodAbsPath = path8.resolve(rootDir, routeFilePath);
|
|
2136
|
+
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2137
|
+
if (!fs7.existsSync(sourceAbsPath)) {
|
|
2138
|
+
return false;
|
|
2211
2139
|
}
|
|
2212
|
-
|
|
2140
|
+
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
2141
|
+
generatedSchemas.add(schemaPath);
|
|
2142
|
+
return false;
|
|
2143
|
+
}
|
|
2144
|
+
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
2145
|
+
if (fileRoutes.length === 0) {
|
|
2146
|
+
return false;
|
|
2147
|
+
}
|
|
2148
|
+
const sourceRelPath = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2149
|
+
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2150
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2151
|
+
generatedSchemas.add(schemaPath);
|
|
2152
|
+
return true;
|
|
2213
2153
|
}
|
|
2214
|
-
function
|
|
2215
|
-
const
|
|
2216
|
-
const
|
|
2217
|
-
|
|
2218
|
-
if (
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2154
|
+
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
2155
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
2156
|
+
for (const route of routes) {
|
|
2157
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
2158
|
+
if (deleted.has(schemaPath)) continue;
|
|
2159
|
+
deleted.add(schemaPath);
|
|
2160
|
+
try {
|
|
2161
|
+
await fs7.promises.unlink(schemaPath);
|
|
2162
|
+
} catch {
|
|
2222
2163
|
}
|
|
2223
2164
|
}
|
|
2224
|
-
return params;
|
|
2225
|
-
}
|
|
2226
|
-
function isCatchAllSegment(segment) {
|
|
2227
|
-
return /^\[\.\.\..+\]$/.test(segment);
|
|
2228
|
-
}
|
|
2229
|
-
function isRouteGroup(segment) {
|
|
2230
|
-
return /^\(.+\)$/.test(segment);
|
|
2231
2165
|
}
|
|
2232
|
-
function
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
return "";
|
|
2166
|
+
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2167
|
+
const rel = path8.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2168
|
+
let relWithoutDist = rel;
|
|
2169
|
+
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2170
|
+
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2238
2171
|
}
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
var middlewareCache = /* @__PURE__ */ new Map();
|
|
2245
|
-
function invalidateMiddlewareCache() {
|
|
2246
|
-
middlewareCache.clear();
|
|
2172
|
+
const srcRel = `src/${relWithoutDist}`;
|
|
2173
|
+
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2174
|
+
const tsAbs = path8.resolve(rootDir, tsRel);
|
|
2175
|
+
if (fs7.existsSync(tsAbs)) return tsAbs;
|
|
2176
|
+
return path8.resolve(rootDir, srcRel);
|
|
2247
2177
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2178
|
+
var devOnDemandEnabled = false;
|
|
2179
|
+
function isDevOnDemandEnabled() {
|
|
2180
|
+
return devOnDemandEnabled;
|
|
2250
2181
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2182
|
+
var devDistDir;
|
|
2183
|
+
function getDevDist() {
|
|
2184
|
+
return devDistDir;
|
|
2253
2185
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
if (typeof injectors !== "object" || injectors === null) {
|
|
2271
|
-
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
2272
|
-
return { middlewares: validMiddlewares, injectors: {} };
|
|
2273
|
-
}
|
|
2274
|
-
const validInjectors = {};
|
|
2275
|
-
for (const [name, injector] of Object.entries(injectors)) {
|
|
2276
|
-
if (typeof injector !== "function") {
|
|
2277
|
-
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
2278
|
-
continue;
|
|
2186
|
+
|
|
2187
|
+
// src/loader/loadRouteModule.ts
|
|
2188
|
+
async function loadRouteModule(filePath, method, rootDir) {
|
|
2189
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
2190
|
+
const dist = getDevDist();
|
|
2191
|
+
if (dist) {
|
|
2192
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2193
|
+
if (sourcePath && fs8.existsSync(sourcePath)) {
|
|
2194
|
+
try {
|
|
2195
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2196
|
+
} catch (compileErr) {
|
|
2197
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
2198
|
+
throw new Error(`Failed to compile route module "${sourcePath}": ${reason}`, {
|
|
2199
|
+
cause: compileErr
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2279
2202
|
}
|
|
2280
|
-
validInjectors[name] = injector;
|
|
2281
|
-
}
|
|
2282
|
-
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
2283
|
-
} catch {
|
|
2284
|
-
return { middlewares: [], injectors: {} };
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
async function loadMergedMiddlewares(middlewarePaths) {
|
|
2288
|
-
if (middlewarePaths.length === 0) return void 0;
|
|
2289
|
-
const mergedMiddlewares = [];
|
|
2290
|
-
const mergedInjectors = {};
|
|
2291
|
-
for (const absMwPath of middlewarePaths) {
|
|
2292
|
-
let bundle = getCachedMiddlewares(absMwPath);
|
|
2293
|
-
if (bundle === void 0) {
|
|
2294
|
-
bundle = await loadMiddlewaresFile(absMwPath);
|
|
2295
|
-
setCachedMiddlewares(absMwPath, bundle);
|
|
2296
|
-
}
|
|
2297
|
-
mergedMiddlewares.push(...bundle.middlewares);
|
|
2298
|
-
for (const [name, injector] of Object.entries(bundle.injectors)) {
|
|
2299
|
-
mergedInjectors[name] = injector;
|
|
2300
2203
|
}
|
|
2301
2204
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2205
|
+
let module;
|
|
2206
|
+
try {
|
|
2207
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
2208
|
+
} catch (err) {
|
|
2209
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2210
|
+
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
2304
2211
|
}
|
|
2305
|
-
|
|
2212
|
+
const handler = resolveExport(module, method);
|
|
2213
|
+
validateRouteModule(handler, method, filePath);
|
|
2214
|
+
return { handler, method };
|
|
2306
2215
|
}
|
|
2307
2216
|
|
|
2308
|
-
// src/
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const names = /* @__PURE__ */ new Set();
|
|
2315
|
-
let match;
|
|
2316
|
-
HTTP_OR_WS_EXPORT_RE.lastIndex = 0;
|
|
2317
|
-
while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {
|
|
2318
|
-
names.add(match[1]);
|
|
2217
|
+
// src/runtime/sse.ts
|
|
2218
|
+
function encodeSseEvent(event) {
|
|
2219
|
+
let out = "";
|
|
2220
|
+
if (event.comment !== void 0) {
|
|
2221
|
+
out += `: ${event.comment}
|
|
2222
|
+
`;
|
|
2319
2223
|
}
|
|
2320
|
-
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
const routeDir = path4.dirname(routeFilePath);
|
|
2324
|
-
const resolvedRoot = path4.resolve(rootDir);
|
|
2325
|
-
const paths = [];
|
|
2326
|
-
let currentDir = path4.resolve(rootDir, routeDir);
|
|
2327
|
-
while (true) {
|
|
2328
|
-
if (dist) {
|
|
2329
|
-
const mwTsPath = path4.join(currentDir, "middlewares.ts");
|
|
2330
|
-
const mwJsPath = path4.join(currentDir, "middlewares.js");
|
|
2331
|
-
const absTsPath = path4.resolve(rootDir, mwTsPath);
|
|
2332
|
-
const absJsPath = path4.resolve(rootDir, mwJsPath);
|
|
2333
|
-
const absMwPath = fs3.existsSync(absTsPath) ? absTsPath : fs3.existsSync(absJsPath) ? absJsPath : null;
|
|
2334
|
-
if (absMwPath) {
|
|
2335
|
-
const relMwPath = path4.relative(rootDir, absMwPath);
|
|
2336
|
-
const prodAbsPath = path4.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
2337
|
-
paths.push(prodAbsPath);
|
|
2338
|
-
}
|
|
2339
|
-
} else {
|
|
2340
|
-
for (const ext of [".ts", ".js"]) {
|
|
2341
|
-
const mwPath = path4.join(currentDir, `middlewares${ext}`);
|
|
2342
|
-
const absMwPath = path4.resolve(rootDir, mwPath);
|
|
2343
|
-
if (fs3.existsSync(absMwPath)) {
|
|
2344
|
-
paths.push(absMwPath);
|
|
2345
|
-
break;
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
if (currentDir === resolvedRoot) break;
|
|
2350
|
-
const parentDir = path4.dirname(currentDir);
|
|
2351
|
-
if (parentDir === currentDir) break;
|
|
2352
|
-
currentDir = parentDir;
|
|
2224
|
+
if (event.event !== void 0) {
|
|
2225
|
+
out += `event: ${event.event}
|
|
2226
|
+
`;
|
|
2353
2227
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
function toProdFilePath(filePath, dist) {
|
|
2358
|
-
let rel = filePath.replace(/\\/g, "/");
|
|
2359
|
-
if (rel.startsWith("src/")) {
|
|
2360
|
-
rel = rel.slice(4);
|
|
2228
|
+
if (event.id !== void 0) {
|
|
2229
|
+
out += `id: ${event.id}
|
|
2230
|
+
`;
|
|
2361
2231
|
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
const
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
const paramNames = extractParamNames(urlPath);
|
|
2380
|
-
const isDynamic = paramNames.length > 0;
|
|
2381
|
-
const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
|
|
2382
|
-
let middlewarePaths;
|
|
2383
|
-
let middlewareBundle;
|
|
2384
|
-
if (dist) {
|
|
2385
|
-
middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);
|
|
2386
|
-
} else {
|
|
2387
|
-
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
2388
|
-
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
2389
|
-
}
|
|
2390
|
-
const source = await fs3.promises.readFile(absPath, "utf8").catch(() => "");
|
|
2391
|
-
const exportNames = extractExportsFromSource(source);
|
|
2392
|
-
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
2393
|
-
for (const method of methods) {
|
|
2394
|
-
routes.push({
|
|
2395
|
-
method,
|
|
2396
|
-
urlPath,
|
|
2397
|
-
filePath: normalizedFile,
|
|
2398
|
-
paramNames,
|
|
2399
|
-
isDynamic,
|
|
2400
|
-
isCatchAll: isCatchAll || void 0,
|
|
2401
|
-
middlewarePaths,
|
|
2402
|
-
middlewares: middlewareBundle?.middlewares,
|
|
2403
|
-
injectors: middlewareBundle?.injectors
|
|
2404
|
-
});
|
|
2405
|
-
}
|
|
2406
|
-
if (exportNames.has("WS")) {
|
|
2407
|
-
wsRoutes.push({
|
|
2408
|
-
urlPath,
|
|
2409
|
-
filePath: normalizedFile,
|
|
2410
|
-
paramNames,
|
|
2411
|
-
isDynamic,
|
|
2412
|
-
isCatchAll: isCatchAll || void 0,
|
|
2413
|
-
middlewarePaths,
|
|
2414
|
-
middlewares: middlewareBundle?.middlewares,
|
|
2415
|
-
injectors: middlewareBundle?.injectors
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
continue;
|
|
2232
|
+
if (event.retry !== void 0) {
|
|
2233
|
+
out += `retry: ${event.retry}
|
|
2234
|
+
`;
|
|
2235
|
+
}
|
|
2236
|
+
if (event.data !== void 0) {
|
|
2237
|
+
let dataStr;
|
|
2238
|
+
if (typeof event.data === "string") {
|
|
2239
|
+
dataStr = event.data;
|
|
2240
|
+
} else if (event.data === null) {
|
|
2241
|
+
dataStr = "null";
|
|
2242
|
+
} else {
|
|
2243
|
+
dataStr = JSON.stringify(event.data);
|
|
2244
|
+
}
|
|
2245
|
+
const lines = dataStr.split("\n");
|
|
2246
|
+
for (const line of lines) {
|
|
2247
|
+
out += `data: ${line}
|
|
2248
|
+
`;
|
|
2419
2249
|
}
|
|
2420
2250
|
}
|
|
2421
|
-
|
|
2251
|
+
out += "\n";
|
|
2252
|
+
return out;
|
|
2422
2253
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2254
|
+
function createSseWriter() {
|
|
2255
|
+
const encoder = new TextEncoder();
|
|
2256
|
+
let controller = null;
|
|
2257
|
+
let closed = false;
|
|
2258
|
+
let aborted = false;
|
|
2259
|
+
const stream = new ReadableStream({
|
|
2260
|
+
start(c) {
|
|
2261
|
+
controller = c;
|
|
2262
|
+
},
|
|
2263
|
+
cancel() {
|
|
2264
|
+
aborted = true;
|
|
2265
|
+
closed = true;
|
|
2266
|
+
controller = null;
|
|
2432
2267
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2268
|
+
});
|
|
2269
|
+
const response = new Response(stream, {
|
|
2270
|
+
status: 200,
|
|
2271
|
+
headers: {
|
|
2272
|
+
"Content-Type": "text/event-stream",
|
|
2273
|
+
"Cache-Control": "no-cache",
|
|
2274
|
+
Connection: "keep-alive"
|
|
2437
2275
|
}
|
|
2438
|
-
return a.urlPath.localeCompare(b.urlPath);
|
|
2439
2276
|
});
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
import fs6 from "fs";
|
|
2460
|
-
|
|
2461
|
-
// src/utils/resolveAlias.ts
|
|
2462
|
-
function resolveAlias(specifier, config) {
|
|
2463
|
-
const candidates = [];
|
|
2464
|
-
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
2465
|
-
const wildcardIndex = pattern.indexOf("*");
|
|
2466
|
-
if (wildcardIndex === -1) {
|
|
2467
|
-
if (specifier === pattern) {
|
|
2468
|
-
candidates.push(...targets);
|
|
2277
|
+
const writer = {
|
|
2278
|
+
send(event) {
|
|
2279
|
+
if (closed || !controller) return;
|
|
2280
|
+
const text = encodeSseEvent(event);
|
|
2281
|
+
controller.enqueue(encoder.encode(text));
|
|
2282
|
+
},
|
|
2283
|
+
sendRaw(chunk) {
|
|
2284
|
+
if (closed || !controller) return;
|
|
2285
|
+
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
2286
|
+
controller.enqueue(bytes);
|
|
2287
|
+
},
|
|
2288
|
+
sendError(error) {
|
|
2289
|
+
if (closed || !controller) return;
|
|
2290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2291
|
+
const text = encodeSseEvent({ event: "error", data: message });
|
|
2292
|
+
try {
|
|
2293
|
+
controller.enqueue(encoder.encode(text));
|
|
2294
|
+
} finally {
|
|
2295
|
+
writer.close();
|
|
2469
2296
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2297
|
+
},
|
|
2298
|
+
close() {
|
|
2299
|
+
if (closed) return;
|
|
2300
|
+
closed = true;
|
|
2301
|
+
if (controller) {
|
|
2302
|
+
try {
|
|
2303
|
+
controller.close();
|
|
2304
|
+
} catch {
|
|
2305
|
+
}
|
|
2306
|
+
controller = null;
|
|
2478
2307
|
}
|
|
2308
|
+
},
|
|
2309
|
+
get closed() {
|
|
2310
|
+
return closed;
|
|
2311
|
+
},
|
|
2312
|
+
get aborted() {
|
|
2313
|
+
return aborted;
|
|
2314
|
+
},
|
|
2315
|
+
get response() {
|
|
2316
|
+
return response;
|
|
2479
2317
|
}
|
|
2480
|
-
}
|
|
2481
|
-
return
|
|
2318
|
+
};
|
|
2319
|
+
return writer;
|
|
2482
2320
|
}
|
|
2483
2321
|
|
|
2484
|
-
// src/
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
2495
|
-
const rawPaths = parsed.options.paths;
|
|
2496
|
-
if (!rawPaths) return null;
|
|
2497
|
-
const paths = {};
|
|
2498
|
-
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2499
|
-
paths[pattern] = targets.map((t) => path6.resolve(baseUrl, t));
|
|
2322
|
+
// src/runtime/createContext.ts
|
|
2323
|
+
function parseCookies(cookieHeader) {
|
|
2324
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
2325
|
+
if (!cookieHeader) return cookies;
|
|
2326
|
+
for (const pair of cookieHeader.split(";")) {
|
|
2327
|
+
const [name, ...rest] = pair.split("=");
|
|
2328
|
+
const trimmed = name?.trim();
|
|
2329
|
+
if (trimmed) {
|
|
2330
|
+
cookies.set(trimmed, rest.join("=").trim());
|
|
2331
|
+
}
|
|
2500
2332
|
}
|
|
2501
|
-
return
|
|
2333
|
+
return cookies;
|
|
2502
2334
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
if (
|
|
2507
|
-
if (
|
|
2508
|
-
if (
|
|
2509
|
-
|
|
2335
|
+
function formatSetCookie(name, value, options) {
|
|
2336
|
+
let cookie = `${name}=${value}`;
|
|
2337
|
+
if (options?.domain) cookie += `; Domain=${options.domain}`;
|
|
2338
|
+
if (options?.path) cookie += `; Path=${options.path}`;
|
|
2339
|
+
if (options?.maxAge !== void 0) cookie += `; Max-Age=${options.maxAge}`;
|
|
2340
|
+
if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
2341
|
+
if (options?.httpOnly) cookie += `; HttpOnly`;
|
|
2342
|
+
if (options?.secure) cookie += `; Secure`;
|
|
2343
|
+
if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
2344
|
+
return cookie;
|
|
2510
2345
|
}
|
|
2511
|
-
function
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
function toRealPath(p) {
|
|
2519
|
-
try {
|
|
2520
|
-
return fs6.realpathSync(p);
|
|
2521
|
-
} catch {
|
|
2522
|
-
return p;
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
function isInsideDir(filePath, dir) {
|
|
2526
|
-
const rel = path7.relative(dir, filePath);
|
|
2527
|
-
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
2528
|
-
}
|
|
2529
|
-
var APP_DIR = "src";
|
|
2530
|
-
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
2531
|
-
const appDirAbs = toRealPath(path7.resolve(rootDir, APP_DIR));
|
|
2532
|
-
const sourceReal = toRealPath(sourceFile);
|
|
2533
|
-
let rel = path7.relative(appDirAbs, sourceReal);
|
|
2534
|
-
rel = rel.split(path7.sep).join("/");
|
|
2535
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2536
|
-
return toProdExtension(rel);
|
|
2537
|
-
}
|
|
2538
|
-
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
2539
|
-
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2540
|
-
var INDEX_EXTS = [
|
|
2541
|
-
"/index.ts",
|
|
2542
|
-
"/index.tsx",
|
|
2543
|
-
"/index.js",
|
|
2544
|
-
"/index.jsx",
|
|
2545
|
-
"/index.mjs",
|
|
2546
|
-
"/index.cjs"
|
|
2547
|
-
];
|
|
2548
|
-
function resolveRelativeSpecifier(importer, specifier) {
|
|
2549
|
-
const importerDir = path7.dirname(importer);
|
|
2550
|
-
const base = path7.resolve(importerDir, specifier);
|
|
2551
|
-
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2552
|
-
return fs6.existsSync(base) ? base : null;
|
|
2553
|
-
}
|
|
2554
|
-
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
2555
|
-
return fs6.existsSync(base) ? base : null;
|
|
2556
|
-
}
|
|
2557
|
-
for (const ext of SOURCE_EXTS) {
|
|
2558
|
-
const file = base + ext;
|
|
2559
|
-
if (fs6.existsSync(file)) return file;
|
|
2560
|
-
}
|
|
2561
|
-
for (const indexExt of INDEX_EXTS) {
|
|
2562
|
-
const file = base + indexExt;
|
|
2563
|
-
if (fs6.existsSync(file)) return file;
|
|
2346
|
+
function createContext(request, params, config = {}, ip = "") {
|
|
2347
|
+
const url = new URL(request.url);
|
|
2348
|
+
const meta = { headers: {}, setCookies: [] };
|
|
2349
|
+
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
2350
|
+
const cookiesObj = {};
|
|
2351
|
+
for (const [key, val] of parsedCookies) {
|
|
2352
|
+
cookiesObj[key] = val;
|
|
2564
2353
|
}
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2591
|
-
return full;
|
|
2592
|
-
}
|
|
2593
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
2594
|
-
modified = true;
|
|
2595
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2596
|
-
resolved,
|
|
2597
|
-
options.rootDir
|
|
2598
|
-
)}${quote}`;
|
|
2599
|
-
}
|
|
2600
|
-
modified = true;
|
|
2601
|
-
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
2602
|
-
}
|
|
2603
|
-
return full;
|
|
2604
|
-
}
|
|
2605
|
-
const candidates = resolveAlias(specifier, config);
|
|
2606
|
-
for (const candidate of candidates) {
|
|
2607
|
-
for (const ext of SOURCE_EXTS) {
|
|
2608
|
-
const file = candidate + ext;
|
|
2609
|
-
if (fs6.existsSync(file)) {
|
|
2610
|
-
modified = true;
|
|
2611
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2612
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2613
|
-
file,
|
|
2614
|
-
options.rootDir
|
|
2615
|
-
)}${quote}`;
|
|
2616
|
-
}
|
|
2617
|
-
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
for (const indexExt of INDEX_EXTS) {
|
|
2621
|
-
const file = candidate + indexExt;
|
|
2622
|
-
if (fs6.existsSync(file)) {
|
|
2623
|
-
modified = true;
|
|
2624
|
-
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2625
|
-
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
2626
|
-
file,
|
|
2627
|
-
options.rootDir
|
|
2628
|
-
)}${quote}`;
|
|
2629
|
-
}
|
|
2630
|
-
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
return full;
|
|
2635
|
-
});
|
|
2636
|
-
if (!modified) return void 0;
|
|
2637
|
-
return { contents: newSource, loader: "default" };
|
|
2354
|
+
const ctx = {
|
|
2355
|
+
request,
|
|
2356
|
+
params,
|
|
2357
|
+
query: url.searchParams,
|
|
2358
|
+
headers: request.headers,
|
|
2359
|
+
method: request.method,
|
|
2360
|
+
path: url.pathname,
|
|
2361
|
+
ip,
|
|
2362
|
+
ua: request.headers.get("user-agent") ?? "",
|
|
2363
|
+
cookies: cookiesObj,
|
|
2364
|
+
config,
|
|
2365
|
+
meta,
|
|
2366
|
+
setStatus(status) {
|
|
2367
|
+
meta.status = status;
|
|
2368
|
+
},
|
|
2369
|
+
setHeader(key, value) {
|
|
2370
|
+
meta.headers[key] = value;
|
|
2371
|
+
},
|
|
2372
|
+
setETag(value) {
|
|
2373
|
+
meta.headers["etag"] = value;
|
|
2374
|
+
},
|
|
2375
|
+
redirect(url2, status = 302) {
|
|
2376
|
+
return new Response(null, {
|
|
2377
|
+
status,
|
|
2378
|
+
headers: { Location: url2 }
|
|
2638
2379
|
});
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
const
|
|
2715
|
-
if (
|
|
2716
|
-
|
|
2717
|
-
return false;
|
|
2718
|
-
}
|
|
2719
|
-
await compileDevRoutes({
|
|
2720
|
-
rootDir,
|
|
2721
|
-
dist,
|
|
2722
|
-
files: [sourceAbsPath],
|
|
2723
|
-
logLevel: "silent"
|
|
2724
|
-
});
|
|
2725
|
-
compiledFiles.add(sourceAbsPath);
|
|
2726
|
-
return true;
|
|
2727
|
-
}
|
|
2728
|
-
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2729
|
-
const rel = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2730
|
-
if (!rel.startsWith("src/")) return null;
|
|
2731
|
-
const relWithoutSrc = rel.slice(4);
|
|
2732
|
-
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2733
|
-
return path9.resolve(rootDir, dist, jsRel);
|
|
2734
|
-
}
|
|
2735
|
-
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
2736
|
-
function clearGeneratedSchemas() {
|
|
2737
|
-
generatedSchemas.clear();
|
|
2738
|
-
}
|
|
2739
|
-
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2740
|
-
if (generatedSchemas.has(schemaPath)) {
|
|
2741
|
-
return false;
|
|
2742
|
-
}
|
|
2743
|
-
const prodAbsPath = path9.resolve(rootDir, routeFilePath);
|
|
2744
|
-
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2745
|
-
if (!fs8.existsSync(sourceAbsPath)) {
|
|
2746
|
-
return false;
|
|
2747
|
-
}
|
|
2748
|
-
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
2749
|
-
generatedSchemas.add(schemaPath);
|
|
2750
|
-
return false;
|
|
2751
|
-
}
|
|
2752
|
-
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
2753
|
-
if (fileRoutes.length === 0) {
|
|
2754
|
-
return false;
|
|
2755
|
-
}
|
|
2756
|
-
const sourceRelPath = path9.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2757
|
-
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2758
|
-
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2759
|
-
generatedSchemas.add(schemaPath);
|
|
2760
|
-
return true;
|
|
2761
|
-
}
|
|
2762
|
-
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
2763
|
-
const deleted = /* @__PURE__ */ new Set();
|
|
2764
|
-
for (const route of routes) {
|
|
2765
|
-
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
2766
|
-
if (deleted.has(schemaPath)) continue;
|
|
2767
|
-
deleted.add(schemaPath);
|
|
2768
|
-
try {
|
|
2769
|
-
await fs8.promises.unlink(schemaPath);
|
|
2770
|
-
} catch {
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2775
|
-
const rel = path9.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2776
|
-
let relWithoutDist = rel;
|
|
2777
|
-
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2778
|
-
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2779
|
-
}
|
|
2780
|
-
const srcRel = `src/${relWithoutDist}`;
|
|
2781
|
-
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2782
|
-
const tsAbs = path9.resolve(rootDir, tsRel);
|
|
2783
|
-
if (fs8.existsSync(tsAbs)) return tsAbs;
|
|
2784
|
-
return path9.resolve(rootDir, srcRel);
|
|
2785
|
-
}
|
|
2786
|
-
var devOnDemandEnabled = false;
|
|
2787
|
-
function isDevOnDemandEnabled() {
|
|
2788
|
-
return devOnDemandEnabled;
|
|
2789
|
-
}
|
|
2790
|
-
var devDistDir;
|
|
2791
|
-
function getDevDist() {
|
|
2792
|
-
return devDistDir;
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
// src/validator/validateInput.ts
|
|
2796
|
-
var moduleCache = /* @__PURE__ */ new Map();
|
|
2797
|
-
function invalidateSchemaCache() {
|
|
2798
|
-
moduleCache.clear();
|
|
2799
|
-
}
|
|
2800
|
-
async function loadSchemaModule(schemaPath) {
|
|
2801
|
-
let mod = moduleCache.get(schemaPath);
|
|
2802
|
-
if (!mod) {
|
|
2803
|
-
mod = await importWithCacheBust(schemaPath, isDevOnDemandEnabled());
|
|
2804
|
-
moduleCache.set(schemaPath, mod);
|
|
2805
|
-
}
|
|
2806
|
-
return mod;
|
|
2807
|
-
}
|
|
2808
|
-
async function validateInput(schemaPath, method, inputType, input) {
|
|
2809
|
-
const schemaName = getSchemaName(method, inputType);
|
|
2810
|
-
const schemaKey = `${schemaName}Schema`;
|
|
2811
|
-
let mod;
|
|
2812
|
-
try {
|
|
2813
|
-
mod = await loadSchemaModule(schemaPath);
|
|
2814
|
-
} catch (err) {
|
|
2815
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
2816
|
-
throw new InternalError(`Schema \u6A21\u5757\u52A0\u8F7D\u5931\u8D25: ${schemaPath}: ${reason}`);
|
|
2817
|
-
}
|
|
2818
|
-
const schema = mod[schemaKey];
|
|
2819
|
-
if (schema === void 0 || schema === null) {
|
|
2820
|
-
const data = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
2821
|
-
return { valid: true, issues: [], data };
|
|
2822
|
-
}
|
|
2823
|
-
if (typeof schema !== "object" || typeof schema.safeParse !== "function") {
|
|
2824
|
-
throw new InternalError(`Schema \u4E0D\u662F\u6709\u6548\u7684 zod schema: ${schemaPath}#${schemaName}`);
|
|
2825
|
-
}
|
|
2826
|
-
const inputObj = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
2827
|
-
const zodSchema = schema;
|
|
2828
|
-
const result = zodSchema.safeParse(inputObj);
|
|
2829
|
-
if (result.success) {
|
|
2830
|
-
const data = typeof result.data === "object" && result.data !== null && !Array.isArray(result.data) ? result.data : {};
|
|
2831
|
-
return { valid: true, issues: [], data };
|
|
2832
|
-
}
|
|
2833
|
-
const issues = mapZodIssues(result.error);
|
|
2834
|
-
return { valid: false, issues, data: inputObj };
|
|
2835
|
-
}
|
|
2836
|
-
function mapZodIssues(error) {
|
|
2837
|
-
return error.issues.map((issue) => {
|
|
2838
|
-
const code = mapZodCode(issue.code, issue.message);
|
|
2839
|
-
const path15 = issue.path.map(String).join(".") || "";
|
|
2840
|
-
return {
|
|
2841
|
-
path: path15,
|
|
2842
|
-
code,
|
|
2843
|
-
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2844
|
-
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
2845
|
-
message: issue.message
|
|
2846
|
-
};
|
|
2847
|
-
});
|
|
2848
|
-
}
|
|
2849
|
-
function mapZodCode(zodCode, message) {
|
|
2850
|
-
switch (zodCode) {
|
|
2851
|
-
case "invalid_type":
|
|
2852
|
-
case "invalid_union":
|
|
2853
|
-
case "invalid_union_discriminator":
|
|
2854
|
-
return "TYPE_MISMATCH";
|
|
2855
|
-
case "unrecognized_keys":
|
|
2856
|
-
return "INVALID_FORMAT";
|
|
2857
|
-
case "invalid_value":
|
|
2858
|
-
case "invalid_string":
|
|
2859
|
-
case "too_small":
|
|
2860
|
-
case "too_big":
|
|
2861
|
-
case "invalid_intersection_types":
|
|
2862
|
-
case "not_multiple_of":
|
|
2863
|
-
return "INVALID_VALUE";
|
|
2864
|
-
case "custom":
|
|
2865
|
-
return "INVALID_VALUE";
|
|
2866
|
-
default:
|
|
2867
|
-
if (message.includes("Required") || message.includes("required")) {
|
|
2868
|
-
return "MISSING_FIELD";
|
|
2869
|
-
}
|
|
2870
|
-
return "INVALID_VALUE";
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
function mapExpectedFromMessage(message) {
|
|
2874
|
-
const match = message.match(/Expected\s+(\w+)/i);
|
|
2875
|
-
return match ? match[1].toLowerCase() : "unknown";
|
|
2876
|
-
}
|
|
2877
|
-
function mapReceivedFromMessage(message) {
|
|
2878
|
-
const match = message.match(/received\s+(\w+)/i);
|
|
2879
|
-
return match ? match[1].toLowerCase() : "unknown";
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
// src/server/createServer.ts
|
|
2883
|
-
import {
|
|
2884
|
-
createServer as createHttpServer
|
|
2885
|
-
} from "http";
|
|
2886
|
-
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2887
|
-
import { readFileSync } from "fs";
|
|
2888
|
-
import { Readable as Readable2 } from "stream";
|
|
2889
|
-
import path11 from "path";
|
|
2890
|
-
|
|
2891
|
-
// src/router/matchRoute.ts
|
|
2892
|
-
function matchRoute(routes, method, path15) {
|
|
2893
|
-
for (const route of routes) {
|
|
2894
|
-
if (route.method !== method) {
|
|
2895
|
-
continue;
|
|
2896
|
-
}
|
|
2897
|
-
if (!route.isDynamic) {
|
|
2898
|
-
if (route.urlPath === path15) {
|
|
2899
|
-
return { route, params: {} };
|
|
2900
|
-
}
|
|
2901
|
-
continue;
|
|
2902
|
-
}
|
|
2903
|
-
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2904
|
-
if (params !== null) {
|
|
2905
|
-
return { route, params };
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
return null;
|
|
2909
|
-
}
|
|
2910
|
-
function matchWsRoute(wsRoutes, path15) {
|
|
2911
|
-
for (const route of wsRoutes) {
|
|
2912
|
-
if (!route.isDynamic) {
|
|
2913
|
-
if (route.urlPath === path15) {
|
|
2914
|
-
return { route, params: {} };
|
|
2915
|
-
}
|
|
2916
|
-
continue;
|
|
2917
|
-
}
|
|
2918
|
-
const params = matchDynamicPath(route.urlPath, path15, route.paramNames, route.isCatchAll);
|
|
2919
|
-
if (params !== null) {
|
|
2920
|
-
return { route, params };
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
return null;
|
|
2924
|
-
}
|
|
2925
|
-
function matchDynamicPath(pattern, path15, paramNames, isCatchAll) {
|
|
2926
|
-
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2927
|
-
const pathSegments = path15.split("/").filter(Boolean);
|
|
2928
|
-
if (isCatchAll) {
|
|
2929
|
-
const nonCatchAllCount = patternSegments.length - 1;
|
|
2930
|
-
if (pathSegments.length <= nonCatchAllCount) {
|
|
2931
|
-
return null;
|
|
2932
|
-
}
|
|
2933
|
-
const params2 = {};
|
|
2934
|
-
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
2935
|
-
const patternSeg = patternSegments[i];
|
|
2936
|
-
const pathSeg = pathSegments[i];
|
|
2937
|
-
if (patternSeg.startsWith(":")) {
|
|
2938
|
-
const paramName = patternSeg.slice(1);
|
|
2939
|
-
params2[paramName] = pathSeg;
|
|
2940
|
-
} else if (patternSeg !== pathSeg) {
|
|
2941
|
-
return null;
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
2945
|
-
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
2946
|
-
params2[catchAllParamName] = catchAllValue;
|
|
2947
|
-
if (Object.keys(params2).length !== paramNames.length) {
|
|
2948
|
-
return null;
|
|
2949
|
-
}
|
|
2950
|
-
return params2;
|
|
2951
|
-
}
|
|
2952
|
-
if (patternSegments.length !== pathSegments.length) {
|
|
2953
|
-
return null;
|
|
2954
|
-
}
|
|
2955
|
-
const params = {};
|
|
2956
|
-
for (let i = 0; i < patternSegments.length; i++) {
|
|
2957
|
-
const patternSeg = patternSegments[i];
|
|
2958
|
-
const pathSeg = pathSegments[i];
|
|
2959
|
-
if (patternSeg.startsWith(":")) {
|
|
2960
|
-
const paramName = patternSeg.slice(1);
|
|
2961
|
-
params[paramName] = pathSeg;
|
|
2962
|
-
} else if (patternSeg !== pathSeg) {
|
|
2963
|
-
return null;
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
if (Object.keys(params).length !== paramNames.length) {
|
|
2967
|
-
return null;
|
|
2968
|
-
}
|
|
2969
|
-
return params;
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
// src/loader/loadRouteModule.ts
|
|
2973
|
-
import fs9 from "fs";
|
|
2974
|
-
|
|
2975
|
-
// src/loader/resolveExports.ts
|
|
2976
|
-
function resolveExport(module, exportName) {
|
|
2977
|
-
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
2978
|
-
return module[exportName];
|
|
2979
|
-
}
|
|
2980
|
-
const defaultExport = module.default;
|
|
2981
|
-
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
2982
|
-
const value = defaultExport[exportName];
|
|
2983
|
-
if (value !== void 0) {
|
|
2984
|
-
return value;
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
return void 0;
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
// src/loader/validateRouteModule.ts
|
|
2991
|
-
function validateRouteModule(value, method, filePath) {
|
|
2992
|
-
if (typeof value !== "function") {
|
|
2993
|
-
throw new Error(
|
|
2994
|
-
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
2995
|
-
);
|
|
2380
|
+
},
|
|
2381
|
+
json(data, status) {
|
|
2382
|
+
const headers = { "Content-Type": "application/json" };
|
|
2383
|
+
return new Response(JSON.stringify(data), {
|
|
2384
|
+
status: status ?? 200,
|
|
2385
|
+
headers
|
|
2386
|
+
});
|
|
2387
|
+
},
|
|
2388
|
+
html(html, status) {
|
|
2389
|
+
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
2390
|
+
return new Response(html, {
|
|
2391
|
+
status: status ?? 200,
|
|
2392
|
+
headers
|
|
2393
|
+
});
|
|
2394
|
+
},
|
|
2395
|
+
getCookie(name) {
|
|
2396
|
+
return parsedCookies.get(name);
|
|
2397
|
+
},
|
|
2398
|
+
setCookie(name, value, options) {
|
|
2399
|
+
meta.setCookies.push(formatSetCookie(name, value, options));
|
|
2400
|
+
},
|
|
2401
|
+
deleteCookie(name) {
|
|
2402
|
+
meta.setCookies.push(formatSetCookie(name, "", { maxAge: 0 }));
|
|
2403
|
+
},
|
|
2404
|
+
/**
|
|
2405
|
+
* 创建 SSE writer,用于流式推送事件
|
|
2406
|
+
*
|
|
2407
|
+
* handler 调用此方法后,通过返回的 writer 推送事件,框架自动把 writer.response
|
|
2408
|
+
* 作为 HTTP 响应(Content-Type: text/event-stream)。
|
|
2409
|
+
*
|
|
2410
|
+
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
2411
|
+
*/
|
|
2412
|
+
sse() {
|
|
2413
|
+
const writer = createSseWriter();
|
|
2414
|
+
const ctxWithSse = ctx;
|
|
2415
|
+
ctxWithSse.__sseResponse = writer.response;
|
|
2416
|
+
ctxWithSse.__sseWriter = writer;
|
|
2417
|
+
return writer;
|
|
2418
|
+
},
|
|
2419
|
+
/**
|
|
2420
|
+
* 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
|
|
2421
|
+
*
|
|
2422
|
+
* 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
|
|
2423
|
+
* handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
|
|
2424
|
+
*/
|
|
2425
|
+
ok(data) {
|
|
2426
|
+
const responseConfig = config.response;
|
|
2427
|
+
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
2428
|
+
const body = okFn(data);
|
|
2429
|
+
return ctx.json(body);
|
|
2430
|
+
},
|
|
2431
|
+
/**
|
|
2432
|
+
* 返回错误响应(对象形式参数,status 和 code 均可省略)
|
|
2433
|
+
*
|
|
2434
|
+
* - status 省略时 HTTP 状态码默认 500
|
|
2435
|
+
* - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
|
|
2436
|
+
* - status 和 code 独立无关联
|
|
2437
|
+
*
|
|
2438
|
+
* body 用 config.response.fail(或默认实现)包装。
|
|
2439
|
+
*/
|
|
2440
|
+
fail(options) {
|
|
2441
|
+
const responseConfig = config.response;
|
|
2442
|
+
const failFn = responseConfig?.fail ?? ((e) => {
|
|
2443
|
+
const error = { message: e.message };
|
|
2444
|
+
if (e.code !== void 0) error.code = e.code;
|
|
2445
|
+
return { error };
|
|
2446
|
+
});
|
|
2447
|
+
const body = failFn({
|
|
2448
|
+
status: options.status,
|
|
2449
|
+
code: options.code,
|
|
2450
|
+
message: options.message
|
|
2451
|
+
});
|
|
2452
|
+
return ctx.json(body, options.status ?? 500);
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
const extend = config?.extendContext;
|
|
2456
|
+
if (typeof extend === "function") {
|
|
2457
|
+
extend(ctx);
|
|
2996
2458
|
}
|
|
2459
|
+
return ctx;
|
|
2997
2460
|
}
|
|
2998
2461
|
|
|
2999
|
-
// src/
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3005
|
-
if (sourcePath && fs9.existsSync(sourcePath)) {
|
|
3006
|
-
try {
|
|
3007
|
-
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3008
|
-
} catch (compileErr) {
|
|
3009
|
-
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
3010
|
-
throw new Error(`Failed to compile route module "${sourcePath}": ${reason}`, {
|
|
3011
|
-
cause: compileErr
|
|
3012
|
-
});
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
}
|
|
3017
|
-
let module;
|
|
3018
|
-
try {
|
|
3019
|
-
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
3020
|
-
} catch (err) {
|
|
3021
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
3022
|
-
throw new Error(`Failed to load route module "${filePath}": ${reason}`, { cause: err });
|
|
2462
|
+
// src/utils/queryToObject.ts
|
|
2463
|
+
function queryToObject(params) {
|
|
2464
|
+
const result = {};
|
|
2465
|
+
for (const [key, value] of params) {
|
|
2466
|
+
result[key] = value;
|
|
3023
2467
|
}
|
|
3024
|
-
|
|
3025
|
-
validateRouteModule(handler, method, filePath);
|
|
3026
|
-
return { handler, method };
|
|
2468
|
+
return result;
|
|
3027
2469
|
}
|
|
3028
2470
|
|
|
3029
2471
|
// src/utils/parseJsonBody.ts
|
|
@@ -3063,48 +2505,297 @@ async function parseMultipart(request) {
|
|
|
3063
2505
|
}
|
|
3064
2506
|
}
|
|
3065
2507
|
}
|
|
3066
|
-
return { fields, files };
|
|
2508
|
+
return { fields, files };
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/runtime/resolveInput.ts
|
|
2512
|
+
init_inputType();
|
|
2513
|
+
async function resolveInput(method, request) {
|
|
2514
|
+
const inputType = getInputTypeForMethod(method);
|
|
2515
|
+
if (inputType === "body") {
|
|
2516
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
2517
|
+
if (contentType.includes("multipart/form-data")) {
|
|
2518
|
+
return parseMultipart(request);
|
|
2519
|
+
}
|
|
2520
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
2521
|
+
const text2 = await request.text();
|
|
2522
|
+
if (text2.trim() === "") return null;
|
|
2523
|
+
const params = new URLSearchParams(text2);
|
|
2524
|
+
const obj = {};
|
|
2525
|
+
for (const [key, value] of params) {
|
|
2526
|
+
obj[key] = value;
|
|
2527
|
+
}
|
|
2528
|
+
return obj;
|
|
2529
|
+
}
|
|
2530
|
+
const text = await request.text();
|
|
2531
|
+
if (text.trim() === "") {
|
|
2532
|
+
return null;
|
|
2533
|
+
}
|
|
2534
|
+
const result = parseJsonBody(text);
|
|
2535
|
+
if (!result.success) {
|
|
2536
|
+
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
2537
|
+
{
|
|
2538
|
+
path: "body",
|
|
2539
|
+
code: "INVALID_FORMAT",
|
|
2540
|
+
expected: "JSON",
|
|
2541
|
+
received: "text",
|
|
2542
|
+
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
2543
|
+
}
|
|
2544
|
+
]);
|
|
2545
|
+
}
|
|
2546
|
+
return result.data;
|
|
2547
|
+
}
|
|
2548
|
+
const url = new URL(request.url);
|
|
2549
|
+
return queryToObject(url.searchParams);
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// src/utils/isPlainObject.ts
|
|
2553
|
+
function isPlainObject(value) {
|
|
2554
|
+
if (value === null || typeof value !== "object") {
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
if (Array.isArray(value)) {
|
|
2558
|
+
return false;
|
|
2559
|
+
}
|
|
2560
|
+
const proto = Object.getPrototypeOf(value);
|
|
2561
|
+
return proto === null || proto === Object.prototype;
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/response/toResponse.ts
|
|
2565
|
+
async function toResponse(value, meta) {
|
|
2566
|
+
if (value instanceof Promise) {
|
|
2567
|
+
return toResponse(await value, meta);
|
|
2568
|
+
}
|
|
2569
|
+
const applyMeta = (headers2) => {
|
|
2570
|
+
if (!meta) return;
|
|
2571
|
+
for (const [key, val] of Object.entries(meta.headers)) {
|
|
2572
|
+
headers2.set(key, val);
|
|
2573
|
+
}
|
|
2574
|
+
for (const cookie of meta.setCookies ?? []) {
|
|
2575
|
+
headers2.append("set-cookie", cookie);
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
if (value instanceof Response) {
|
|
2579
|
+
if (meta && (meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0)) {
|
|
2580
|
+
const headers2 = new Headers(value.headers);
|
|
2581
|
+
applyMeta(headers2);
|
|
2582
|
+
return new Response(value.body, {
|
|
2583
|
+
status: meta.status ?? value.status,
|
|
2584
|
+
headers: headers2
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
return value;
|
|
2588
|
+
}
|
|
2589
|
+
if (value === null || value === void 0) {
|
|
2590
|
+
const status = meta?.status ?? 204;
|
|
2591
|
+
const headers2 = new Headers();
|
|
2592
|
+
applyMeta(headers2);
|
|
2593
|
+
return new Response(null, { status, headers: headers2 });
|
|
2594
|
+
}
|
|
2595
|
+
if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
|
|
2596
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
2597
|
+
applyMeta(headers2);
|
|
2598
|
+
return new Response(value, {
|
|
2599
|
+
status: meta?.status ?? 200,
|
|
2600
|
+
headers: headers2
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
2604
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
2605
|
+
applyMeta(headers2);
|
|
2606
|
+
return new Response(value, {
|
|
2607
|
+
status: meta?.status ?? 200,
|
|
2608
|
+
headers: headers2
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
if (value instanceof Uint8Array) {
|
|
2612
|
+
const headers2 = new Headers({ "Content-Type": "application/octet-stream" });
|
|
2613
|
+
applyMeta(headers2);
|
|
2614
|
+
return new Response(value, {
|
|
2615
|
+
status: meta?.status ?? 200,
|
|
2616
|
+
headers: headers2
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
if (isPlainObject(value) || Array.isArray(value)) {
|
|
2620
|
+
const body2 = JSON.stringify(value);
|
|
2621
|
+
const headers2 = new Headers({ "Content-Type": "application/json" });
|
|
2622
|
+
applyMeta(headers2);
|
|
2623
|
+
return new Response(body2, {
|
|
2624
|
+
status: meta?.status ?? 200,
|
|
2625
|
+
headers: headers2
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
if (typeof value === "string") {
|
|
2629
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
2630
|
+
applyMeta(headers2);
|
|
2631
|
+
return new Response(value, {
|
|
2632
|
+
status: meta?.status ?? 200,
|
|
2633
|
+
headers: headers2
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
2637
|
+
const headers2 = new Headers({ "Content-Type": "text/plain" });
|
|
2638
|
+
applyMeta(headers2);
|
|
2639
|
+
return new Response(String(value), {
|
|
2640
|
+
status: meta?.status ?? 200,
|
|
2641
|
+
headers: headers2
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
const body = JSON.stringify(value);
|
|
2645
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2646
|
+
applyMeta(headers);
|
|
2647
|
+
return new Response(body, {
|
|
2648
|
+
status: meta?.status ?? 200,
|
|
2649
|
+
headers
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
// src/injection/injectParams.ts
|
|
2654
|
+
init_resolveInjection();
|
|
2655
|
+
function getBuiltinInjectionValue(type, ctx, body) {
|
|
2656
|
+
switch (type) {
|
|
2657
|
+
case "query":
|
|
2658
|
+
return queryToObject(ctx.query);
|
|
2659
|
+
case "params":
|
|
2660
|
+
return ctx.params;
|
|
2661
|
+
case "headers":
|
|
2662
|
+
return ctx.headers;
|
|
2663
|
+
case "context":
|
|
2664
|
+
return ctx;
|
|
2665
|
+
case "cookies":
|
|
2666
|
+
return ctx.cookies;
|
|
2667
|
+
case "ip":
|
|
2668
|
+
return ctx.ip;
|
|
2669
|
+
case "ua":
|
|
2670
|
+
return ctx.ua;
|
|
2671
|
+
case "body":
|
|
2672
|
+
return body;
|
|
2673
|
+
// form 与 body 共享解析结果(resolveInput 已按 Content-Type 解析 form-urlencoded)
|
|
2674
|
+
// 差异仅在 schema 校验(form coerce=true,由 collectRouteSchemaSources 标记)
|
|
2675
|
+
case "form":
|
|
2676
|
+
return body;
|
|
2677
|
+
case "files":
|
|
2678
|
+
if (body && typeof body === "object" && "files" in body) {
|
|
2679
|
+
return body.files;
|
|
2680
|
+
}
|
|
2681
|
+
return [];
|
|
2682
|
+
case "fields":
|
|
2683
|
+
if (body && typeof body === "object" && "fields" in body) {
|
|
2684
|
+
return body.fields;
|
|
2685
|
+
}
|
|
2686
|
+
return {};
|
|
2687
|
+
default:
|
|
2688
|
+
return void 0;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
2692
|
+
const injections = resolveInjection(handler);
|
|
2693
|
+
if (injections.length === 0) {
|
|
2694
|
+
return await handler();
|
|
2695
|
+
}
|
|
2696
|
+
const args = await Promise.all(
|
|
2697
|
+
injections.map(async (injection) => {
|
|
2698
|
+
if (injection.type !== "unknown") {
|
|
2699
|
+
return getBuiltinInjectionValue(injection.type, ctx, body);
|
|
2700
|
+
}
|
|
2701
|
+
if (injectors && injection.name in injectors) {
|
|
2702
|
+
return await injectors[injection.name](ctx);
|
|
2703
|
+
}
|
|
2704
|
+
return void 0;
|
|
2705
|
+
})
|
|
2706
|
+
);
|
|
2707
|
+
return await handler(...args);
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// src/runtime/invokeHandler.ts
|
|
2711
|
+
function wrapResult(result, ctx) {
|
|
2712
|
+
if (result instanceof Response) return result;
|
|
2713
|
+
const responseConfig = ctx.config.response;
|
|
2714
|
+
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
2715
|
+
return okFn(result);
|
|
2716
|
+
}
|
|
2717
|
+
function mergeMeta(response, meta) {
|
|
2718
|
+
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
2719
|
+
if (!hasMeta) return response;
|
|
2720
|
+
const headers = new Headers(response.headers);
|
|
2721
|
+
for (const [key, value] of Object.entries(meta.headers)) {
|
|
2722
|
+
headers.set(key, value);
|
|
2723
|
+
}
|
|
2724
|
+
for (const cookie of meta.setCookies) {
|
|
2725
|
+
headers.append("set-cookie", cookie);
|
|
2726
|
+
}
|
|
2727
|
+
return new Response(response.body, {
|
|
2728
|
+
status: meta.status ?? response.status,
|
|
2729
|
+
headers
|
|
2730
|
+
});
|
|
3067
2731
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
async function
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
const contentType = request.headers.get("content-type") ?? "";
|
|
3075
|
-
if (contentType.includes("multipart/form-data")) {
|
|
3076
|
-
return parseMultipart(request);
|
|
2732
|
+
async function compose(middlewares, ctx, finalHandler) {
|
|
2733
|
+
const meta = ctx.meta;
|
|
2734
|
+
let index = -1;
|
|
2735
|
+
async function dispatch(i) {
|
|
2736
|
+
if (i <= index) {
|
|
2737
|
+
throw new Error("next() called multiple times");
|
|
3077
2738
|
}
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
const params = new URLSearchParams(text2);
|
|
3082
|
-
const obj = {};
|
|
3083
|
-
for (const [key, value] of params) {
|
|
3084
|
-
obj[key] = value;
|
|
3085
|
-
}
|
|
3086
|
-
return obj;
|
|
2739
|
+
index = i;
|
|
2740
|
+
if (i >= middlewares.length) {
|
|
2741
|
+
return await finalHandler();
|
|
3087
2742
|
}
|
|
3088
|
-
const
|
|
3089
|
-
|
|
3090
|
-
|
|
2743
|
+
const mw = middlewares[i];
|
|
2744
|
+
let innerResponse;
|
|
2745
|
+
const next = async () => {
|
|
2746
|
+
innerResponse = await dispatch(i + 1);
|
|
2747
|
+
return innerResponse;
|
|
2748
|
+
};
|
|
2749
|
+
const result = await mw(ctx, next);
|
|
2750
|
+
if (result instanceof Response) {
|
|
2751
|
+
return mergeMeta(result, meta);
|
|
3091
2752
|
}
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
3095
|
-
{
|
|
3096
|
-
path: "body",
|
|
3097
|
-
code: "INVALID_FORMAT",
|
|
3098
|
-
expected: "JSON",
|
|
3099
|
-
received: "text",
|
|
3100
|
-
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
3101
|
-
}
|
|
3102
|
-
]);
|
|
2753
|
+
if (innerResponse !== void 0) {
|
|
2754
|
+
return innerResponse;
|
|
3103
2755
|
}
|
|
3104
|
-
|
|
2756
|
+
throw new Error("\u4E2D\u95F4\u4EF6\u5FC5\u987B await next() \u6216\u8FD4\u56DE Response");
|
|
3105
2757
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
2758
|
+
return await dispatch(0);
|
|
2759
|
+
}
|
|
2760
|
+
async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
2761
|
+
const meta = ctx.meta;
|
|
2762
|
+
const pickSseAndAutoClose = () => {
|
|
2763
|
+
const sseWriter = ctx.__sseWriter;
|
|
2764
|
+
if (!sseWriter) return null;
|
|
2765
|
+
if (!sseWriter.closed && !sseWriter.aborted) {
|
|
2766
|
+
sseWriter.close();
|
|
2767
|
+
}
|
|
2768
|
+
return mergeMeta(sseWriter.response, meta);
|
|
2769
|
+
};
|
|
2770
|
+
const autoCloseSseOnError = () => {
|
|
2771
|
+
const sseWriter = ctx.__sseWriter;
|
|
2772
|
+
if (sseWriter && !sseWriter.closed && !sseWriter.aborted) {
|
|
2773
|
+
sseWriter.close();
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
if (!middlewares || middlewares.length === 0) {
|
|
2777
|
+
try {
|
|
2778
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2779
|
+
const sseResponse = pickSseAndAutoClose();
|
|
2780
|
+
if (sseResponse) return sseResponse;
|
|
2781
|
+
return toResponse(wrapResult(result, ctx), meta);
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
autoCloseSseOnError();
|
|
2784
|
+
throw err;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
const finalHandler = async () => {
|
|
2788
|
+
try {
|
|
2789
|
+
const result = await injectParamsAsync(handler, ctx, body, injectors);
|
|
2790
|
+
const sseResponse = pickSseAndAutoClose();
|
|
2791
|
+
if (sseResponse) return sseResponse;
|
|
2792
|
+
return toResponse(wrapResult(result, ctx), meta);
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
autoCloseSseOnError();
|
|
2795
|
+
throw err;
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
return await compose(middlewares, ctx, finalHandler);
|
|
3108
2799
|
}
|
|
3109
2800
|
|
|
3110
2801
|
// src/response/sendNodeResponse.ts
|
|
@@ -3131,6 +2822,94 @@ async function sendNodeResponse(response, res) {
|
|
|
3131
2822
|
res.end();
|
|
3132
2823
|
}
|
|
3133
2824
|
|
|
2825
|
+
// src/validator/validateInput.ts
|
|
2826
|
+
init_schemaName();
|
|
2827
|
+
var moduleCache = /* @__PURE__ */ new Map();
|
|
2828
|
+
function invalidateSchemaCache() {
|
|
2829
|
+
moduleCache.clear();
|
|
2830
|
+
}
|
|
2831
|
+
async function loadSchemaModule(schemaPath) {
|
|
2832
|
+
let mod = moduleCache.get(schemaPath);
|
|
2833
|
+
if (!mod) {
|
|
2834
|
+
mod = await importWithCacheBust(schemaPath, isDevOnDemandEnabled());
|
|
2835
|
+
moduleCache.set(schemaPath, mod);
|
|
2836
|
+
}
|
|
2837
|
+
return mod;
|
|
2838
|
+
}
|
|
2839
|
+
async function validateInput(schemaPath, method, inputType, input) {
|
|
2840
|
+
const schemaName = getSchemaName(method, inputType);
|
|
2841
|
+
const schemaKey = `${schemaName}Schema`;
|
|
2842
|
+
let mod;
|
|
2843
|
+
try {
|
|
2844
|
+
mod = await loadSchemaModule(schemaPath);
|
|
2845
|
+
} catch (err) {
|
|
2846
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2847
|
+
throw new InternalError(`Schema \u6A21\u5757\u52A0\u8F7D\u5931\u8D25: ${schemaPath}: ${reason}`);
|
|
2848
|
+
}
|
|
2849
|
+
const schema = mod[schemaKey];
|
|
2850
|
+
if (schema === void 0 || schema === null) {
|
|
2851
|
+
const data = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
2852
|
+
return { valid: true, issues: [], data };
|
|
2853
|
+
}
|
|
2854
|
+
if (typeof schema !== "object" || typeof schema.safeParse !== "function") {
|
|
2855
|
+
throw new InternalError(`Schema \u4E0D\u662F\u6709\u6548\u7684 zod schema: ${schemaPath}#${schemaName}`);
|
|
2856
|
+
}
|
|
2857
|
+
const inputObj = typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
|
|
2858
|
+
const zodSchema = schema;
|
|
2859
|
+
const result = zodSchema.safeParse(inputObj);
|
|
2860
|
+
if (result.success) {
|
|
2861
|
+
const data = typeof result.data === "object" && result.data !== null && !Array.isArray(result.data) ? result.data : {};
|
|
2862
|
+
return { valid: true, issues: [], data };
|
|
2863
|
+
}
|
|
2864
|
+
const issues = mapZodIssues(result.error);
|
|
2865
|
+
return { valid: false, issues, data: inputObj };
|
|
2866
|
+
}
|
|
2867
|
+
function mapZodIssues(error) {
|
|
2868
|
+
return error.issues.map((issue) => {
|
|
2869
|
+
const code = mapZodCode(issue.code, issue.message);
|
|
2870
|
+
const path14 = issue.path.map(String).join(".") || "";
|
|
2871
|
+
return {
|
|
2872
|
+
path: path14,
|
|
2873
|
+
code,
|
|
2874
|
+
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2875
|
+
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
2876
|
+
message: issue.message
|
|
2877
|
+
};
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
function mapZodCode(zodCode, message) {
|
|
2881
|
+
switch (zodCode) {
|
|
2882
|
+
case "invalid_type":
|
|
2883
|
+
case "invalid_union":
|
|
2884
|
+
case "invalid_union_discriminator":
|
|
2885
|
+
return "TYPE_MISMATCH";
|
|
2886
|
+
case "unrecognized_keys":
|
|
2887
|
+
return "INVALID_FORMAT";
|
|
2888
|
+
case "invalid_value":
|
|
2889
|
+
case "invalid_string":
|
|
2890
|
+
case "too_small":
|
|
2891
|
+
case "too_big":
|
|
2892
|
+
case "invalid_intersection_types":
|
|
2893
|
+
case "not_multiple_of":
|
|
2894
|
+
return "INVALID_VALUE";
|
|
2895
|
+
case "custom":
|
|
2896
|
+
return "INVALID_VALUE";
|
|
2897
|
+
default:
|
|
2898
|
+
if (message.includes("Required") || message.includes("required")) {
|
|
2899
|
+
return "MISSING_FIELD";
|
|
2900
|
+
}
|
|
2901
|
+
return "INVALID_VALUE";
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
function mapExpectedFromMessage(message) {
|
|
2905
|
+
const match = message.match(/Expected\s+(\w+)/i);
|
|
2906
|
+
return match ? match[1].toLowerCase() : "unknown";
|
|
2907
|
+
}
|
|
2908
|
+
function mapReceivedFromMessage(message) {
|
|
2909
|
+
const match = message.match(/received\s+(\w+)/i);
|
|
2910
|
+
return match ? match[1].toLowerCase() : "unknown";
|
|
2911
|
+
}
|
|
2912
|
+
|
|
3134
2913
|
// src/server/createServer.ts
|
|
3135
2914
|
init_inputType();
|
|
3136
2915
|
|
|
@@ -3152,9 +2931,9 @@ function getClientIp(req) {
|
|
|
3152
2931
|
}
|
|
3153
2932
|
|
|
3154
2933
|
// src/server/handleWsUpgrade.ts
|
|
3155
|
-
import
|
|
2934
|
+
import fs9 from "fs";
|
|
3156
2935
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3157
|
-
import
|
|
2936
|
+
import path9 from "path";
|
|
3158
2937
|
|
|
3159
2938
|
// src/errors/formatErrorResponse.ts
|
|
3160
2939
|
function formatErrorResponse(error) {
|
|
@@ -3229,6 +3008,71 @@ function buildErrorResponse(err) {
|
|
|
3229
3008
|
}
|
|
3230
3009
|
}
|
|
3231
3010
|
|
|
3011
|
+
// src/middleware/loadMiddlewares.ts
|
|
3012
|
+
var middlewareCache = /* @__PURE__ */ new Map();
|
|
3013
|
+
function invalidateMiddlewareCache() {
|
|
3014
|
+
middlewareCache.clear();
|
|
3015
|
+
}
|
|
3016
|
+
function getCachedMiddlewares(absPath) {
|
|
3017
|
+
return middlewareCache.get(absPath);
|
|
3018
|
+
}
|
|
3019
|
+
function setCachedMiddlewares(absPath, bundle) {
|
|
3020
|
+
middlewareCache.set(absPath, bundle);
|
|
3021
|
+
}
|
|
3022
|
+
async function loadMiddlewaresFile(filePath) {
|
|
3023
|
+
try {
|
|
3024
|
+
const module = await importWithCacheBust(filePath);
|
|
3025
|
+
const middlewares = module.default ?? module.middlewares ?? [];
|
|
3026
|
+
if (!Array.isArray(middlewares)) {
|
|
3027
|
+
console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3028
|
+
return { middlewares: [], injectors: {} };
|
|
3029
|
+
}
|
|
3030
|
+
const validMiddlewares = middlewares.filter((m) => {
|
|
3031
|
+
if (typeof m !== "function") {
|
|
3032
|
+
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
3035
|
+
return true;
|
|
3036
|
+
});
|
|
3037
|
+
const injectors = module.injectors ?? {};
|
|
3038
|
+
if (typeof injectors !== "object" || injectors === null) {
|
|
3039
|
+
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3040
|
+
return { middlewares: validMiddlewares, injectors: {} };
|
|
3041
|
+
}
|
|
3042
|
+
const validInjectors = {};
|
|
3043
|
+
for (const [name, injector] of Object.entries(injectors)) {
|
|
3044
|
+
if (typeof injector !== "function") {
|
|
3045
|
+
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
validInjectors[name] = injector;
|
|
3049
|
+
}
|
|
3050
|
+
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
3051
|
+
} catch {
|
|
3052
|
+
return { middlewares: [], injectors: {} };
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
async function loadMergedMiddlewares(middlewarePaths) {
|
|
3056
|
+
if (middlewarePaths.length === 0) return void 0;
|
|
3057
|
+
const mergedMiddlewares = [];
|
|
3058
|
+
const mergedInjectors = {};
|
|
3059
|
+
for (const absMwPath of middlewarePaths) {
|
|
3060
|
+
let bundle = getCachedMiddlewares(absMwPath);
|
|
3061
|
+
if (bundle === void 0) {
|
|
3062
|
+
bundle = await loadMiddlewaresFile(absMwPath);
|
|
3063
|
+
setCachedMiddlewares(absMwPath, bundle);
|
|
3064
|
+
}
|
|
3065
|
+
mergedMiddlewares.push(...bundle.middlewares);
|
|
3066
|
+
for (const [name, injector] of Object.entries(bundle.injectors)) {
|
|
3067
|
+
mergedInjectors[name] = injector;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (mergedMiddlewares.length === 0 && Object.keys(mergedInjectors).length === 0) {
|
|
3071
|
+
return void 0;
|
|
3072
|
+
}
|
|
3073
|
+
return { middlewares: mergedMiddlewares, injectors: mergedInjectors };
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3232
3076
|
// src/runtime/wsHandler.ts
|
|
3233
3077
|
function wrapWsSocket(rawSocket) {
|
|
3234
3078
|
return {
|
|
@@ -3256,7 +3100,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
3256
3100
|
const dist = getDevDist();
|
|
3257
3101
|
if (dist) {
|
|
3258
3102
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3259
|
-
if (sourcePath &&
|
|
3103
|
+
if (sourcePath && fs9.existsSync(sourcePath)) {
|
|
3260
3104
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3261
3105
|
}
|
|
3262
3106
|
}
|
|
@@ -3336,7 +3180,7 @@ function attachWebSocket(options) {
|
|
|
3336
3180
|
const finalHandler = async () => {
|
|
3337
3181
|
let handlers;
|
|
3338
3182
|
try {
|
|
3339
|
-
const absoluteFilePath =
|
|
3183
|
+
const absoluteFilePath = path9.resolve(rootDir, route.filePath);
|
|
3340
3184
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
3341
3185
|
} catch (err) {
|
|
3342
3186
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -3435,15 +3279,15 @@ function limitStreamSize(stream, maxSize) {
|
|
|
3435
3279
|
}
|
|
3436
3280
|
});
|
|
3437
3281
|
}
|
|
3438
|
-
function findAllowedMethods(routes,
|
|
3282
|
+
function findAllowedMethods(routes, path14) {
|
|
3439
3283
|
const methods = /* @__PURE__ */ new Set();
|
|
3440
3284
|
for (const route of routes) {
|
|
3441
|
-
if (route.urlPath ===
|
|
3285
|
+
if (route.urlPath === path14) {
|
|
3442
3286
|
methods.add(route.method);
|
|
3443
3287
|
continue;
|
|
3444
3288
|
}
|
|
3445
3289
|
if (route.isDynamic) {
|
|
3446
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3290
|
+
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
3447
3291
|
if (params !== null) {
|
|
3448
3292
|
methods.add(route.method);
|
|
3449
3293
|
}
|
|
@@ -3529,7 +3373,7 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3529
3373
|
}
|
|
3530
3374
|
ctx.params = match.params;
|
|
3531
3375
|
const { route } = match;
|
|
3532
|
-
const absoluteFilePath =
|
|
3376
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3533
3377
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3534
3378
|
const input = await resolveInput(route.method, request);
|
|
3535
3379
|
const inputType = getInputTypeForMethod(route.method);
|
|
@@ -3590,229 +3434,6 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3590
3434
|
}
|
|
3591
3435
|
}
|
|
3592
3436
|
|
|
3593
|
-
// src/testServer.ts
|
|
3594
|
-
var DEFAULT_PATTERNS = ["src/api/**/*.ts"];
|
|
3595
|
-
var DEFAULT_BODY_LIMIT2 = 10 * 1024 * 1024;
|
|
3596
|
-
async function createTestServer(options) {
|
|
3597
|
-
const {
|
|
3598
|
-
rootDir,
|
|
3599
|
-
patterns = DEFAULT_PATTERNS,
|
|
3600
|
-
dist,
|
|
3601
|
-
cors: cors2 = false,
|
|
3602
|
-
helmet: helmet2 = false,
|
|
3603
|
-
logger: logger2 = false,
|
|
3604
|
-
middlewares,
|
|
3605
|
-
injectors,
|
|
3606
|
-
onError,
|
|
3607
|
-
config,
|
|
3608
|
-
bodyLimit = DEFAULT_BODY_LIMIT2
|
|
3609
|
-
} = options;
|
|
3610
|
-
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
3611
|
-
const sorted = sortRoutes(routes);
|
|
3612
|
-
const schemaDist = dist ? path12.isAbsolute(dist) ? dist : path12.resolve(rootDir, dist) : await fs11.mkdtemp(path12.join(os.tmpdir(), "faapi-test-schema-"));
|
|
3613
|
-
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
3614
|
-
const { server } = createServer({
|
|
3615
|
-
routes: sorted,
|
|
3616
|
-
rootDir,
|
|
3617
|
-
dist: schemaDist,
|
|
3618
|
-
cors: cors2,
|
|
3619
|
-
helmet: helmet2,
|
|
3620
|
-
logger: logger2,
|
|
3621
|
-
middlewares,
|
|
3622
|
-
injectors,
|
|
3623
|
-
onError,
|
|
3624
|
-
config,
|
|
3625
|
-
wsRoutes,
|
|
3626
|
-
bodyLimit
|
|
3627
|
-
});
|
|
3628
|
-
const baseUrl = await listenOnRandomPort(server);
|
|
3629
|
-
let closed = false;
|
|
3630
|
-
const testServer = {
|
|
3631
|
-
server,
|
|
3632
|
-
baseUrl,
|
|
3633
|
-
routes: sorted,
|
|
3634
|
-
wsRoutes,
|
|
3635
|
-
schemaDist,
|
|
3636
|
-
async close() {
|
|
3637
|
-
if (closed) return;
|
|
3638
|
-
closed = true;
|
|
3639
|
-
const s = server;
|
|
3640
|
-
s.closeAllConnections?.();
|
|
3641
|
-
s.closeIdleConnections?.();
|
|
3642
|
-
await new Promise((resolve) => {
|
|
3643
|
-
server.close(() => resolve());
|
|
3644
|
-
});
|
|
3645
|
-
await fs11.rm(schemaDist, { recursive: true, force: true }).catch(() => {
|
|
3646
|
-
});
|
|
3647
|
-
invalidateSchemaCache();
|
|
3648
|
-
}
|
|
3649
|
-
};
|
|
3650
|
-
return testServer;
|
|
3651
|
-
}
|
|
3652
|
-
function listenOnRandomPort(server) {
|
|
3653
|
-
return new Promise((resolve, reject) => {
|
|
3654
|
-
server.listen(0, () => {
|
|
3655
|
-
const addr = server.address();
|
|
3656
|
-
if (typeof addr === "object" && addr !== null) {
|
|
3657
|
-
resolve(`http://localhost:${addr.port}`);
|
|
3658
|
-
} else {
|
|
3659
|
-
reject(new Error("Failed to get server address"));
|
|
3660
|
-
}
|
|
3661
|
-
});
|
|
3662
|
-
server.on("error", (err) => {
|
|
3663
|
-
reject(err);
|
|
3664
|
-
});
|
|
3665
|
-
});
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
|
-
// src/wsTestClient.ts
|
|
3669
|
-
import { WebSocket as WebSocket2 } from "ws";
|
|
3670
|
-
var MessageQueue = class {
|
|
3671
|
-
queue = [];
|
|
3672
|
-
waiters = [];
|
|
3673
|
-
listener;
|
|
3674
|
-
constructor(ws) {
|
|
3675
|
-
this.listener = (data) => {
|
|
3676
|
-
const msg = normalizeRawData(data);
|
|
3677
|
-
const waiter = this.waiters.shift();
|
|
3678
|
-
if (waiter) {
|
|
3679
|
-
waiter(msg);
|
|
3680
|
-
} else {
|
|
3681
|
-
this.queue.push(msg);
|
|
3682
|
-
}
|
|
3683
|
-
};
|
|
3684
|
-
ws.on("message", this.listener);
|
|
3685
|
-
}
|
|
3686
|
-
/**
|
|
3687
|
-
* 取下一条消息
|
|
3688
|
-
*
|
|
3689
|
-
* 队列有则立即 resolve,无则注册 waiter 等待下一条 'message' 事件。
|
|
3690
|
-
* 超时未到 → reject('WebSocket message timeout'),waiter 被清理。
|
|
3691
|
-
*
|
|
3692
|
-
* @param timeout 超时毫秒,默认 2000
|
|
3693
|
-
*/
|
|
3694
|
-
next(timeout = 2e3) {
|
|
3695
|
-
return new Promise((resolve, reject) => {
|
|
3696
|
-
const wrapped = (msg2) => {
|
|
3697
|
-
clearTimeout(timer);
|
|
3698
|
-
resolve(msg2);
|
|
3699
|
-
};
|
|
3700
|
-
const timer = setTimeout(() => {
|
|
3701
|
-
const idx = this.waiters.indexOf(wrapped);
|
|
3702
|
-
if (idx >= 0) this.waiters.splice(idx, 1);
|
|
3703
|
-
reject(new Error("WebSocket message timeout"));
|
|
3704
|
-
}, timeout);
|
|
3705
|
-
const msg = this.queue.shift();
|
|
3706
|
-
if (msg !== void 0) {
|
|
3707
|
-
wrapped(msg);
|
|
3708
|
-
} else {
|
|
3709
|
-
this.waiters.push(wrapped);
|
|
3710
|
-
}
|
|
3711
|
-
});
|
|
3712
|
-
}
|
|
3713
|
-
};
|
|
3714
|
-
function normalizeRawData(data) {
|
|
3715
|
-
if (Buffer.isBuffer(data)) {
|
|
3716
|
-
return data.toString("utf8");
|
|
3717
|
-
}
|
|
3718
|
-
if (Array.isArray(data)) {
|
|
3719
|
-
return Buffer.concat(data).toString("utf8");
|
|
3720
|
-
}
|
|
3721
|
-
return Buffer.from(data).toString("utf8");
|
|
3722
|
-
}
|
|
3723
|
-
function waitForWsOpen(ws, timeout = 2e3) {
|
|
3724
|
-
return new Promise((resolve, reject) => {
|
|
3725
|
-
const timer = setTimeout(() => {
|
|
3726
|
-
reject(new Error("WebSocket open timeout"));
|
|
3727
|
-
}, timeout);
|
|
3728
|
-
const cleanup = () => {
|
|
3729
|
-
clearTimeout(timer);
|
|
3730
|
-
ws.removeListener("open", onOpen);
|
|
3731
|
-
ws.removeListener("error", onError);
|
|
3732
|
-
ws.removeListener("close", onClose);
|
|
3733
|
-
};
|
|
3734
|
-
const onOpen = () => {
|
|
3735
|
-
cleanup();
|
|
3736
|
-
resolve();
|
|
3737
|
-
};
|
|
3738
|
-
const onError = (err) => {
|
|
3739
|
-
cleanup();
|
|
3740
|
-
reject(err);
|
|
3741
|
-
};
|
|
3742
|
-
const onClose = () => {
|
|
3743
|
-
cleanup();
|
|
3744
|
-
reject(new Error("WebSocket closed before open"));
|
|
3745
|
-
};
|
|
3746
|
-
ws.once("open", onOpen);
|
|
3747
|
-
ws.once("error", onError);
|
|
3748
|
-
ws.once("close", onClose);
|
|
3749
|
-
});
|
|
3750
|
-
}
|
|
3751
|
-
async function connectWs(baseUrl, pathname, options = {}) {
|
|
3752
|
-
const { timeout = 2e3, headers, protocols } = options;
|
|
3753
|
-
const wsBaseUrl = baseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
3754
|
-
const url = `${wsBaseUrl}${pathname}`;
|
|
3755
|
-
const ws = new WebSocket2(url, protocols, headers ? { headers } : void 0);
|
|
3756
|
-
const queue = new MessageQueue(ws);
|
|
3757
|
-
try {
|
|
3758
|
-
await waitForWsOpen(ws, timeout);
|
|
3759
|
-
} catch (err) {
|
|
3760
|
-
if (ws.readyState === WebSocket2.OPEN || ws.readyState === WebSocket2.CONNECTING) {
|
|
3761
|
-
ws.close();
|
|
3762
|
-
}
|
|
3763
|
-
throw err;
|
|
3764
|
-
}
|
|
3765
|
-
let closed = false;
|
|
3766
|
-
return {
|
|
3767
|
-
ws,
|
|
3768
|
-
queue,
|
|
3769
|
-
async close() {
|
|
3770
|
-
if (closed) return;
|
|
3771
|
-
closed = true;
|
|
3772
|
-
if (ws.readyState === WebSocket2.OPEN || ws.readyState === WebSocket2.CONNECTING) {
|
|
3773
|
-
ws.close();
|
|
3774
|
-
}
|
|
3775
|
-
await new Promise((resolve) => {
|
|
3776
|
-
const timer = setTimeout(resolve, 1e3);
|
|
3777
|
-
ws.once("close", () => {
|
|
3778
|
-
clearTimeout(timer);
|
|
3779
|
-
resolve();
|
|
3780
|
-
});
|
|
3781
|
-
});
|
|
3782
|
-
}
|
|
3783
|
-
};
|
|
3784
|
-
}
|
|
3785
|
-
|
|
3786
|
-
// src/cli/createAppCore.ts
|
|
3787
|
-
import fs13 from "fs";
|
|
3788
|
-
import path14 from "path";
|
|
3789
|
-
import { PassThrough, Readable as Readable3 } from "stream";
|
|
3790
|
-
|
|
3791
|
-
// src/router/detectRouteConflicts.ts
|
|
3792
|
-
function detectRouteConflicts(routes) {
|
|
3793
|
-
const map = /* @__PURE__ */ new Map();
|
|
3794
|
-
for (const route of routes) {
|
|
3795
|
-
const key = `${route.method} ${route.urlPath}`;
|
|
3796
|
-
const existing = map.get(key);
|
|
3797
|
-
if (existing) {
|
|
3798
|
-
existing.files.push(route.filePath);
|
|
3799
|
-
} else {
|
|
3800
|
-
map.set(key, {
|
|
3801
|
-
method: route.method,
|
|
3802
|
-
urlPath: route.urlPath,
|
|
3803
|
-
files: [route.filePath]
|
|
3804
|
-
});
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
const conflicts = [];
|
|
3808
|
-
for (const conflict of map.values()) {
|
|
3809
|
-
if (conflict.files.length > 1) {
|
|
3810
|
-
conflicts.push(conflict);
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
return conflicts;
|
|
3814
|
-
}
|
|
3815
|
-
|
|
3816
3437
|
// src/server/startServer.ts
|
|
3817
3438
|
function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
3818
3439
|
if (handlerWrappers.length > 0) {
|
|
@@ -3842,8 +3463,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
3842
3463
|
}
|
|
3843
3464
|
|
|
3844
3465
|
// src/cli/generateRoutes.ts
|
|
3845
|
-
import
|
|
3846
|
-
import
|
|
3466
|
+
import fs10 from "fs";
|
|
3467
|
+
import path11 from "path";
|
|
3847
3468
|
async function hydrateRoutes(manifest) {
|
|
3848
3469
|
const hydrateRoute = (serialized) => ({
|
|
3849
3470
|
method: serialized.method,
|
|
@@ -3959,8 +3580,8 @@ function isFaapiConfigKey(key) {
|
|
|
3959
3580
|
async function createAppBase(options) {
|
|
3960
3581
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
3961
3582
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
3962
|
-
const routesPath =
|
|
3963
|
-
if (!
|
|
3583
|
+
const routesPath = path12.resolve(rootDir, dist, ROUTES_FILE);
|
|
3584
|
+
if (!fs11.existsSync(routesPath)) {
|
|
3964
3585
|
throw new Error(
|
|
3965
3586
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
3966
3587
|
);
|
|
@@ -4160,6 +3781,180 @@ async function createAppBase(options) {
|
|
|
4160
3781
|
return { app, ctx };
|
|
4161
3782
|
}
|
|
4162
3783
|
|
|
3784
|
+
// src/router/scanRoutes.ts
|
|
3785
|
+
import fg2 from "fast-glob";
|
|
3786
|
+
import path13 from "path";
|
|
3787
|
+
import fs12 from "fs";
|
|
3788
|
+
|
|
3789
|
+
// src/router/constants.ts
|
|
3790
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
3791
|
+
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
3792
|
+
|
|
3793
|
+
// src/utils/normalizePath.ts
|
|
3794
|
+
function normalizePath(path14) {
|
|
3795
|
+
if (!path14) return "";
|
|
3796
|
+
let result = path14.replace(/\\/g, "/");
|
|
3797
|
+
result = result.replace(/\/+/g, "/");
|
|
3798
|
+
result = result.replace(/\/+$/, "");
|
|
3799
|
+
if (result && !result.startsWith("/")) {
|
|
3800
|
+
result = "/" + result;
|
|
3801
|
+
}
|
|
3802
|
+
return result;
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
// src/router/parseRouteFile.ts
|
|
3806
|
+
function dynamicSegmentToParam(segment) {
|
|
3807
|
+
const match = segment.match(/^\[(.+)\]$/);
|
|
3808
|
+
if (match) {
|
|
3809
|
+
return ":" + match[1];
|
|
3810
|
+
}
|
|
3811
|
+
return segment;
|
|
3812
|
+
}
|
|
3813
|
+
function extractParamNames(urlPath) {
|
|
3814
|
+
const params = [];
|
|
3815
|
+
const segments = urlPath.split("/");
|
|
3816
|
+
for (const segment of segments) {
|
|
3817
|
+
if (segment.startsWith(":...")) {
|
|
3818
|
+
params.push(segment.slice(4));
|
|
3819
|
+
} else if (segment.startsWith(":")) {
|
|
3820
|
+
params.push(segment.slice(1));
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
return params;
|
|
3824
|
+
}
|
|
3825
|
+
function isCatchAllSegment(segment) {
|
|
3826
|
+
return /^\[\.\.\..+\]$/.test(segment);
|
|
3827
|
+
}
|
|
3828
|
+
function isRouteGroup(segment) {
|
|
3829
|
+
return /^\(.+\)$/.test(segment);
|
|
3830
|
+
}
|
|
3831
|
+
function filePathToUrlPath(filePath) {
|
|
3832
|
+
const withoutPrefix = filePath.startsWith("src/") ? filePath.slice(4) : filePath;
|
|
3833
|
+
const lastSlashIndex = withoutPrefix.lastIndexOf("/");
|
|
3834
|
+
const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
|
|
3835
|
+
if (!dirPath) {
|
|
3836
|
+
return "";
|
|
3837
|
+
}
|
|
3838
|
+
const segments = dirPath.split("/").filter((s) => !isRouteGroup(s)).map(dynamicSegmentToParam);
|
|
3839
|
+
return normalizePath(segments.join("/"));
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
// src/router/scanRoutes.ts
|
|
3843
|
+
var HTTP_OR_WS_EXPORT_RE = new RegExp(
|
|
3844
|
+
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|WS)\b`,
|
|
3845
|
+
"g"
|
|
3846
|
+
);
|
|
3847
|
+
function extractExportsFromSource(source) {
|
|
3848
|
+
const names = /* @__PURE__ */ new Set();
|
|
3849
|
+
let match;
|
|
3850
|
+
HTTP_OR_WS_EXPORT_RE.lastIndex = 0;
|
|
3851
|
+
while ((match = HTTP_OR_WS_EXPORT_RE.exec(source)) !== null) {
|
|
3852
|
+
names.add(match[1]);
|
|
3853
|
+
}
|
|
3854
|
+
return names;
|
|
3855
|
+
}
|
|
3856
|
+
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
3857
|
+
const routeDir = path13.dirname(routeFilePath);
|
|
3858
|
+
const resolvedRoot = path13.resolve(rootDir);
|
|
3859
|
+
const paths = [];
|
|
3860
|
+
let currentDir = path13.resolve(rootDir, routeDir);
|
|
3861
|
+
while (true) {
|
|
3862
|
+
if (dist) {
|
|
3863
|
+
const mwTsPath = path13.join(currentDir, "middlewares.ts");
|
|
3864
|
+
const mwJsPath = path13.join(currentDir, "middlewares.js");
|
|
3865
|
+
const absTsPath = path13.resolve(rootDir, mwTsPath);
|
|
3866
|
+
const absJsPath = path13.resolve(rootDir, mwJsPath);
|
|
3867
|
+
const absMwPath = fs12.existsSync(absTsPath) ? absTsPath : fs12.existsSync(absJsPath) ? absJsPath : null;
|
|
3868
|
+
if (absMwPath) {
|
|
3869
|
+
const relMwPath = path13.relative(rootDir, absMwPath);
|
|
3870
|
+
const prodAbsPath = path13.resolve(rootDir, toProdFilePath(relMwPath, dist));
|
|
3871
|
+
paths.push(prodAbsPath);
|
|
3872
|
+
}
|
|
3873
|
+
} else {
|
|
3874
|
+
for (const ext of [".ts", ".js"]) {
|
|
3875
|
+
const mwPath = path13.join(currentDir, `middlewares${ext}`);
|
|
3876
|
+
const absMwPath = path13.resolve(rootDir, mwPath);
|
|
3877
|
+
if (fs12.existsSync(absMwPath)) {
|
|
3878
|
+
paths.push(absMwPath);
|
|
3879
|
+
break;
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
if (currentDir === resolvedRoot) break;
|
|
3884
|
+
const parentDir = path13.dirname(currentDir);
|
|
3885
|
+
if (parentDir === currentDir) break;
|
|
3886
|
+
currentDir = parentDir;
|
|
3887
|
+
}
|
|
3888
|
+
paths.reverse();
|
|
3889
|
+
return paths;
|
|
3890
|
+
}
|
|
3891
|
+
function toProdFilePath(filePath, dist) {
|
|
3892
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
3893
|
+
if (rel.startsWith("src/")) {
|
|
3894
|
+
rel = rel.slice(4);
|
|
3895
|
+
}
|
|
3896
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
3897
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
3898
|
+
}
|
|
3899
|
+
async function scanRoutes(rootDir, patterns, dist) {
|
|
3900
|
+
const files = await fg2(patterns, {
|
|
3901
|
+
cwd: rootDir,
|
|
3902
|
+
onlyFiles: true,
|
|
3903
|
+
absolute: false
|
|
3904
|
+
});
|
|
3905
|
+
const routes = [];
|
|
3906
|
+
const wsRoutes = [];
|
|
3907
|
+
for (const file of files) {
|
|
3908
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
3909
|
+
const fileName = normalizedFile.split("/").pop();
|
|
3910
|
+
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
3911
|
+
const absPath = path13.resolve(rootDir, normalizedFile);
|
|
3912
|
+
const urlPath = filePathToUrlPath(normalizedFile);
|
|
3913
|
+
const paramNames = extractParamNames(urlPath);
|
|
3914
|
+
const isDynamic = paramNames.length > 0;
|
|
3915
|
+
const isCatchAll = normalizedFile.split("/").some(isCatchAllSegment);
|
|
3916
|
+
let middlewarePaths;
|
|
3917
|
+
let middlewareBundle;
|
|
3918
|
+
if (dist) {
|
|
3919
|
+
middlewarePaths = collectMiddlewarePaths(normalizedFile, rootDir, dist);
|
|
3920
|
+
} else {
|
|
3921
|
+
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
3922
|
+
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
3923
|
+
}
|
|
3924
|
+
const source = await fs12.promises.readFile(absPath, "utf8").catch(() => "");
|
|
3925
|
+
const exportNames = extractExportsFromSource(source);
|
|
3926
|
+
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
3927
|
+
for (const method of methods) {
|
|
3928
|
+
routes.push({
|
|
3929
|
+
method,
|
|
3930
|
+
urlPath,
|
|
3931
|
+
filePath: normalizedFile,
|
|
3932
|
+
paramNames,
|
|
3933
|
+
isDynamic,
|
|
3934
|
+
isCatchAll: isCatchAll || void 0,
|
|
3935
|
+
middlewarePaths,
|
|
3936
|
+
middlewares: middlewareBundle?.middlewares,
|
|
3937
|
+
injectors: middlewareBundle?.injectors
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
if (exportNames.has("WS")) {
|
|
3941
|
+
wsRoutes.push({
|
|
3942
|
+
urlPath,
|
|
3943
|
+
filePath: normalizedFile,
|
|
3944
|
+
paramNames,
|
|
3945
|
+
isDynamic,
|
|
3946
|
+
isCatchAll: isCatchAll || void 0,
|
|
3947
|
+
middlewarePaths,
|
|
3948
|
+
middlewares: middlewareBundle?.middlewares,
|
|
3949
|
+
injectors: middlewareBundle?.injectors
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
return { routes, wsRoutes };
|
|
3956
|
+
}
|
|
3957
|
+
|
|
4163
3958
|
// src/cli/createDevApp.ts
|
|
4164
3959
|
init_createProgram();
|
|
4165
3960
|
async function createDevApp(options) {
|
|
@@ -4193,31 +3988,25 @@ async function createProdApp(options) {
|
|
|
4193
3988
|
export {
|
|
4194
3989
|
FaapiError,
|
|
4195
3990
|
InternalError,
|
|
4196
|
-
MessageQueue,
|
|
4197
3991
|
MethodNotAllowedError,
|
|
4198
3992
|
ModuleLoadError,
|
|
4199
3993
|
RouteNotFoundError,
|
|
4200
3994
|
SchemaExtractionError,
|
|
4201
3995
|
ValidationError,
|
|
4202
3996
|
collectRouteSchemaSources,
|
|
4203
|
-
connectWs,
|
|
4204
3997
|
cors,
|
|
4205
3998
|
createProdApp as createApp,
|
|
4206
|
-
createContext,
|
|
4207
3999
|
createDevApp,
|
|
4208
4000
|
createProdApp,
|
|
4209
4001
|
createProgram,
|
|
4210
|
-
createTestServer,
|
|
4211
4002
|
extractTypeInfo,
|
|
4212
4003
|
getApp,
|
|
4213
4004
|
getInputTypeForMethod,
|
|
4214
4005
|
helmet,
|
|
4215
4006
|
invalidateProgramCache,
|
|
4216
|
-
invokeHandler,
|
|
4217
4007
|
loadConfig,
|
|
4218
4008
|
loadEnv,
|
|
4219
4009
|
logger,
|
|
4220
|
-
resolveTypeNode
|
|
4221
|
-
waitForWsOpen
|
|
4010
|
+
resolveTypeNode
|
|
4222
4011
|
};
|
|
4223
4012
|
//# sourceMappingURL=index.js.map
|