@faapi/faapi 3.2.0 → 3.3.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 +657 -413
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +63 -6
- package/dist/index.js +549 -303
- package/dist/index.js.map +1 -1
- package/dist/testing.js +473 -239
- package/dist/testing.js.map +1 -1
- package/package.json +3 -3
package/dist/testing.js
CHANGED
|
@@ -129,14 +129,14 @@ var ValidationError = class extends FaapiError {
|
|
|
129
129
|
issues;
|
|
130
130
|
};
|
|
131
131
|
var RouteNotFoundError = class extends FaapiError {
|
|
132
|
-
constructor(
|
|
133
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
132
|
+
constructor(path12) {
|
|
133
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path12}`, 404);
|
|
134
134
|
this.name = "RouteNotFoundError";
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
137
|
var MethodNotAllowedError = class extends FaapiError {
|
|
138
|
-
constructor(method,
|
|
139
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
138
|
+
constructor(method, path12, allowedMethods) {
|
|
139
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path12}`, 405);
|
|
140
140
|
this.allowedMethods = allowedMethods;
|
|
141
141
|
this.name = "MethodNotAllowedError";
|
|
142
142
|
}
|
|
@@ -266,7 +266,9 @@ function formatSetCookie(name, value, options) {
|
|
|
266
266
|
return cookie;
|
|
267
267
|
}
|
|
268
268
|
function createContext(request, params, config = {}, ip = "") {
|
|
269
|
-
|
|
269
|
+
return createContextFromUrl(request, new URL(request.url), params, config, ip);
|
|
270
|
+
}
|
|
271
|
+
function createContextFromUrl(request, url, params, config = {}, ip = "") {
|
|
270
272
|
const meta = { headers: {}, setCookies: [] };
|
|
271
273
|
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
272
274
|
const cookiesObj = {};
|
|
@@ -370,8 +372,8 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
370
372
|
return ctx;
|
|
371
373
|
}
|
|
372
374
|
function createTestContext(options) {
|
|
373
|
-
const { method = "GET", path:
|
|
374
|
-
const url = new URL(`http://localhost${
|
|
375
|
+
const { method = "GET", path: path12, query, headers, params = {}, config = {}, ip = "" } = options;
|
|
376
|
+
const url = new URL(`http://localhost${path12}`);
|
|
375
377
|
if (query) {
|
|
376
378
|
for (const [key, value] of Object.entries(query)) {
|
|
377
379
|
if (Array.isArray(value)) {
|
|
@@ -756,7 +758,7 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
756
758
|
}
|
|
757
759
|
|
|
758
760
|
// src/testServer.ts
|
|
759
|
-
import
|
|
761
|
+
import path11 from "path";
|
|
760
762
|
import os from "os";
|
|
761
763
|
import fs9 from "fs/promises";
|
|
762
764
|
|
|
@@ -770,9 +772,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
|
770
772
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
771
773
|
|
|
772
774
|
// src/utils/normalizePath.ts
|
|
773
|
-
function normalizePath(
|
|
774
|
-
if (!
|
|
775
|
-
let result =
|
|
775
|
+
function normalizePath(path12) {
|
|
776
|
+
if (!path12) return "";
|
|
777
|
+
let result = path12.replace(/\\/g, "/");
|
|
776
778
|
result = result.replace(/\/+/g, "/");
|
|
777
779
|
result = result.replace(/\/+$/, "");
|
|
778
780
|
if (result && !result.startsWith("/")) {
|
|
@@ -1045,27 +1047,131 @@ function sortRoutes(routes) {
|
|
|
1045
1047
|
}
|
|
1046
1048
|
|
|
1047
1049
|
// src/cli/generateSchemaFiles.ts
|
|
1048
|
-
import
|
|
1049
|
-
import
|
|
1050
|
+
import path4 from "path";
|
|
1051
|
+
import fs3 from "fs/promises";
|
|
1050
1052
|
|
|
1051
1053
|
// src/ast/createProgram.ts
|
|
1052
1054
|
import ts2 from "typescript";
|
|
1055
|
+
import fs2 from "fs";
|
|
1056
|
+
import path2 from "path";
|
|
1053
1057
|
var programCache = /* @__PURE__ */ new Map();
|
|
1058
|
+
var tsConfigCache = /* @__PURE__ */ new Map();
|
|
1059
|
+
function findTsConfig(filePath) {
|
|
1060
|
+
let dir = path2.dirname(filePath);
|
|
1061
|
+
const root = path2.parse(dir).root;
|
|
1062
|
+
while (true) {
|
|
1063
|
+
const candidate = path2.join(dir, "tsconfig.json");
|
|
1064
|
+
if (fs2.existsSync(candidate)) {
|
|
1065
|
+
return candidate;
|
|
1066
|
+
}
|
|
1067
|
+
if (dir === root) return null;
|
|
1068
|
+
const parent = path2.dirname(dir);
|
|
1069
|
+
if (parent === dir) return null;
|
|
1070
|
+
dir = parent;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function parseTsConfig(tsconfigPath) {
|
|
1074
|
+
const cached = tsConfigCache.get(tsconfigPath);
|
|
1075
|
+
if (cached) return cached;
|
|
1076
|
+
const result = { fileNames: [] };
|
|
1077
|
+
try {
|
|
1078
|
+
const configFile = ts2.readConfigFile(tsconfigPath, (p) => fs2.readFileSync(p, "utf-8"));
|
|
1079
|
+
if (configFile.error) {
|
|
1080
|
+
tsConfigCache.set(tsconfigPath, result);
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
const config = configFile.config ?? {};
|
|
1084
|
+
const basePath = path2.dirname(tsconfigPath);
|
|
1085
|
+
const parsed = ts2.parseJsonConfigFileContent(
|
|
1086
|
+
config,
|
|
1087
|
+
ts2.sys,
|
|
1088
|
+
basePath,
|
|
1089
|
+
/* existingOptions */
|
|
1090
|
+
void 0,
|
|
1091
|
+
tsconfigPath
|
|
1092
|
+
);
|
|
1093
|
+
result.fileNames = parsed.fileNames;
|
|
1094
|
+
if (parsed.options.module !== void 0) {
|
|
1095
|
+
result.module = parsed.options.module;
|
|
1096
|
+
}
|
|
1097
|
+
if (parsed.options.moduleResolution !== void 0) {
|
|
1098
|
+
result.moduleResolution = parsed.options.moduleResolution;
|
|
1099
|
+
}
|
|
1100
|
+
} catch {
|
|
1101
|
+
}
|
|
1102
|
+
tsConfigCache.set(tsconfigPath, result);
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
1054
1105
|
function createProgram(filePath) {
|
|
1055
1106
|
const cached = programCache.get(filePath);
|
|
1056
1107
|
if (cached) {
|
|
1057
1108
|
return cached;
|
|
1058
1109
|
}
|
|
1059
|
-
const program =
|
|
1110
|
+
const program = buildProgram([filePath], findTsConfig(filePath));
|
|
1111
|
+
programCache.set(filePath, program);
|
|
1112
|
+
return program;
|
|
1113
|
+
}
|
|
1114
|
+
function createPrograms(filePaths) {
|
|
1115
|
+
const unique = [...new Set(filePaths)];
|
|
1116
|
+
const result = /* @__PURE__ */ new Map();
|
|
1117
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1118
|
+
const noTsconfigFiles = [];
|
|
1119
|
+
for (const filePath of unique) {
|
|
1120
|
+
const tsconfigPath = findTsConfig(filePath);
|
|
1121
|
+
if (!tsconfigPath) {
|
|
1122
|
+
noTsconfigFiles.push(filePath);
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
const group = groups.get(tsconfigPath);
|
|
1126
|
+
if (group) {
|
|
1127
|
+
group.files.push(filePath);
|
|
1128
|
+
} else {
|
|
1129
|
+
groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
for (const { tsconfigPath, files } of groups.values()) {
|
|
1133
|
+
const cacheKey = `shared::${tsconfigPath}::${[...files].sort().join("|")}`;
|
|
1134
|
+
let program = programCache.get(cacheKey);
|
|
1135
|
+
if (!program) {
|
|
1136
|
+
program = buildProgram(files, tsconfigPath);
|
|
1137
|
+
programCache.set(cacheKey, program);
|
|
1138
|
+
}
|
|
1139
|
+
for (const filePath of files) {
|
|
1140
|
+
result.set(filePath, program);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
for (const filePath of noTsconfigFiles) {
|
|
1144
|
+
result.set(filePath, createProgram(filePath));
|
|
1145
|
+
}
|
|
1146
|
+
return result;
|
|
1147
|
+
}
|
|
1148
|
+
function buildProgram(entryFiles, tsconfigPath) {
|
|
1149
|
+
const options = {
|
|
1060
1150
|
strict: true,
|
|
1061
1151
|
target: ts2.ScriptTarget.ES2022,
|
|
1062
1152
|
module: ts2.ModuleKind.NodeNext,
|
|
1063
1153
|
moduleResolution: ts2.ModuleResolutionKind.NodeNext,
|
|
1064
1154
|
skipLibCheck: true,
|
|
1065
1155
|
noEmit: true
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1156
|
+
};
|
|
1157
|
+
const rootNames = [...entryFiles];
|
|
1158
|
+
if (tsconfigPath) {
|
|
1159
|
+
const tsOptions = parseTsConfig(tsconfigPath);
|
|
1160
|
+
if (tsOptions.module !== void 0) {
|
|
1161
|
+
options.module = tsOptions.module;
|
|
1162
|
+
}
|
|
1163
|
+
if (tsOptions.moduleResolution !== void 0) {
|
|
1164
|
+
options.moduleResolution = tsOptions.moduleResolution;
|
|
1165
|
+
}
|
|
1166
|
+
if (tsOptions.fileNames.length > 0) {
|
|
1167
|
+
for (const fileName of tsOptions.fileNames) {
|
|
1168
|
+
if (!rootNames.includes(fileName)) {
|
|
1169
|
+
rootNames.push(fileName);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return ts2.createProgram(rootNames, options);
|
|
1069
1175
|
}
|
|
1070
1176
|
|
|
1071
1177
|
// src/ast/extractHandlerTypes.ts
|
|
@@ -1073,6 +1179,10 @@ import ts4 from "typescript";
|
|
|
1073
1179
|
|
|
1074
1180
|
// src/ast/resolveTypeNode.ts
|
|
1075
1181
|
import ts3 from "typescript";
|
|
1182
|
+
var currentProgram = null;
|
|
1183
|
+
function setProgramContext(program) {
|
|
1184
|
+
currentProgram = program;
|
|
1185
|
+
}
|
|
1076
1186
|
var SchemaExtractionError = class extends Error {
|
|
1077
1187
|
constructor(typeText, reason, options) {
|
|
1078
1188
|
super(`\u65E0\u6CD5\u89E3\u6790\u7C7B\u578B "${typeText}": ${reason}`, options);
|
|
@@ -1395,11 +1505,69 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
1395
1505
|
if (ts3.isEnumDeclaration(declaration)) {
|
|
1396
1506
|
return resolveEnumDeclaration(declaration);
|
|
1397
1507
|
}
|
|
1508
|
+
if (ts3.isImportSpecifier(declaration) || ts3.isImportClause(declaration)) {
|
|
1509
|
+
const resolved = resolveImportAlias(typeNode, symbol, checker, visited);
|
|
1510
|
+
if (resolved) return resolved;
|
|
1511
|
+
}
|
|
1398
1512
|
}
|
|
1399
1513
|
}
|
|
1400
1514
|
}
|
|
1401
1515
|
throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
|
|
1402
1516
|
}
|
|
1517
|
+
function resolveImportAlias(typeNode, symbol, checker, visited) {
|
|
1518
|
+
const typeName = typeNode.typeName.getText();
|
|
1519
|
+
try {
|
|
1520
|
+
const aliased = checker.getAliasedSymbol(symbol);
|
|
1521
|
+
if (aliased && aliased.declarations && aliased.declarations.length > 0) {
|
|
1522
|
+
const decl = aliased.declarations[0];
|
|
1523
|
+
if (ts3.isInterfaceDeclaration(decl)) {
|
|
1524
|
+
return resolveInterfaceDeclaration(decl, checker, visited);
|
|
1525
|
+
}
|
|
1526
|
+
if (ts3.isTypeAliasDeclaration(decl)) {
|
|
1527
|
+
return resolveTypeNode(decl.type, checker, visited);
|
|
1528
|
+
}
|
|
1529
|
+
if (ts3.isEnumDeclaration(decl)) {
|
|
1530
|
+
return resolveEnumDeclaration(decl);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
const program = currentProgram;
|
|
1536
|
+
if (!program) return null;
|
|
1537
|
+
const allSFs = program.getSourceFiles();
|
|
1538
|
+
for (const sourceFile of allSFs) {
|
|
1539
|
+
if (sourceFile.fileName.includes("/node_modules/") || sourceFile.fileName.includes("typescript/lib/")) {
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
const found = findTopLevelDecl(sourceFile, typeName);
|
|
1543
|
+
if (found) {
|
|
1544
|
+
if (found.kind === "interface") {
|
|
1545
|
+
return resolveInterfaceDeclaration(found.node, checker, visited);
|
|
1546
|
+
}
|
|
1547
|
+
if (found.kind === "typeAlias") {
|
|
1548
|
+
return resolveTypeNode(found.node.type, checker, visited);
|
|
1549
|
+
}
|
|
1550
|
+
if (found.kind === "enum") {
|
|
1551
|
+
return resolveEnumDeclaration(found.node);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
function findTopLevelDecl(sourceFile, typeName) {
|
|
1558
|
+
let found = null;
|
|
1559
|
+
ts3.forEachChild(sourceFile, (node) => {
|
|
1560
|
+
if (found) return;
|
|
1561
|
+
if (ts3.isInterfaceDeclaration(node) && node.name.text === typeName) {
|
|
1562
|
+
found = { kind: "interface", node };
|
|
1563
|
+
} else if (ts3.isTypeAliasDeclaration(node) && node.name.text === typeName) {
|
|
1564
|
+
found = { kind: "typeAlias", node };
|
|
1565
|
+
} else if (ts3.isEnumDeclaration(node) && node.name.text === typeName) {
|
|
1566
|
+
found = { kind: "enum", node };
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
return found;
|
|
1570
|
+
}
|
|
1403
1571
|
function resolveEnumDeclaration(node) {
|
|
1404
1572
|
const members = [];
|
|
1405
1573
|
let nextNumericValue = 0;
|
|
@@ -1604,80 +1772,90 @@ function extractTypeInfo(program, filePath, typeName) {
|
|
|
1604
1772
|
const sourceFile = program.getSourceFile(filePath);
|
|
1605
1773
|
if (!sourceFile) return null;
|
|
1606
1774
|
const checker = program.getTypeChecker();
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1775
|
+
setProgramContext(program);
|
|
1776
|
+
try {
|
|
1777
|
+
let result = null;
|
|
1778
|
+
ts4.forEachChild(sourceFile, (node) => {
|
|
1779
|
+
if (result) return;
|
|
1780
|
+
if (ts4.isInterfaceDeclaration(node) && node.name.text === typeName) {
|
|
1781
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1782
|
+
visited.add(typeName);
|
|
1783
|
+
const runtimeType = withFileContext(
|
|
1784
|
+
filePath,
|
|
1785
|
+
typeName,
|
|
1786
|
+
() => resolveInterfaceDeclaration(node, checker, visited)
|
|
1787
|
+
);
|
|
1788
|
+
result = {
|
|
1789
|
+
name: typeName,
|
|
1790
|
+
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
1791
|
+
runtimeType
|
|
1792
|
+
};
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
if (ts4.isTypeAliasDeclaration(node) && node.name.text === typeName) {
|
|
1796
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1797
|
+
visited.add(typeName);
|
|
1798
|
+
const runtimeType = withFileContext(
|
|
1799
|
+
filePath,
|
|
1800
|
+
typeName,
|
|
1801
|
+
() => resolveTypeNode(node.type, checker, visited)
|
|
1802
|
+
);
|
|
1803
|
+
result = {
|
|
1804
|
+
name: typeName,
|
|
1805
|
+
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
1806
|
+
runtimeType
|
|
1807
|
+
};
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
return result;
|
|
1812
|
+
} finally {
|
|
1813
|
+
setProgramContext(null);
|
|
1814
|
+
}
|
|
1642
1815
|
}
|
|
1643
1816
|
function extractAllTypes(program, filePath) {
|
|
1644
1817
|
const sourceFile = program.getSourceFile(filePath);
|
|
1645
1818
|
if (!sourceFile) return /* @__PURE__ */ new Map();
|
|
1646
1819
|
const checker = program.getTypeChecker();
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1820
|
+
setProgramContext(program);
|
|
1821
|
+
try {
|
|
1822
|
+
const result = /* @__PURE__ */ new Map();
|
|
1823
|
+
ts4.forEachChild(sourceFile, (node) => {
|
|
1824
|
+
if (ts4.isInterfaceDeclaration(node)) {
|
|
1825
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1826
|
+
visited.add(node.name.text);
|
|
1827
|
+
const runtimeType = withFileContext(
|
|
1828
|
+
filePath,
|
|
1829
|
+
node.name.text,
|
|
1830
|
+
() => resolveInterfaceDeclaration(node, checker, visited)
|
|
1831
|
+
);
|
|
1832
|
+
result.set(node.name.text, {
|
|
1833
|
+
name: node.name.text,
|
|
1834
|
+
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
1835
|
+
runtimeType
|
|
1836
|
+
});
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (ts4.isTypeAliasDeclaration(node)) {
|
|
1840
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1841
|
+
visited.add(node.name.text);
|
|
1842
|
+
const runtimeType = withFileContext(
|
|
1843
|
+
filePath,
|
|
1844
|
+
node.name.text,
|
|
1845
|
+
() => resolveTypeNode(node.type, checker, visited)
|
|
1846
|
+
);
|
|
1847
|
+
result.set(node.name.text, {
|
|
1848
|
+
name: node.name.text,
|
|
1849
|
+
properties: runtimeType.kind === "object" ? runtimeType.properties : [],
|
|
1850
|
+
runtimeType
|
|
1851
|
+
});
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
return result;
|
|
1856
|
+
} finally {
|
|
1857
|
+
setProgramContext(null);
|
|
1858
|
+
}
|
|
1681
1859
|
}
|
|
1682
1860
|
function withFileContext(filePath, typeName, fn) {
|
|
1683
1861
|
try {
|
|
@@ -1760,11 +1938,11 @@ function extractSchema(typeNode, sourceFile) {
|
|
|
1760
1938
|
}
|
|
1761
1939
|
|
|
1762
1940
|
// src/cli/collectRouteSchemaSources.ts
|
|
1763
|
-
import
|
|
1941
|
+
import path3 from "path";
|
|
1764
1942
|
function collectRouteSchemaSources(routes, rootDir) {
|
|
1765
1943
|
const methodsByFile = /* @__PURE__ */ new Map();
|
|
1766
1944
|
for (const route of routes) {
|
|
1767
|
-
const filePath = rootDir ?
|
|
1945
|
+
const filePath = rootDir ? path3.resolve(rootDir, route.filePath) : route.filePath;
|
|
1768
1946
|
let entry = methodsByFile.get(filePath);
|
|
1769
1947
|
if (!entry) {
|
|
1770
1948
|
entry = { urlPath: route.urlPath, methods: /* @__PURE__ */ new Set() };
|
|
@@ -1772,12 +1950,11 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1772
1950
|
}
|
|
1773
1951
|
entry.methods.add(route.method);
|
|
1774
1952
|
}
|
|
1775
|
-
const programByFile =
|
|
1953
|
+
const programByFile = createPrograms([...methodsByFile.keys()]);
|
|
1776
1954
|
const allTypesByFile = /* @__PURE__ */ new Map();
|
|
1777
1955
|
const mergedAllTypes = /* @__PURE__ */ new Map();
|
|
1778
1956
|
for (const filePath of methodsByFile.keys()) {
|
|
1779
|
-
const program =
|
|
1780
|
-
programByFile.set(filePath, program);
|
|
1957
|
+
const program = programByFile.get(filePath);
|
|
1781
1958
|
const allTypes = extractAllTypes(program, filePath);
|
|
1782
1959
|
allTypesByFile.set(filePath, allTypes);
|
|
1783
1960
|
for (const [name, info] of allTypes) {
|
|
@@ -2115,7 +2292,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
2115
2292
|
}
|
|
2116
2293
|
const idx = rel.lastIndexOf("/");
|
|
2117
2294
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2118
|
-
return
|
|
2295
|
+
return path4.resolve(rootDir, dist, relDir, "zod.js");
|
|
2119
2296
|
}
|
|
2120
2297
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
2121
2298
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -2126,7 +2303,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
2126
2303
|
}
|
|
2127
2304
|
const idx = rel.lastIndexOf("/");
|
|
2128
2305
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
2129
|
-
return
|
|
2306
|
+
return path4.resolve(rootDir, dist, relDir, "zod.js");
|
|
2130
2307
|
}
|
|
2131
2308
|
function getHelpersImportPath(relDir) {
|
|
2132
2309
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -2176,7 +2353,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2176
2353
|
}
|
|
2177
2354
|
const fileEntries = [];
|
|
2178
2355
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2179
|
-
const relFile =
|
|
2356
|
+
const relFile = path4.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2180
2357
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
2181
2358
|
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
2182
2359
|
let relForDir = relFile;
|
|
@@ -2191,7 +2368,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2191
2368
|
}
|
|
2192
2369
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2193
2370
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
2194
|
-
const helpersPath =
|
|
2371
|
+
const helpersPath = path4.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
2195
2372
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
2196
2373
|
}
|
|
2197
2374
|
await Promise.all(
|
|
@@ -2199,22 +2376,22 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
2199
2376
|
);
|
|
2200
2377
|
}
|
|
2201
2378
|
async function writeSchemaFile(outputPath, source) {
|
|
2202
|
-
await
|
|
2203
|
-
await
|
|
2379
|
+
await fs3.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
2380
|
+
await fs3.writeFile(outputPath, source, "utf-8");
|
|
2204
2381
|
}
|
|
2205
2382
|
|
|
2206
2383
|
// src/cli/compileOnDemand.ts
|
|
2207
|
-
import
|
|
2208
|
-
import
|
|
2384
|
+
import path8 from "path";
|
|
2385
|
+
import fs7 from "fs";
|
|
2209
2386
|
|
|
2210
2387
|
// src/cli/compileDevRoutes.ts
|
|
2211
|
-
import
|
|
2212
|
-
import
|
|
2388
|
+
import path7 from "path";
|
|
2389
|
+
import fs6 from "fs";
|
|
2213
2390
|
import fg2 from "fast-glob";
|
|
2214
2391
|
|
|
2215
2392
|
// src/cli/aliasPlugin.ts
|
|
2216
|
-
import
|
|
2217
|
-
import
|
|
2393
|
+
import path6 from "path";
|
|
2394
|
+
import fs5 from "fs";
|
|
2218
2395
|
|
|
2219
2396
|
// src/utils/resolveAlias.ts
|
|
2220
2397
|
function resolveAlias(specifier, config) {
|
|
@@ -2241,11 +2418,11 @@ function resolveAlias(specifier, config) {
|
|
|
2241
2418
|
|
|
2242
2419
|
// src/utils/readTsconfig.ts
|
|
2243
2420
|
import ts6 from "typescript";
|
|
2244
|
-
import
|
|
2245
|
-
import
|
|
2421
|
+
import path5 from "path";
|
|
2422
|
+
import fs4 from "fs";
|
|
2246
2423
|
function readTsconfig(rootDir) {
|
|
2247
|
-
const tsconfigPath =
|
|
2248
|
-
if (!
|
|
2424
|
+
const tsconfigPath = path5.resolve(rootDir, "tsconfig.json");
|
|
2425
|
+
if (!fs4.existsSync(tsconfigPath)) return null;
|
|
2249
2426
|
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
2250
2427
|
if (configFile.error || !configFile.config) return null;
|
|
2251
2428
|
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
@@ -2254,7 +2431,7 @@ function readTsconfig(rootDir) {
|
|
|
2254
2431
|
if (!rawPaths) return null;
|
|
2255
2432
|
const paths = {};
|
|
2256
2433
|
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
2257
|
-
paths[pattern] = targets.map((t) =>
|
|
2434
|
+
paths[pattern] = targets.map((t) => path5.resolve(baseUrl, t));
|
|
2258
2435
|
}
|
|
2259
2436
|
return { baseUrl, paths };
|
|
2260
2437
|
}
|
|
@@ -2267,29 +2444,29 @@ function toProdExtension(filePath) {
|
|
|
2267
2444
|
return filePath;
|
|
2268
2445
|
}
|
|
2269
2446
|
function toProdImportPath(sourceFile, importer) {
|
|
2270
|
-
const importerDir =
|
|
2271
|
-
let rel =
|
|
2272
|
-
rel = rel.split(
|
|
2447
|
+
const importerDir = path6.dirname(importer);
|
|
2448
|
+
let rel = path6.relative(importerDir, sourceFile);
|
|
2449
|
+
rel = rel.split(path6.sep).join("/");
|
|
2273
2450
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2274
2451
|
return toProdExtension(rel);
|
|
2275
2452
|
}
|
|
2276
2453
|
function toRealPath(p) {
|
|
2277
2454
|
try {
|
|
2278
|
-
return
|
|
2455
|
+
return fs5.realpathSync(p);
|
|
2279
2456
|
} catch {
|
|
2280
2457
|
return p;
|
|
2281
2458
|
}
|
|
2282
2459
|
}
|
|
2283
2460
|
function isInsideDir(filePath, dir) {
|
|
2284
|
-
const rel =
|
|
2285
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
2461
|
+
const rel = path6.relative(dir, filePath);
|
|
2462
|
+
return rel !== "" && !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
2286
2463
|
}
|
|
2287
2464
|
var APP_DIR = "src";
|
|
2288
2465
|
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
2289
|
-
const appDirAbs = toRealPath(
|
|
2466
|
+
const appDirAbs = toRealPath(path6.resolve(rootDir, APP_DIR));
|
|
2290
2467
|
const sourceReal = toRealPath(sourceFile);
|
|
2291
|
-
let rel =
|
|
2292
|
-
rel = rel.split(
|
|
2468
|
+
let rel = path6.relative(appDirAbs, sourceReal);
|
|
2469
|
+
rel = rel.split(path6.sep).join("/");
|
|
2293
2470
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
2294
2471
|
return toProdExtension(rel);
|
|
2295
2472
|
}
|
|
@@ -2304,34 +2481,34 @@ var INDEX_EXTS = [
|
|
|
2304
2481
|
"/index.cjs"
|
|
2305
2482
|
];
|
|
2306
2483
|
function resolveRelativeSpecifier(importer, specifier) {
|
|
2307
|
-
const importerDir =
|
|
2308
|
-
const base =
|
|
2484
|
+
const importerDir = path6.dirname(importer);
|
|
2485
|
+
const base = path6.resolve(importerDir, specifier);
|
|
2309
2486
|
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
2310
|
-
return
|
|
2487
|
+
return fs5.existsSync(base) ? base : null;
|
|
2311
2488
|
}
|
|
2312
2489
|
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
2313
|
-
return
|
|
2490
|
+
return fs5.existsSync(base) ? base : null;
|
|
2314
2491
|
}
|
|
2315
2492
|
for (const ext of SOURCE_EXTS) {
|
|
2316
2493
|
const file = base + ext;
|
|
2317
|
-
if (
|
|
2494
|
+
if (fs5.existsSync(file)) return file;
|
|
2318
2495
|
}
|
|
2319
2496
|
for (const indexExt of INDEX_EXTS) {
|
|
2320
2497
|
const file = base + indexExt;
|
|
2321
|
-
if (
|
|
2498
|
+
if (fs5.existsSync(file)) return file;
|
|
2322
2499
|
}
|
|
2323
2500
|
return null;
|
|
2324
2501
|
}
|
|
2325
2502
|
function createAliasPlugin(config, options) {
|
|
2326
2503
|
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
2327
|
-
const appDirAbs = options?.rootDir ? toRealPath(
|
|
2504
|
+
const appDirAbs = options?.rootDir ? toRealPath(path6.resolve(options.rootDir, APP_DIR)) : null;
|
|
2328
2505
|
return {
|
|
2329
2506
|
name: "faapi-alias",
|
|
2330
2507
|
setup(build) {
|
|
2331
2508
|
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
2332
2509
|
let source;
|
|
2333
2510
|
try {
|
|
2334
|
-
source =
|
|
2511
|
+
source = fs5.readFileSync(args.path, "utf8");
|
|
2335
2512
|
} catch {
|
|
2336
2513
|
return void 0;
|
|
2337
2514
|
}
|
|
@@ -2364,7 +2541,7 @@ function createAliasPlugin(config, options) {
|
|
|
2364
2541
|
for (const candidate of candidates) {
|
|
2365
2542
|
for (const ext of SOURCE_EXTS) {
|
|
2366
2543
|
const file = candidate + ext;
|
|
2367
|
-
if (
|
|
2544
|
+
if (fs5.existsSync(file)) {
|
|
2368
2545
|
modified = true;
|
|
2369
2546
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2370
2547
|
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
@@ -2377,7 +2554,7 @@ function createAliasPlugin(config, options) {
|
|
|
2377
2554
|
}
|
|
2378
2555
|
for (const indexExt of INDEX_EXTS) {
|
|
2379
2556
|
const file = candidate + indexExt;
|
|
2380
|
-
if (
|
|
2557
|
+
if (fs5.existsSync(file)) {
|
|
2381
2558
|
modified = true;
|
|
2382
2559
|
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
2383
2560
|
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
@@ -2415,11 +2592,11 @@ async function compileDevRoutes(options) {
|
|
|
2415
2592
|
if (entryPoints.length === 0) {
|
|
2416
2593
|
return { compiledFiles: [] };
|
|
2417
2594
|
}
|
|
2418
|
-
const absDist =
|
|
2419
|
-
await
|
|
2595
|
+
const absDist = path7.resolve(rootDir, dist);
|
|
2596
|
+
await fs6.promises.mkdir(absDist, { recursive: true });
|
|
2420
2597
|
const plugins = buildAliasPlugins(rootDir);
|
|
2421
2598
|
const esbuild = await import("esbuild");
|
|
2422
|
-
const outbase =
|
|
2599
|
+
const outbase = path7.resolve(rootDir, APP_DIR2);
|
|
2423
2600
|
const result = await esbuild.build({
|
|
2424
2601
|
entryPoints,
|
|
2425
2602
|
outdir: absDist,
|
|
@@ -2436,10 +2613,10 @@ async function compileDevRoutes(options) {
|
|
|
2436
2613
|
if (result.outputFiles) {
|
|
2437
2614
|
await Promise.all(
|
|
2438
2615
|
result.outputFiles.map(async (file) => {
|
|
2439
|
-
await
|
|
2616
|
+
await fs6.promises.mkdir(path7.dirname(file.path), { recursive: true });
|
|
2440
2617
|
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2441
|
-
await
|
|
2442
|
-
await
|
|
2618
|
+
await fs6.promises.writeFile(tmp, file.contents);
|
|
2619
|
+
await fs6.promises.rename(tmp, file.path);
|
|
2443
2620
|
})
|
|
2444
2621
|
);
|
|
2445
2622
|
}
|
|
@@ -2449,8 +2626,8 @@ async function compileDevRoutes(options) {
|
|
|
2449
2626
|
// src/cli/compileOnDemand.ts
|
|
2450
2627
|
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
2451
2628
|
try {
|
|
2452
|
-
const srcStat =
|
|
2453
|
-
const prodStat =
|
|
2629
|
+
const srcStat = fs7.statSync(sourceAbsPath);
|
|
2630
|
+
const prodStat = fs7.statSync(productAbsPath);
|
|
2454
2631
|
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2455
2632
|
} catch {
|
|
2456
2633
|
return false;
|
|
@@ -2477,7 +2654,7 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
2477
2654
|
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
2478
2655
|
return false;
|
|
2479
2656
|
}
|
|
2480
|
-
if (!
|
|
2657
|
+
if (!fs7.existsSync(sourceAbsPath)) {
|
|
2481
2658
|
return false;
|
|
2482
2659
|
}
|
|
2483
2660
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
@@ -2503,11 +2680,11 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
2503
2680
|
}
|
|
2504
2681
|
}
|
|
2505
2682
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
2506
|
-
const rel =
|
|
2683
|
+
const rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2507
2684
|
if (!rel.startsWith("src/")) return null;
|
|
2508
2685
|
const relWithoutSrc = rel.slice(4);
|
|
2509
2686
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
2510
|
-
return
|
|
2687
|
+
return path8.resolve(rootDir, dist, jsRel);
|
|
2511
2688
|
}
|
|
2512
2689
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
2513
2690
|
const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
|
|
@@ -2519,9 +2696,9 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2519
2696
|
if (state.generatedSchemas.has(schemaPath)) {
|
|
2520
2697
|
return false;
|
|
2521
2698
|
}
|
|
2522
|
-
const prodAbsPath =
|
|
2699
|
+
const prodAbsPath = path8.resolve(rootDir, routeFilePath);
|
|
2523
2700
|
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
2524
|
-
if (!
|
|
2701
|
+
if (!fs7.existsSync(sourceAbsPath)) {
|
|
2525
2702
|
return false;
|
|
2526
2703
|
}
|
|
2527
2704
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
@@ -2532,7 +2709,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2532
2709
|
if (fileRoutes.length === 0) {
|
|
2533
2710
|
return false;
|
|
2534
2711
|
}
|
|
2535
|
-
const sourceRelPath =
|
|
2712
|
+
const sourceRelPath = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
2536
2713
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2537
2714
|
const generatePromise = (async () => {
|
|
2538
2715
|
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
@@ -2546,17 +2723,26 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
2546
2723
|
state.inFlightSchemaGenerations.delete(schemaPath);
|
|
2547
2724
|
}
|
|
2548
2725
|
}
|
|
2726
|
+
var sourcePathCache = /* @__PURE__ */ new Map();
|
|
2549
2727
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2550
|
-
const
|
|
2728
|
+
const cached = sourcePathCache.get(prodAbsPath);
|
|
2729
|
+
if (cached) return cached;
|
|
2730
|
+
const rel = path8.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
2551
2731
|
let relWithoutDist = rel;
|
|
2552
2732
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
2553
2733
|
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
2554
2734
|
}
|
|
2555
2735
|
const srcRel = `src/${relWithoutDist}`;
|
|
2556
2736
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2557
|
-
const tsAbs =
|
|
2558
|
-
|
|
2559
|
-
|
|
2737
|
+
const tsAbs = path8.resolve(rootDir, tsRel);
|
|
2738
|
+
let result;
|
|
2739
|
+
if (fs7.existsSync(tsAbs)) {
|
|
2740
|
+
result = tsAbs;
|
|
2741
|
+
} else {
|
|
2742
|
+
result = path8.resolve(rootDir, srcRel);
|
|
2743
|
+
}
|
|
2744
|
+
sourcePathCache.set(prodAbsPath, result);
|
|
2745
|
+
return result;
|
|
2560
2746
|
}
|
|
2561
2747
|
function isDevOnDemandEnabled() {
|
|
2562
2748
|
return state.enabled;
|
|
@@ -2609,9 +2795,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
2609
2795
|
function mapZodIssues(error) {
|
|
2610
2796
|
return error.issues.map((issue) => {
|
|
2611
2797
|
const code = mapZodCode(issue.code, issue.message);
|
|
2612
|
-
const
|
|
2798
|
+
const path12 = issue.path.map(String).join(".") || "";
|
|
2613
2799
|
return {
|
|
2614
|
-
path:
|
|
2800
|
+
path: path12,
|
|
2615
2801
|
code,
|
|
2616
2802
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2617
2803
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -2659,45 +2845,96 @@ import {
|
|
|
2659
2845
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2660
2846
|
import { readFileSync } from "fs";
|
|
2661
2847
|
import { Readable as Readable2 } from "stream";
|
|
2662
|
-
import
|
|
2848
|
+
import path10 from "path";
|
|
2663
2849
|
|
|
2664
2850
|
// src/router/matchRoute.ts
|
|
2665
|
-
|
|
2851
|
+
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
2852
|
+
var wsIndexCache = /* @__PURE__ */ new WeakMap();
|
|
2853
|
+
function getHttpIndex(routes) {
|
|
2854
|
+
let index = httpIndexCache.get(routes);
|
|
2855
|
+
if (index) return index;
|
|
2856
|
+
index = { static: /* @__PURE__ */ new Map(), methodsByStaticPath: /* @__PURE__ */ new Map(), dynamics: [] };
|
|
2666
2857
|
for (const route of routes) {
|
|
2667
|
-
if (route.
|
|
2668
|
-
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2858
|
+
if (route.isDynamic) {
|
|
2859
|
+
index.dynamics.push(route);
|
|
2860
|
+
} else {
|
|
2861
|
+
index.static.set(`${route.method}|${route.urlPath}`, route);
|
|
2862
|
+
let methods = index.methodsByStaticPath.get(route.urlPath);
|
|
2863
|
+
if (!methods) {
|
|
2864
|
+
methods = /* @__PURE__ */ new Set();
|
|
2865
|
+
index.methodsByStaticPath.set(route.urlPath, methods);
|
|
2673
2866
|
}
|
|
2867
|
+
methods.add(route.method);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
httpIndexCache.set(routes, index);
|
|
2871
|
+
return index;
|
|
2872
|
+
}
|
|
2873
|
+
function getWsIndex(routes) {
|
|
2874
|
+
let index = wsIndexCache.get(routes);
|
|
2875
|
+
if (index) return index;
|
|
2876
|
+
index = { static: /* @__PURE__ */ new Map(), dynamics: [] };
|
|
2877
|
+
for (const route of routes) {
|
|
2878
|
+
if (route.isDynamic) {
|
|
2879
|
+
index.dynamics.push(route);
|
|
2880
|
+
} else {
|
|
2881
|
+
index.static.set(route.urlPath, route);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
wsIndexCache.set(routes, index);
|
|
2885
|
+
return index;
|
|
2886
|
+
}
|
|
2887
|
+
function matchRoute(routes, method, path12) {
|
|
2888
|
+
const index = getHttpIndex(routes);
|
|
2889
|
+
const staticHit = index.static.get(`${method}|${path12}`);
|
|
2890
|
+
if (staticHit) {
|
|
2891
|
+
return { route: staticHit, params: {} };
|
|
2892
|
+
}
|
|
2893
|
+
for (const route of index.dynamics) {
|
|
2894
|
+
if (route.method !== method) {
|
|
2674
2895
|
continue;
|
|
2675
2896
|
}
|
|
2676
|
-
const params = matchDynamicPath(route.urlPath,
|
|
2897
|
+
const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
|
|
2677
2898
|
if (params !== null) {
|
|
2678
2899
|
return { route, params };
|
|
2679
2900
|
}
|
|
2680
2901
|
}
|
|
2681
2902
|
return null;
|
|
2682
2903
|
}
|
|
2683
|
-
function matchWsRoute(wsRoutes,
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
const params = matchDynamicPath(route.urlPath, path11, route.paramNames, route.isCatchAll);
|
|
2904
|
+
function matchWsRoute(wsRoutes, path12) {
|
|
2905
|
+
const index = getWsIndex(wsRoutes);
|
|
2906
|
+
const staticHit = index.static.get(path12);
|
|
2907
|
+
if (staticHit) {
|
|
2908
|
+
return { route: staticHit, params: {} };
|
|
2909
|
+
}
|
|
2910
|
+
for (const route of index.dynamics) {
|
|
2911
|
+
const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
|
|
2692
2912
|
if (params !== null) {
|
|
2693
2913
|
return { route, params };
|
|
2694
2914
|
}
|
|
2695
2915
|
}
|
|
2696
2916
|
return null;
|
|
2697
2917
|
}
|
|
2698
|
-
function
|
|
2918
|
+
function findAllowedMethods(routes, path12) {
|
|
2919
|
+
const index = getHttpIndex(routes);
|
|
2920
|
+
const methods = /* @__PURE__ */ new Set();
|
|
2921
|
+
const staticMethods = index.methodsByStaticPath.get(path12);
|
|
2922
|
+
if (staticMethods) {
|
|
2923
|
+
for (const method of staticMethods) {
|
|
2924
|
+
methods.add(method);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
for (const route of index.dynamics) {
|
|
2928
|
+
const params = matchDynamicPath(route.urlPath, path12, route.paramNames, route.isCatchAll);
|
|
2929
|
+
if (params !== null) {
|
|
2930
|
+
methods.add(route.method);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return Array.from(methods);
|
|
2934
|
+
}
|
|
2935
|
+
function matchDynamicPath(pattern, path12, paramNames, isCatchAll) {
|
|
2699
2936
|
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2700
|
-
const pathSegments =
|
|
2937
|
+
const pathSegments = path12.split("/").filter(Boolean);
|
|
2701
2938
|
if (isCatchAll) {
|
|
2702
2939
|
const nonCatchAllCount = patternSegments.length - 1;
|
|
2703
2940
|
if (pathSegments.length <= nonCatchAllCount) {
|
|
@@ -2742,9 +2979,6 @@ function matchDynamicPath(pattern, path11, paramNames, isCatchAll) {
|
|
|
2742
2979
|
return params;
|
|
2743
2980
|
}
|
|
2744
2981
|
|
|
2745
|
-
// src/loader/loadRouteModule.ts
|
|
2746
|
-
import fs7 from "fs";
|
|
2747
|
-
|
|
2748
2982
|
// src/loader/resolveExports.ts
|
|
2749
2983
|
function resolveExport(module, exportName) {
|
|
2750
2984
|
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
@@ -2775,7 +3009,7 @@ async function loadRouteModule(filePath, method, rootDir) {
|
|
|
2775
3009
|
const dist = getDevDist();
|
|
2776
3010
|
if (dist) {
|
|
2777
3011
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2778
|
-
if (sourcePath
|
|
3012
|
+
if (sourcePath) {
|
|
2779
3013
|
try {
|
|
2780
3014
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2781
3015
|
} catch (compileErr) {
|
|
@@ -2840,7 +3074,7 @@ async function parseMultipart(request) {
|
|
|
2840
3074
|
}
|
|
2841
3075
|
|
|
2842
3076
|
// src/runtime/resolveInput.ts
|
|
2843
|
-
async function
|
|
3077
|
+
async function resolveInputFromUrl(method, request, url) {
|
|
2844
3078
|
const inputType = getInputTypeForMethod(method);
|
|
2845
3079
|
if (inputType === "body") {
|
|
2846
3080
|
const contentType = request.headers.get("content-type") ?? "";
|
|
@@ -2875,7 +3109,6 @@ async function resolveInput(method, request) {
|
|
|
2875
3109
|
}
|
|
2876
3110
|
return result.data;
|
|
2877
3111
|
}
|
|
2878
|
-
const url = new URL(request.url);
|
|
2879
3112
|
return queryToObject(url.searchParams);
|
|
2880
3113
|
}
|
|
2881
3114
|
|
|
@@ -2904,11 +3137,13 @@ async function sendNodeResponse(response, res) {
|
|
|
2904
3137
|
}
|
|
2905
3138
|
|
|
2906
3139
|
// src/utils/getClientIp.ts
|
|
2907
|
-
function getClientIp(req) {
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
3140
|
+
function getClientIp(req, trustedProxy = false) {
|
|
3141
|
+
if (trustedProxy) {
|
|
3142
|
+
const xff = req.headers["x-forwarded-for"];
|
|
3143
|
+
if (typeof xff === "string" && xff.length > 0) {
|
|
3144
|
+
const first = xff.split(",")[0]?.trim();
|
|
3145
|
+
if (first) return first;
|
|
3146
|
+
}
|
|
2912
3147
|
}
|
|
2913
3148
|
const remote = req.socket?.remoteAddress;
|
|
2914
3149
|
if (remote) {
|
|
@@ -3083,7 +3318,7 @@ function logger(options = {}) {
|
|
|
3083
3318
|
// src/server/handleWsUpgrade.ts
|
|
3084
3319
|
import fs8 from "fs";
|
|
3085
3320
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3086
|
-
import
|
|
3321
|
+
import path9 from "path";
|
|
3087
3322
|
|
|
3088
3323
|
// src/server/serverUtils.ts
|
|
3089
3324
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -3197,7 +3432,7 @@ async function sendResponseToSocket(socket, response) {
|
|
|
3197
3432
|
socket.destroy();
|
|
3198
3433
|
}
|
|
3199
3434
|
function attachWebSocket(options) {
|
|
3200
|
-
const { server, routesRef, rootDir, config, globalMiddlewares } = options;
|
|
3435
|
+
const { server, routesRef, rootDir, config, globalMiddlewares, trustedProxy = false } = options;
|
|
3201
3436
|
const wss = new WebSocketServer({ noServer: true });
|
|
3202
3437
|
server.on("upgrade", async (req, socket, head) => {
|
|
3203
3438
|
const currentWsRoutes = routesRef.wsCurrent;
|
|
@@ -3213,13 +3448,13 @@ function attachWebSocket(options) {
|
|
|
3213
3448
|
const host = req.headers.host ?? "localhost";
|
|
3214
3449
|
const url = `http://${host}${req.url ?? "/"}`;
|
|
3215
3450
|
const request = new Request(url, { method: "GET", headers });
|
|
3216
|
-
const ctx = createContext(request, params, config, getClientIp(req));
|
|
3451
|
+
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
|
|
3217
3452
|
const meta = ctx.meta;
|
|
3218
3453
|
let upgraded = false;
|
|
3219
3454
|
const finalHandler = async () => {
|
|
3220
3455
|
let handlers;
|
|
3221
3456
|
try {
|
|
3222
|
-
const absoluteFilePath =
|
|
3457
|
+
const absoluteFilePath = path9.resolve(rootDir, route.filePath);
|
|
3223
3458
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
3224
3459
|
} catch (err) {
|
|
3225
3460
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -3282,16 +3517,26 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
3282
3517
|
const headers = nodeHttpToWebHeaders(req);
|
|
3283
3518
|
const method = req.method ?? "GET";
|
|
3284
3519
|
if (method === "GET" || method === "HEAD") {
|
|
3285
|
-
return new Request(url.toString(), { method, headers });
|
|
3520
|
+
return { request: new Request(url.toString(), { method, headers }), url };
|
|
3521
|
+
}
|
|
3522
|
+
const contentLength = req.headers["content-length"];
|
|
3523
|
+
if (contentLength !== void 0) {
|
|
3524
|
+
const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);
|
|
3525
|
+
if (Number.isFinite(declared) && declared > bodyLimit) {
|
|
3526
|
+
throw new PayloadTooLargeError(bodyLimit);
|
|
3527
|
+
}
|
|
3286
3528
|
}
|
|
3287
3529
|
const stream = Readable2.toWeb(req);
|
|
3288
3530
|
const limitedStream = limitStreamSize(stream, bodyLimit);
|
|
3289
|
-
return
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3531
|
+
return {
|
|
3532
|
+
request: new Request(url.toString(), {
|
|
3533
|
+
method,
|
|
3534
|
+
headers,
|
|
3535
|
+
body: limitedStream,
|
|
3536
|
+
duplex: "half"
|
|
3537
|
+
}),
|
|
3538
|
+
url
|
|
3539
|
+
};
|
|
3295
3540
|
}
|
|
3296
3541
|
function limitStreamSize(stream, maxSize) {
|
|
3297
3542
|
let totalSize = 0;
|
|
@@ -3343,22 +3588,6 @@ function limitStreamSize(stream, maxSize) {
|
|
|
3343
3588
|
}
|
|
3344
3589
|
});
|
|
3345
3590
|
}
|
|
3346
|
-
function findAllowedMethods(routes, path11) {
|
|
3347
|
-
const methods = /* @__PURE__ */ new Set();
|
|
3348
|
-
for (const route of routes) {
|
|
3349
|
-
if (route.urlPath === path11) {
|
|
3350
|
-
methods.add(route.method);
|
|
3351
|
-
continue;
|
|
3352
|
-
}
|
|
3353
|
-
if (route.isDynamic) {
|
|
3354
|
-
const params = matchDynamicPath(route.urlPath, path11, route.paramNames, route.isCatchAll);
|
|
3355
|
-
if (params !== null) {
|
|
3356
|
-
methods.add(route.method);
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
return Array.from(methods);
|
|
3361
|
-
}
|
|
3362
3591
|
function createServer(options) {
|
|
3363
3592
|
const {
|
|
3364
3593
|
routes,
|
|
@@ -3373,7 +3602,8 @@ function createServer(options) {
|
|
|
3373
3602
|
helmet: helmetOption,
|
|
3374
3603
|
logger: loggerOption,
|
|
3375
3604
|
bodyLimit = DEFAULT_BODY_LIMIT,
|
|
3376
|
-
http2: http2Option
|
|
3605
|
+
http2: http2Option,
|
|
3606
|
+
trustedProxy = false
|
|
3377
3607
|
} = options;
|
|
3378
3608
|
const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
|
|
3379
3609
|
const configMiddlewares = [];
|
|
@@ -3385,6 +3615,10 @@ function createServer(options) {
|
|
|
3385
3615
|
}
|
|
3386
3616
|
const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
|
|
3387
3617
|
if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
|
|
3618
|
+
const outerMiddlewares = [...configMiddlewares];
|
|
3619
|
+
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
3620
|
+
outerMiddlewares.push(...globalMiddlewares);
|
|
3621
|
+
}
|
|
3388
3622
|
const server = (() => {
|
|
3389
3623
|
if (http2Option) {
|
|
3390
3624
|
const h2Opts = typeof http2Option === "object" ? http2Option : {};
|
|
@@ -3404,29 +3638,29 @@ function createServer(options) {
|
|
|
3404
3638
|
dist,
|
|
3405
3639
|
req,
|
|
3406
3640
|
res,
|
|
3407
|
-
|
|
3641
|
+
outerMiddlewares,
|
|
3408
3642
|
onError,
|
|
3409
3643
|
config,
|
|
3410
|
-
globalMiddlewares,
|
|
3411
3644
|
globalInjectors,
|
|
3412
|
-
bodyLimit
|
|
3645
|
+
bodyLimit,
|
|
3646
|
+
trustedProxy
|
|
3413
3647
|
).catch(() => {
|
|
3414
3648
|
res.statusCode = 500;
|
|
3415
3649
|
res.end();
|
|
3416
3650
|
});
|
|
3417
3651
|
});
|
|
3418
3652
|
if (routesRef.wsCurrent.length > 0) {
|
|
3419
|
-
attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
|
|
3653
|
+
attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares, trustedProxy });
|
|
3420
3654
|
}
|
|
3421
3655
|
return { server, routesRef };
|
|
3422
3656
|
}
|
|
3423
|
-
function prepareRequest(req, config, bodyLimit) {
|
|
3424
|
-
const request = toWebRequest(req, bodyLimit);
|
|
3657
|
+
function prepareRequest(req, config, bodyLimit, trustedProxy) {
|
|
3658
|
+
const { request, url } = toWebRequest(req, bodyLimit);
|
|
3425
3659
|
const method = request.method.toUpperCase();
|
|
3426
|
-
const urlPath =
|
|
3427
|
-
const ctx =
|
|
3660
|
+
const urlPath = url.pathname;
|
|
3661
|
+
const ctx = createContextFromUrl(request, url, {}, config, getClientIp(req, trustedProxy));
|
|
3428
3662
|
const meta = ctx.meta;
|
|
3429
|
-
return { request, ctx, meta, method, urlPath };
|
|
3663
|
+
return { request, url, ctx, meta, method, urlPath };
|
|
3430
3664
|
}
|
|
3431
3665
|
function resolveRouteOrThrow(routes, method, urlPath) {
|
|
3432
3666
|
const match = matchRoute(routes, method, urlPath);
|
|
@@ -3438,14 +3672,14 @@ function resolveRouteOrThrow(routes, method, urlPath) {
|
|
|
3438
3672
|
throw new RouteNotFoundError(urlPath);
|
|
3439
3673
|
}
|
|
3440
3674
|
function createRoutePipeline(opts) {
|
|
3441
|
-
const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
3675
|
+
const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
3442
3676
|
return async () => {
|
|
3443
3677
|
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
3444
3678
|
ctx.params = match.params;
|
|
3445
3679
|
const { route } = match;
|
|
3446
|
-
const absoluteFilePath =
|
|
3680
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3447
3681
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3448
|
-
const input = await
|
|
3682
|
+
const input = await resolveInputFromUrl(route.method, request, url);
|
|
3449
3683
|
const inputType = getInputTypeForMethod(route.method);
|
|
3450
3684
|
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
3451
3685
|
if (isDevOnDemandEnabled()) {
|
|
@@ -3477,33 +3711,33 @@ async function sendSuccessResponse(response, res) {
|
|
|
3477
3711
|
await sendNodeResponse(response, res);
|
|
3478
3712
|
}
|
|
3479
3713
|
async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
3480
|
-
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx
|
|
3481
|
-
if (onError) {
|
|
3714
|
+
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);
|
|
3715
|
+
if (onError && ctx) {
|
|
3482
3716
|
try {
|
|
3483
3717
|
await onError(err, ctx);
|
|
3484
3718
|
} catch {
|
|
3485
3719
|
}
|
|
3486
3720
|
}
|
|
3487
3721
|
}
|
|
3488
|
-
async function handleRequest(routes, rootDir, dist, req, res,
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
routes,
|
|
3492
|
-
method,
|
|
3493
|
-
urlPath,
|
|
3494
|
-
ctx,
|
|
3495
|
-
request,
|
|
3496
|
-
rootDir,
|
|
3497
|
-
dist,
|
|
3498
|
-
globalMiddlewares,
|
|
3499
|
-
globalInjectors
|
|
3500
|
-
});
|
|
3501
|
-
const outerMiddlewares = [];
|
|
3502
|
-
if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
|
|
3503
|
-
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
3504
|
-
outerMiddlewares.push(...globalMiddlewares);
|
|
3505
|
-
}
|
|
3722
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
|
|
3723
|
+
let meta = { headers: {}, setCookies: [] };
|
|
3724
|
+
let ctx;
|
|
3506
3725
|
try {
|
|
3726
|
+
const prepared = prepareRequest(req, config, bodyLimit, trustedProxy);
|
|
3727
|
+
ctx = prepared.ctx;
|
|
3728
|
+
meta = prepared.meta;
|
|
3729
|
+
const { request, url, method, urlPath } = prepared;
|
|
3730
|
+
const routePipeline = createRoutePipeline({
|
|
3731
|
+
routes,
|
|
3732
|
+
method,
|
|
3733
|
+
urlPath,
|
|
3734
|
+
url,
|
|
3735
|
+
ctx,
|
|
3736
|
+
request,
|
|
3737
|
+
rootDir,
|
|
3738
|
+
dist,
|
|
3739
|
+
globalInjectors
|
|
3740
|
+
});
|
|
3507
3741
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
3508
3742
|
await sendSuccessResponse(response, res);
|
|
3509
3743
|
} catch (err) {
|
|
@@ -3530,7 +3764,7 @@ async function createTestServer(options) {
|
|
|
3530
3764
|
} = options;
|
|
3531
3765
|
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
3532
3766
|
const sorted = sortRoutes(routes);
|
|
3533
|
-
const schemaDist = dist ?
|
|
3767
|
+
const schemaDist = dist ? path11.isAbsolute(dist) ? dist : path11.resolve(rootDir, dist) : await fs9.mkdtemp(path11.join(os.tmpdir(), "faapi-test-schema-"));
|
|
3534
3768
|
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
3535
3769
|
const { server } = createServer({
|
|
3536
3770
|
routes: sorted,
|