@faapi/faapi 0.0.0-canary.0 → 0.0.0-canary.e24fdb7
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/README.md +1 -1
- package/dist/cli/index.js +386 -136
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +52 -11
- package/dist/index.js +67 -3
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/cli/index.js
CHANGED
|
@@ -613,16 +613,17 @@ var init_normalizePatterns = __esm({
|
|
|
613
613
|
// src/cli/parseArgs.ts
|
|
614
614
|
function parseArgs(argv) {
|
|
615
615
|
const cli = cac("faapi");
|
|
616
|
-
cli.option("--port <port>", "Server port (env: PORT)").option("--app-dir <dir>", "App directory", { default: "
|
|
616
|
+
cli.option("--port <port>", "Server port (env: PORT)").option("--app-dir <dir>", "App directory (root by default)", { default: "." }).option("--cors", "Enable CORS (default in dev mode)").option("--no-cors", "Disable CORS").option("--static <dir>", "Static files directory", { default: void 0 }).option("--no-static", "Disable static file serving").option("--types <path>", "Output path for generated types file").option("--config <path>", "Path to config file (faapi.config.ts)");
|
|
617
617
|
const { args, options } = cli.parse(["", "", ...argv]);
|
|
618
618
|
const rawPatterns = args.map(String).filter((a) => a !== "dev");
|
|
619
619
|
const patterns = normalizePatterns(rawPatterns);
|
|
620
620
|
const port = options.port ? Number(options.port) : Number(process.env.PORT) || 3e3;
|
|
621
|
-
const appDir = String(options.appDir ?? "
|
|
621
|
+
const appDir = String(options.appDir ?? ".");
|
|
622
622
|
const cors2 = options.cors !== false;
|
|
623
623
|
const staticDir = options.static === false ? void 0 : options.static;
|
|
624
|
+
const defaultPattern = appDir === "." ? "api/**/*.ts" : `${appDir}/api/**/*.ts`;
|
|
624
625
|
return {
|
|
625
|
-
patterns: patterns.length > 0 ? patterns : [
|
|
626
|
+
patterns: patterns.length > 0 ? patterns : [defaultPattern],
|
|
626
627
|
port,
|
|
627
628
|
appDir,
|
|
628
629
|
cors: cors2,
|
|
@@ -695,7 +696,7 @@ function isCatchAllSegment(segment) {
|
|
|
695
696
|
function isRouteGroup(segment) {
|
|
696
697
|
return /^\(.+\)$/.test(segment);
|
|
697
698
|
}
|
|
698
|
-
function filePathToUrlPath(filePath, appDir = "
|
|
699
|
+
function filePathToUrlPath(filePath, appDir = ".") {
|
|
699
700
|
const withoutPrefix = filePath.startsWith(appDir + "/") ? filePath.slice(appDir.length + 1) : filePath;
|
|
700
701
|
const lastSlashIndex = withoutPrefix.lastIndexOf("/");
|
|
701
702
|
const dirPath = lastSlashIndex === -1 ? "" : withoutPrefix.slice(0, lastSlashIndex);
|
|
@@ -850,7 +851,7 @@ async function hasWsExport(absPath) {
|
|
|
850
851
|
}
|
|
851
852
|
}
|
|
852
853
|
async function scanRoutes(rootDir, patterns, appDir) {
|
|
853
|
-
const dir = appDir ?? "
|
|
854
|
+
const dir = appDir ?? ".";
|
|
854
855
|
const files = await fg(patterns, {
|
|
855
856
|
cwd: rootDir,
|
|
856
857
|
onlyFiles: true,
|
|
@@ -1396,6 +1397,65 @@ var init_inputType = __esm({
|
|
|
1396
1397
|
}
|
|
1397
1398
|
});
|
|
1398
1399
|
|
|
1400
|
+
// src/errors/FaapiError.ts
|
|
1401
|
+
var FaapiError;
|
|
1402
|
+
var init_FaapiError = __esm({
|
|
1403
|
+
"src/errors/FaapiError.ts"() {
|
|
1404
|
+
"use strict";
|
|
1405
|
+
FaapiError = class extends Error {
|
|
1406
|
+
constructor(code, message, statusCode) {
|
|
1407
|
+
super(message);
|
|
1408
|
+
this.code = code;
|
|
1409
|
+
this.statusCode = statusCode;
|
|
1410
|
+
this.name = "FaapiError";
|
|
1411
|
+
}
|
|
1412
|
+
code;
|
|
1413
|
+
statusCode;
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
// src/errors/httpErrors.ts
|
|
1419
|
+
function deriveStatusCode(issues) {
|
|
1420
|
+
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1421
|
+
return has400 ? 400 : 422;
|
|
1422
|
+
}
|
|
1423
|
+
var ValidationError, RouteNotFoundError, MethodNotAllowedError, InternalError;
|
|
1424
|
+
var init_httpErrors = __esm({
|
|
1425
|
+
"src/errors/httpErrors.ts"() {
|
|
1426
|
+
"use strict";
|
|
1427
|
+
init_FaapiError();
|
|
1428
|
+
ValidationError = class extends FaapiError {
|
|
1429
|
+
constructor(message, issues) {
|
|
1430
|
+
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
1431
|
+
this.issues = issues;
|
|
1432
|
+
this.name = "ValidationError";
|
|
1433
|
+
}
|
|
1434
|
+
issues;
|
|
1435
|
+
};
|
|
1436
|
+
RouteNotFoundError = class extends FaapiError {
|
|
1437
|
+
constructor(path10) {
|
|
1438
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path10}`, 404);
|
|
1439
|
+
this.name = "RouteNotFoundError";
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
MethodNotAllowedError = class extends FaapiError {
|
|
1443
|
+
constructor(method, path10, allowedMethods) {
|
|
1444
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path10}`, 405);
|
|
1445
|
+
this.allowedMethods = allowedMethods;
|
|
1446
|
+
this.name = "MethodNotAllowedError";
|
|
1447
|
+
}
|
|
1448
|
+
allowedMethods;
|
|
1449
|
+
};
|
|
1450
|
+
InternalError = class extends FaapiError {
|
|
1451
|
+
constructor(message) {
|
|
1452
|
+
super("INTERNAL_ERROR", message, 500);
|
|
1453
|
+
this.name = "InternalError";
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1399
1459
|
// src/runtime/resolveInput.ts
|
|
1400
1460
|
async function resolveInput(method, request) {
|
|
1401
1461
|
const inputType = getInputTypeForMethod(method);
|
|
@@ -1404,12 +1464,33 @@ async function resolveInput(method, request) {
|
|
|
1404
1464
|
if (contentType.includes("multipart/form-data")) {
|
|
1405
1465
|
return parseMultipart(request);
|
|
1406
1466
|
}
|
|
1467
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1468
|
+
const text2 = await request.text();
|
|
1469
|
+
if (text2.trim() === "") return null;
|
|
1470
|
+
const params = new URLSearchParams(text2);
|
|
1471
|
+
const obj = {};
|
|
1472
|
+
for (const [key, value] of params) {
|
|
1473
|
+
obj[key] = value;
|
|
1474
|
+
}
|
|
1475
|
+
return obj;
|
|
1476
|
+
}
|
|
1407
1477
|
const text = await request.text();
|
|
1478
|
+
if (text.trim() === "") {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1408
1481
|
const result = parseJsonBody(text);
|
|
1409
|
-
if (result.success) {
|
|
1410
|
-
|
|
1482
|
+
if (!result.success) {
|
|
1483
|
+
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
1484
|
+
{
|
|
1485
|
+
path: "body",
|
|
1486
|
+
code: "INVALID_FORMAT",
|
|
1487
|
+
expected: "JSON",
|
|
1488
|
+
received: "text",
|
|
1489
|
+
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
1490
|
+
}
|
|
1491
|
+
]);
|
|
1411
1492
|
}
|
|
1412
|
-
return
|
|
1493
|
+
return result.data;
|
|
1413
1494
|
}
|
|
1414
1495
|
const url = new URL(request.url);
|
|
1415
1496
|
return queryToObject(url.searchParams);
|
|
@@ -1421,6 +1502,7 @@ var init_resolveInput = __esm({
|
|
|
1421
1502
|
init_parseJsonBody();
|
|
1422
1503
|
init_parseMultipart();
|
|
1423
1504
|
init_inputType();
|
|
1505
|
+
init_httpErrors();
|
|
1424
1506
|
}
|
|
1425
1507
|
});
|
|
1426
1508
|
|
|
@@ -1805,61 +1887,6 @@ var init_sendNodeResponse = __esm({
|
|
|
1805
1887
|
}
|
|
1806
1888
|
});
|
|
1807
1889
|
|
|
1808
|
-
// src/errors/FaapiError.ts
|
|
1809
|
-
var FaapiError;
|
|
1810
|
-
var init_FaapiError = __esm({
|
|
1811
|
-
"src/errors/FaapiError.ts"() {
|
|
1812
|
-
"use strict";
|
|
1813
|
-
FaapiError = class extends Error {
|
|
1814
|
-
constructor(code, message, statusCode) {
|
|
1815
|
-
super(message);
|
|
1816
|
-
this.code = code;
|
|
1817
|
-
this.statusCode = statusCode;
|
|
1818
|
-
this.name = "FaapiError";
|
|
1819
|
-
}
|
|
1820
|
-
code;
|
|
1821
|
-
statusCode;
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
});
|
|
1825
|
-
|
|
1826
|
-
// src/errors/httpErrors.ts
|
|
1827
|
-
var ValidationError, RouteNotFoundError, MethodNotAllowedError, InternalError;
|
|
1828
|
-
var init_httpErrors = __esm({
|
|
1829
|
-
"src/errors/httpErrors.ts"() {
|
|
1830
|
-
"use strict";
|
|
1831
|
-
init_FaapiError();
|
|
1832
|
-
ValidationError = class extends FaapiError {
|
|
1833
|
-
constructor(message, issues) {
|
|
1834
|
-
super("VALIDATION_ERROR", message, 400);
|
|
1835
|
-
this.issues = issues;
|
|
1836
|
-
this.name = "ValidationError";
|
|
1837
|
-
}
|
|
1838
|
-
issues;
|
|
1839
|
-
};
|
|
1840
|
-
RouteNotFoundError = class extends FaapiError {
|
|
1841
|
-
constructor(path10) {
|
|
1842
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${path10}`, 404);
|
|
1843
|
-
this.name = "RouteNotFoundError";
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
MethodNotAllowedError = class extends FaapiError {
|
|
1847
|
-
constructor(method, path10, allowedMethods) {
|
|
1848
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path10}`, 405);
|
|
1849
|
-
this.allowedMethods = allowedMethods;
|
|
1850
|
-
this.name = "MethodNotAllowedError";
|
|
1851
|
-
}
|
|
1852
|
-
allowedMethods;
|
|
1853
|
-
};
|
|
1854
|
-
InternalError = class extends FaapiError {
|
|
1855
|
-
constructor(message) {
|
|
1856
|
-
super("INTERNAL_ERROR", message, 500);
|
|
1857
|
-
this.name = "InternalError";
|
|
1858
|
-
}
|
|
1859
|
-
};
|
|
1860
|
-
}
|
|
1861
|
-
});
|
|
1862
|
-
|
|
1863
1890
|
// src/validator/schemaRegistry.ts
|
|
1864
1891
|
var SchemaRegistry, schemaRegistry;
|
|
1865
1892
|
var init_schemaRegistry = __esm({
|
|
@@ -1971,6 +1998,20 @@ function coerceValue(value, type, path10, issues) {
|
|
|
1971
1998
|
return value.map((item, i) => coerceValue(item, type.element, `${path10}[${i}]`, issues));
|
|
1972
1999
|
}
|
|
1973
2000
|
return value;
|
|
2001
|
+
case "tuple": {
|
|
2002
|
+
if (!Array.isArray(value)) return value;
|
|
2003
|
+
const restElement = type.elements.find((e) => e.rest);
|
|
2004
|
+
return value.map((item, i) => {
|
|
2005
|
+
const elem = type.elements[i];
|
|
2006
|
+
if (elem && !elem.rest) {
|
|
2007
|
+
return coerceValue(item, elem.type, `${path10}[${i}]`, issues);
|
|
2008
|
+
}
|
|
2009
|
+
if (restElement) {
|
|
2010
|
+
return coerceValue(item, restElement.type, `${path10}[${i}]`, issues);
|
|
2011
|
+
}
|
|
2012
|
+
return item;
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
1974
2015
|
case "union":
|
|
1975
2016
|
for (const member of type.members) {
|
|
1976
2017
|
const tempIssues = [];
|
|
@@ -1980,6 +2021,12 @@ function coerceValue(value, type, path10, issues) {
|
|
|
1980
2021
|
}
|
|
1981
2022
|
}
|
|
1982
2023
|
return value;
|
|
2024
|
+
case "literal":
|
|
2025
|
+
if (typeof type.value === "number" && typeof value === "string" && value.trim() !== "") {
|
|
2026
|
+
const num = Number(value);
|
|
2027
|
+
if (!Number.isNaN(num)) return num;
|
|
2028
|
+
}
|
|
2029
|
+
return value;
|
|
1983
2030
|
case "object":
|
|
1984
2031
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1985
2032
|
const obj = value;
|
|
@@ -2005,6 +2052,9 @@ function coerceStringToNumber(value, path10, issues) {
|
|
|
2005
2052
|
if (value.trim() === "") {
|
|
2006
2053
|
issues.push({
|
|
2007
2054
|
path: path10,
|
|
2055
|
+
code: "COERCE_FAILED",
|
|
2056
|
+
expected: "number",
|
|
2057
|
+
received: "string",
|
|
2008
2058
|
message: `\u5B57\u6BB5 "${path10}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
|
|
2009
2059
|
});
|
|
2010
2060
|
return value;
|
|
@@ -2013,6 +2063,9 @@ function coerceStringToNumber(value, path10, issues) {
|
|
|
2013
2063
|
if (Number.isNaN(num)) {
|
|
2014
2064
|
issues.push({
|
|
2015
2065
|
path: path10,
|
|
2066
|
+
code: "COERCE_FAILED",
|
|
2067
|
+
expected: "number",
|
|
2068
|
+
received: "string",
|
|
2016
2069
|
message: `\u5B57\u6BB5 "${path10}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A number`
|
|
2017
2070
|
});
|
|
2018
2071
|
return value;
|
|
@@ -2028,6 +2081,9 @@ function coerceStringToBoolean(value, path10, issues) {
|
|
|
2028
2081
|
}
|
|
2029
2082
|
issues.push({
|
|
2030
2083
|
path: path10,
|
|
2084
|
+
code: "COERCE_FAILED",
|
|
2085
|
+
expected: "boolean",
|
|
2086
|
+
received: "string",
|
|
2031
2087
|
message: `\u5B57\u6BB5 "${path10}" \u7C7B\u578B\u8F6C\u6362\u5931\u8D25\uFF1A\u65E0\u6CD5\u5C06 "${value}" \u8F6C\u4E3A boolean`
|
|
2032
2088
|
});
|
|
2033
2089
|
return value;
|
|
@@ -2275,7 +2331,12 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
|
|
|
2275
2331
|
case ts2.SyntaxKind.BooleanKeyword:
|
|
2276
2332
|
return { kind: "boolean" };
|
|
2277
2333
|
case ts2.SyntaxKind.BigIntKeyword:
|
|
2278
|
-
|
|
2334
|
+
throw new SchemaExtractionError(
|
|
2335
|
+
typeNode.getText(),
|
|
2336
|
+
"bigint \u65E0\u6CD5\u901A\u8FC7 HTTP/JSON \u4F20\u8F93,\u8BF7\u6539\u7528 string \u6216 number"
|
|
2337
|
+
);
|
|
2338
|
+
case ts2.SyntaxKind.SymbolKeyword:
|
|
2339
|
+
throw new SchemaExtractionError(typeNode.getText(), "symbol \u65E0\u6CD5\u901A\u8FC7 HTTP/JSON \u4F20\u8F93");
|
|
2279
2340
|
case ts2.SyntaxKind.NullKeyword:
|
|
2280
2341
|
return { kind: "null" };
|
|
2281
2342
|
case ts2.SyntaxKind.UndefinedKeyword:
|
|
@@ -2320,8 +2381,35 @@ function resolveTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set())
|
|
|
2320
2381
|
};
|
|
2321
2382
|
}
|
|
2322
2383
|
if (ts2.isTupleTypeNode(typeNode)) {
|
|
2323
|
-
const elements = typeNode.elements.map((e) =>
|
|
2324
|
-
|
|
2384
|
+
const elements = typeNode.elements.map((e) => {
|
|
2385
|
+
if (ts2.isRestTypeNode(e)) {
|
|
2386
|
+
const inner = resolveTypeNode(e.type, checker, visited);
|
|
2387
|
+
if (inner.kind === "array") {
|
|
2388
|
+
return { type: inner.element, optional: false, rest: true };
|
|
2389
|
+
}
|
|
2390
|
+
return { type: inner, optional: false, rest: true };
|
|
2391
|
+
}
|
|
2392
|
+
if (ts2.isNamedTupleMember(e)) {
|
|
2393
|
+
return {
|
|
2394
|
+
type: resolveTypeNode(e.type, checker, visited),
|
|
2395
|
+
optional: !!e.questionToken,
|
|
2396
|
+
rest: false
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
if (ts2.isOptionalTypeNode(e)) {
|
|
2400
|
+
return {
|
|
2401
|
+
type: resolveTypeNode(e.type, checker, visited),
|
|
2402
|
+
optional: true,
|
|
2403
|
+
rest: false
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
return {
|
|
2407
|
+
type: resolveTypeNode(e, checker, visited),
|
|
2408
|
+
optional: false,
|
|
2409
|
+
rest: false
|
|
2410
|
+
};
|
|
2411
|
+
});
|
|
2412
|
+
return { kind: "tuple", elements };
|
|
2325
2413
|
}
|
|
2326
2414
|
if (ts2.isUnionTypeNode(typeNode)) {
|
|
2327
2415
|
const members = typeNode.types.map((t) => resolveTypeNode(t, checker, visited));
|
|
@@ -2492,6 +2580,9 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
2492
2580
|
"Promise \u8FD0\u884C\u65F6\u65E0\u6CD5\u6821\u9A8C\uFF0C\u8BF7\u52FF\u5728 query/body \u7C7B\u578B\u4E2D\u4F7F\u7528"
|
|
2493
2581
|
);
|
|
2494
2582
|
}
|
|
2583
|
+
if (typeName === "Function") {
|
|
2584
|
+
throw new SchemaExtractionError(typeNode.getText(), "Function \u65E0\u6CD5\u901A\u8FC7 HTTP/JSON \u4F20\u8F93");
|
|
2585
|
+
}
|
|
2495
2586
|
if (visited.has(typeName)) {
|
|
2496
2587
|
return { kind: "ref", name: typeName };
|
|
2497
2588
|
}
|
|
@@ -2507,11 +2598,38 @@ function resolveTypeReference(typeNode, checker, visited = /* @__PURE__ */ new S
|
|
|
2507
2598
|
if (ts2.isTypeAliasDeclaration(declaration)) {
|
|
2508
2599
|
return resolveTypeNode(declaration.type, checker, visited);
|
|
2509
2600
|
}
|
|
2601
|
+
if (ts2.isEnumDeclaration(declaration)) {
|
|
2602
|
+
return resolveEnumDeclaration(declaration);
|
|
2603
|
+
}
|
|
2510
2604
|
}
|
|
2511
2605
|
}
|
|
2512
2606
|
}
|
|
2513
2607
|
throw new SchemaExtractionError(typeNode.getText(), `\u65E0\u6CD5\u89E3\u6790\u7684\u5F15\u7528\u7C7B\u578B "${typeName}"`);
|
|
2514
2608
|
}
|
|
2609
|
+
function resolveEnumDeclaration(node) {
|
|
2610
|
+
const members = [];
|
|
2611
|
+
let nextNumericValue = 0;
|
|
2612
|
+
for (const member of node.members) {
|
|
2613
|
+
if (member.initializer) {
|
|
2614
|
+
if (ts2.isStringLiteral(member.initializer)) {
|
|
2615
|
+
members.push({ kind: "literal", value: member.initializer.text });
|
|
2616
|
+
} else if (ts2.isNumericLiteral(member.initializer)) {
|
|
2617
|
+
const num = Number(member.initializer.text);
|
|
2618
|
+
members.push({ kind: "literal", value: num });
|
|
2619
|
+
nextNumericValue = num + 1;
|
|
2620
|
+
} else {
|
|
2621
|
+
throw new SchemaExtractionError(
|
|
2622
|
+
node.name.text,
|
|
2623
|
+
`enum \u6210\u5458 "${member.name.getText()}" \u7684\u521D\u59CB\u5316\u503C\u7C7B\u578B\u4E0D\u652F\u6301,\u4EC5\u652F\u6301 string/number \u5B57\u9762\u91CF`
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
} else {
|
|
2627
|
+
members.push({ kind: "literal", value: nextNumericValue });
|
|
2628
|
+
nextNumericValue++;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
return { kind: "union", members };
|
|
2632
|
+
}
|
|
2515
2633
|
function resolveInterfaceDeclaration(node, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2516
2634
|
const properties = [];
|
|
2517
2635
|
const propMap = /* @__PURE__ */ new Map();
|
|
@@ -2563,6 +2681,34 @@ var init_resolveTypeNode = __esm({
|
|
|
2563
2681
|
});
|
|
2564
2682
|
|
|
2565
2683
|
// src/ast/generateValidatorCode.ts
|
|
2684
|
+
function runtimeTypeToExpected(type) {
|
|
2685
|
+
switch (type.kind) {
|
|
2686
|
+
case "string":
|
|
2687
|
+
case "number":
|
|
2688
|
+
case "boolean":
|
|
2689
|
+
case "null":
|
|
2690
|
+
case "undefined":
|
|
2691
|
+
case "bigint":
|
|
2692
|
+
case "date":
|
|
2693
|
+
case "any":
|
|
2694
|
+
case "unknown":
|
|
2695
|
+
return type.kind;
|
|
2696
|
+
case "literal":
|
|
2697
|
+
return typeof type.value === "string" ? `'${type.value}'` : String(type.value);
|
|
2698
|
+
case "array":
|
|
2699
|
+
return `${runtimeTypeToExpected(type.element)}[]`;
|
|
2700
|
+
case "tuple":
|
|
2701
|
+
return `[${type.elements.map((e) => (e.rest ? "..." : "") + runtimeTypeToExpected(e.type) + (e.optional ? "?" : "")).join(", ")}]`;
|
|
2702
|
+
case "object":
|
|
2703
|
+
return "object";
|
|
2704
|
+
case "union":
|
|
2705
|
+
return type.members.map(runtimeTypeToExpected).join(" | ");
|
|
2706
|
+
case "record":
|
|
2707
|
+
return `Record<${runtimeTypeToExpected(type.key)}, ${runtimeTypeToExpected(type.value)}>`;
|
|
2708
|
+
case "ref":
|
|
2709
|
+
return type.name;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2566
2712
|
function generateValidatorSource(typeInfo, resolveType) {
|
|
2567
2713
|
const ctx = new CodeGenContext(resolveType);
|
|
2568
2714
|
collectNamedTypes(typeInfo.runtimeType, typeInfo.name, ctx);
|
|
@@ -2578,7 +2724,7 @@ function generateValidatorSource(typeInfo, resolveType) {
|
|
|
2578
2724
|
const body = generateObjectValidation(type, "value", "issues", ctx, "path");
|
|
2579
2725
|
return `function validate_${name}(value, path, issues) {
|
|
2580
2726
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
2581
|
-
issues.push({ path, message: '\u671F\u671B\u5BF9\u8C61' });
|
|
2727
|
+
issues.push({ path, code: 'TYPE_MISMATCH', expected: 'object', received: typeof value, message: '\u671F\u671B\u5BF9\u8C61' });
|
|
2582
2728
|
return;
|
|
2583
2729
|
}
|
|
2584
2730
|
if (!validate_${name}.__visited) validate_${name}.__visited = new WeakSet();
|
|
@@ -2613,6 +2759,11 @@ function collectNamedTypesFromType(type, ctx) {
|
|
|
2613
2759
|
case "array":
|
|
2614
2760
|
collectNamedTypesFromType(type.element, ctx);
|
|
2615
2761
|
break;
|
|
2762
|
+
case "tuple":
|
|
2763
|
+
for (const elem of type.elements) {
|
|
2764
|
+
collectNamedTypesFromType(elem.type, ctx);
|
|
2765
|
+
}
|
|
2766
|
+
break;
|
|
2616
2767
|
case "union":
|
|
2617
2768
|
for (const member of type.members) {
|
|
2618
2769
|
collectNamedTypesFromType(member, ctx);
|
|
@@ -2649,7 +2800,7 @@ function generateObjectValidation(type, varName, issuesVar, ctx, pathExpr) {
|
|
|
2649
2800
|
const hasCheck = `'${prop.name}' in ${varName}`;
|
|
2650
2801
|
if (!prop.optional) {
|
|
2651
2802
|
lines.push(
|
|
2652
|
-
`if (!${hasCheck}) ${issuesVar}.push({ path: ${propPath}, message: '\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5 "${prop.name}"' });`
|
|
2803
|
+
`if (!${hasCheck}) ${issuesVar}.push({ path: ${propPath}, code: 'MISSING_FIELD', expected: '${prop.name}', received: 'undefined', message: '\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5 "${prop.name}"' });`
|
|
2653
2804
|
);
|
|
2654
2805
|
lines.push(`else {`);
|
|
2655
2806
|
} else {
|
|
@@ -2668,21 +2819,23 @@ function generateValueValidation(type, varName, issuesVar, ctx, pathExpr) {
|
|
|
2668
2819
|
return "";
|
|
2669
2820
|
// 不校验
|
|
2670
2821
|
case "string":
|
|
2671
|
-
return `if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B string\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2822
|
+
return `if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'string', received: typeof ${varName}, message: '\u671F\u671B string\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2672
2823
|
case "number":
|
|
2673
|
-
return `if (typeof ${varName} !== 'number' || Number.isNaN(${varName})) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B number\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2824
|
+
return `if (typeof ${varName} !== 'number' || Number.isNaN(${varName})) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'number', received: typeof ${varName}, message: '\u671F\u671B number\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2674
2825
|
case "boolean":
|
|
2675
|
-
return `if (typeof ${varName} !== 'boolean') ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B boolean\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2826
|
+
return `if (typeof ${varName} !== 'boolean') ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'boolean', received: typeof ${varName}, message: '\u671F\u671B boolean\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2676
2827
|
case "bigint":
|
|
2677
|
-
return `if (typeof ${varName} !== 'bigint') ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B bigint\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2828
|
+
return `if (typeof ${varName} !== 'bigint') ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'bigint', received: typeof ${varName}, message: '\u671F\u671B bigint\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2678
2829
|
case "null":
|
|
2679
|
-
return `if (${varName} !== null) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B null\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2830
|
+
return `if (${varName} !== null) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'null', received: typeof ${varName}, message: '\u671F\u671B null\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`;
|
|
2680
2831
|
case "undefined":
|
|
2681
|
-
return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B undefined' });`;
|
|
2832
|
+
return `if (${varName} !== undefined) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'undefined', received: typeof ${varName}, message: '\u671F\u671B undefined' });`;
|
|
2682
2833
|
case "literal":
|
|
2683
|
-
return `if (${varName} !== ${JSON.stringify(type.value)}) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B\u5B57\u9762\u91CF ${JSON.stringify(type.value)}\uFF0C\u5B9E\u9645 ' + JSON.stringify(${varName}) });`;
|
|
2834
|
+
return `if (${varName} !== ${JSON.stringify(type.value)}) ${issuesVar}.push({ path: ${path10}, code: 'INVALID_VALUE', expected: ${JSON.stringify(JSON.stringify(type.value))}, received: JSON.stringify(${varName}), message: '\u671F\u671B\u5B57\u9762\u91CF ${JSON.stringify(type.value)}\uFF0C\u5B9E\u9645 ' + JSON.stringify(${varName}) });`;
|
|
2684
2835
|
case "date":
|
|
2685
|
-
return `if (
|
|
2836
|
+
return `if (${varName} instanceof Date) { /* Date \u5B9E\u4F8B,\u901A\u8FC7 */ }
|
|
2837
|
+
else if (typeof ${varName} !== 'string') ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'Date | ISO 8601 string', received: typeof ${varName}, message: '\u671F\u671B Date \u6216 ISO 8601 \u5B57\u7B26\u4E32\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
|
|
2838
|
+
else if (!/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,3})?(Z|[+-]\\d{2}:?\\d{2})?)?$/.test(${varName}) || isNaN(new Date(${varName}).getTime())) ${issuesVar}.push({ path: ${path10}, code: 'INVALID_FORMAT', expected: 'ISO 8601', received: String(${varName}), message: '\u4E0D\u662F\u5408\u6CD5\u7684 ISO 8601 \u65E5\u671F\u5B57\u7B26\u4E32: ' + ${varName} });`;
|
|
2686
2839
|
case "array": {
|
|
2687
2840
|
const id = ctx.nextVarId();
|
|
2688
2841
|
const itemVar = `item${id}`;
|
|
@@ -2695,21 +2848,71 @@ function generateValueValidation(type, varName, issuesVar, ctx, pathExpr) {
|
|
|
2695
2848
|
ctx,
|
|
2696
2849
|
elemPath
|
|
2697
2850
|
);
|
|
2698
|
-
return `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B\u6570\u7EC4\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
|
|
2851
|
+
return `if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'array', received: typeof ${varName}, message: '\u671F\u671B\u6570\u7EC4\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
|
|
2699
2852
|
else for (let ${indexVar} = 0; ${indexVar} < ${varName}.length; ${indexVar}++) { const ${itemVar} = ${varName}[${indexVar}]; ${elemValidation} }`;
|
|
2700
2853
|
}
|
|
2854
|
+
case "tuple": {
|
|
2855
|
+
const minRequired = type.elements.filter((e) => !e.optional && !e.rest).length;
|
|
2856
|
+
const fixedCount = type.elements.filter((e) => !e.rest).length;
|
|
2857
|
+
const restElement = type.elements.find((e) => e.rest);
|
|
2858
|
+
const id = ctx.nextVarId();
|
|
2859
|
+
const itemVar = `t${id}`;
|
|
2860
|
+
const indexVar = `ti${id}`;
|
|
2861
|
+
const lines = [];
|
|
2862
|
+
lines.push(
|
|
2863
|
+
`if (!Array.isArray(${varName})) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'tuple', received: typeof ${varName}, message: '\u671F\u671B\u5143\u7EC4\uFF08\u6570\u7EC4\uFF09\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`
|
|
2864
|
+
);
|
|
2865
|
+
lines.push(`else {`);
|
|
2866
|
+
lines.push(
|
|
2867
|
+
`if (${varName}.length < ${minRequired}) ${issuesVar}.push({ path: ${path10}, code: 'MISSING_FIELD', expected: 'tuple length >= ${minRequired}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u4E0D\u8DB3\uFF0C\u671F\u671B\u81F3\u5C11 ${minRequired}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
|
|
2868
|
+
);
|
|
2869
|
+
if (!restElement) {
|
|
2870
|
+
lines.push(
|
|
2871
|
+
`if (${varName}.length > ${fixedCount}) ${issuesVar}.push({ path: ${path10}, code: 'INVALID_VALUE', expected: 'tuple length = ${fixedCount}', received: 'length ' + ${varName}.length, message: '\u5143\u7EC4\u957F\u5EA6\u8D85\u51FA\uFF0C\u671F\u671B ${fixedCount}\uFF0C\u5B9E\u9645 ' + ${varName}.length });`
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
for (let i = 0; i < type.elements.length; i++) {
|
|
2875
|
+
const elem = type.elements[i];
|
|
2876
|
+
if (elem.rest) continue;
|
|
2877
|
+
const elemPath = `${path10} + '[' + ${i} + ']'`;
|
|
2878
|
+
const elemAccess = `${varName}[${i}]`;
|
|
2879
|
+
if (elem.optional) {
|
|
2880
|
+
lines.push(`if (${i} < ${varName}.length && ${elemAccess} !== undefined) {`);
|
|
2881
|
+
} else {
|
|
2882
|
+
lines.push(`if (${i} < ${varName}.length) {`);
|
|
2883
|
+
}
|
|
2884
|
+
lines.push(` ${generateValueValidation(elem.type, elemAccess, issuesVar, ctx, elemPath)}`);
|
|
2885
|
+
lines.push(`}`);
|
|
2886
|
+
}
|
|
2887
|
+
if (restElement) {
|
|
2888
|
+
const restPath = `${path10} + '[' + ${indexVar} + ']'`;
|
|
2889
|
+
const restValidation = generateValueValidation(
|
|
2890
|
+
restElement.type,
|
|
2891
|
+
itemVar,
|
|
2892
|
+
issuesVar,
|
|
2893
|
+
ctx,
|
|
2894
|
+
restPath
|
|
2895
|
+
);
|
|
2896
|
+
lines.push(
|
|
2897
|
+
`for (let ${indexVar} = ${fixedCount}; ${indexVar} < ${varName}.length; ${indexVar}++) { const ${itemVar} = ${varName}[${indexVar}]; ${restValidation} }`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
lines.push(`}`);
|
|
2901
|
+
return lines.join("\n");
|
|
2902
|
+
}
|
|
2701
2903
|
case "union": {
|
|
2702
2904
|
const tempVar = `tempIssues_${Math.random().toString(36).slice(2, 8)}`;
|
|
2703
2905
|
const memberChecks = type.members.map((member) => {
|
|
2704
2906
|
const check = generateValueValidation(member, varName, tempVar, ctx, path10);
|
|
2705
2907
|
return `(() => { const ${tempVar} = []; ${check}; return ${tempVar}.length === 0; })()`;
|
|
2706
2908
|
});
|
|
2707
|
-
|
|
2909
|
+
const expected = runtimeTypeToExpected(type);
|
|
2910
|
+
return `if (!(${memberChecks.join(" || ")})) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: ${JSON.stringify(expected)}, received: typeof ${varName}, message: '\u503C ' + JSON.stringify(${varName}) + ' \u4E0D\u5339\u914D\u8054\u5408\u7C7B\u578B ${expected.replace(/'/g, "\\'")}' });`;
|
|
2708
2911
|
}
|
|
2709
2912
|
case "record": {
|
|
2710
2913
|
const valuePath = `${path10} + '.' + key`;
|
|
2711
2914
|
const valueValidation = generateValueValidation(type.value, "val", issuesVar, ctx, valuePath);
|
|
2712
|
-
return `if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${path10}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
|
|
2915
|
+
return `if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${path10}, code: 'TYPE_MISMATCH', expected: 'object', received: typeof ${varName}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });
|
|
2713
2916
|
else for (const [key, val] of Object.entries(${varName})) { ${valueValidation} }`;
|
|
2714
2917
|
}
|
|
2715
2918
|
case "object": {
|
|
@@ -2723,7 +2926,7 @@ else for (const [key, val] of Object.entries(${varName})) { ${valueValidation} }
|
|
|
2723
2926
|
function generateInlineObjectValidation(type, varName, issuesVar, ctx, pathExpr) {
|
|
2724
2927
|
const lines = [];
|
|
2725
2928
|
lines.push(
|
|
2726
|
-
`if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${pathExpr}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`
|
|
2929
|
+
`if (typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})) ${issuesVar}.push({ path: ${pathExpr}, code: 'TYPE_MISMATCH', expected: 'object', received: typeof ${varName}, message: '\u671F\u671B\u5BF9\u8C61\uFF0C\u5B9E\u9645 ' + typeof ${varName} });`
|
|
2727
2930
|
);
|
|
2728
2931
|
lines.push("else {");
|
|
2729
2932
|
for (const prop of type.properties) {
|
|
@@ -2732,7 +2935,7 @@ function generateInlineObjectValidation(type, varName, issuesVar, ctx, pathExpr)
|
|
|
2732
2935
|
const hasCheck = `'${prop.name}' in ${varName}`;
|
|
2733
2936
|
if (!prop.optional) {
|
|
2734
2937
|
lines.push(
|
|
2735
|
-
` if (!${hasCheck}) ${issuesVar}.push({ path: ${propPath}, message: '\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5 "${prop.name}"' });`
|
|
2938
|
+
` if (!${hasCheck}) ${issuesVar}.push({ path: ${propPath}, code: 'MISSING_FIELD', expected: '${prop.name}', received: 'undefined', message: '\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5 "${prop.name}"' });`
|
|
2736
2939
|
);
|
|
2737
2940
|
lines.push(` else {`);
|
|
2738
2941
|
} else {
|
|
@@ -3138,21 +3341,24 @@ function nodeHttpToWebHeaders(req) {
|
|
|
3138
3341
|
return headers;
|
|
3139
3342
|
}
|
|
3140
3343
|
function buildErrorResponse(err, ctx, errorFormat) {
|
|
3141
|
-
|
|
3142
|
-
return errorFormat ? errorFormat(err, ctx) : formatErrorResponse(err);
|
|
3143
|
-
} catch {
|
|
3344
|
+
if (errorFormat) {
|
|
3144
3345
|
try {
|
|
3145
|
-
|
|
3346
|
+
const res = errorFormat(err, ctx);
|
|
3347
|
+
if (res) return res;
|
|
3146
3348
|
} catch {
|
|
3147
|
-
return new Response(
|
|
3148
|
-
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
3149
|
-
{
|
|
3150
|
-
status: 500,
|
|
3151
|
-
headers: { "Content-Type": "application/json" }
|
|
3152
|
-
}
|
|
3153
|
-
);
|
|
3154
3349
|
}
|
|
3155
3350
|
}
|
|
3351
|
+
try {
|
|
3352
|
+
return formatErrorResponse(err);
|
|
3353
|
+
} catch {
|
|
3354
|
+
return new Response(
|
|
3355
|
+
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
3356
|
+
{
|
|
3357
|
+
status: 500,
|
|
3358
|
+
headers: { "Content-Type": "application/json" }
|
|
3359
|
+
}
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3156
3362
|
}
|
|
3157
3363
|
var init_serverUtils = __esm({
|
|
3158
3364
|
"src/server/serverUtils.ts"() {
|
|
@@ -3380,12 +3586,7 @@ function findAllowedMethods(routes, path10) {
|
|
|
3380
3586
|
continue;
|
|
3381
3587
|
}
|
|
3382
3588
|
if (route.isDynamic) {
|
|
3383
|
-
const params = matchDynamicPath(
|
|
3384
|
-
route.urlPath,
|
|
3385
|
-
path10,
|
|
3386
|
-
route.paramNames,
|
|
3387
|
-
route.isCatchAll
|
|
3388
|
-
);
|
|
3589
|
+
const params = matchDynamicPath(route.urlPath, path10, route.paramNames, route.isCatchAll);
|
|
3389
3590
|
if (params !== null) {
|
|
3390
3591
|
methods.add(route.method);
|
|
3391
3592
|
}
|
|
@@ -3555,7 +3756,8 @@ function startServer(options) {
|
|
|
3555
3756
|
config,
|
|
3556
3757
|
wsRoutes,
|
|
3557
3758
|
middlewares,
|
|
3558
|
-
injectors
|
|
3759
|
+
injectors,
|
|
3760
|
+
beforeListen
|
|
3559
3761
|
} = options;
|
|
3560
3762
|
const server = createServer({
|
|
3561
3763
|
routes,
|
|
@@ -3570,26 +3772,57 @@ function startServer(options) {
|
|
|
3570
3772
|
middlewares,
|
|
3571
3773
|
injectors
|
|
3572
3774
|
});
|
|
3573
|
-
return
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
console.log(
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
3775
|
+
return (async () => {
|
|
3776
|
+
if (beforeListen) {
|
|
3777
|
+
await beforeListen(server);
|
|
3778
|
+
}
|
|
3779
|
+
return new Promise((resolve3) => {
|
|
3780
|
+
server.listen(port, () => {
|
|
3781
|
+
const address = server.address();
|
|
3782
|
+
const actualPort = typeof address === "object" && address !== null ? address.port : port;
|
|
3783
|
+
console.log("faapi dev server started");
|
|
3784
|
+
console.log(`- Local: http://localhost:${actualPort}`);
|
|
3785
|
+
console.log("- Loaded routes:");
|
|
3786
|
+
for (const route of routes) {
|
|
3787
|
+
const method = route.method.padEnd(6);
|
|
3788
|
+
console.log(` ${method}${route.urlPath} ${route.filePath}`);
|
|
3588
3789
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3790
|
+
if (wsRoutes && wsRoutes.length > 0) {
|
|
3791
|
+
console.log("- WebSocket routes:");
|
|
3792
|
+
for (const route of wsRoutes) {
|
|
3793
|
+
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
resolve3(server);
|
|
3797
|
+
});
|
|
3591
3798
|
});
|
|
3592
|
-
});
|
|
3799
|
+
})();
|
|
3800
|
+
}
|
|
3801
|
+
function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
3802
|
+
if (handlerWrappers.length > 0) {
|
|
3803
|
+
const listeners = server.listeners("request");
|
|
3804
|
+
const original = listeners[0];
|
|
3805
|
+
if (original) {
|
|
3806
|
+
server.removeAllListeners("request");
|
|
3807
|
+
let handler = original;
|
|
3808
|
+
for (const wrap of handlerWrappers) {
|
|
3809
|
+
handler = wrap(handler);
|
|
3810
|
+
}
|
|
3811
|
+
server.on("request", handler);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
if (upgradeWrappers.length > 0) {
|
|
3815
|
+
const listeners = server.listeners("upgrade");
|
|
3816
|
+
const original = listeners[0];
|
|
3817
|
+
server.removeAllListeners("upgrade");
|
|
3818
|
+
let upgrade = original;
|
|
3819
|
+
for (const wrap of upgradeWrappers) {
|
|
3820
|
+
upgrade = wrap(upgrade);
|
|
3821
|
+
}
|
|
3822
|
+
if (upgrade) {
|
|
3823
|
+
server.on("upgrade", upgrade);
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3593
3826
|
}
|
|
3594
3827
|
var init_startServer = __esm({
|
|
3595
3828
|
"src/server/startServer.ts"() {
|
|
@@ -5531,7 +5764,20 @@ var init_loadConfig = __esm({
|
|
|
5531
5764
|
|
|
5532
5765
|
// src/cli/loadPlugins.ts
|
|
5533
5766
|
async function loadPlugins(declarations, ctx) {
|
|
5534
|
-
|
|
5767
|
+
const handlerWrappers = [];
|
|
5768
|
+
const upgradeWrappers = [];
|
|
5769
|
+
if (!declarations || declarations.length === 0) {
|
|
5770
|
+
return { handlerWrappers, upgradeWrappers };
|
|
5771
|
+
}
|
|
5772
|
+
const fullCtx = {
|
|
5773
|
+
...ctx,
|
|
5774
|
+
wrapHandler: (fn) => {
|
|
5775
|
+
handlerWrappers.push(fn);
|
|
5776
|
+
},
|
|
5777
|
+
wrapUpgradeHandler: (fn) => {
|
|
5778
|
+
upgradeWrappers.push(fn);
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5535
5781
|
const loaded = /* @__PURE__ */ new Set();
|
|
5536
5782
|
for (const decl of declarations) {
|
|
5537
5783
|
const { specifier, options, enable } = resolveDeclaration(decl);
|
|
@@ -5548,12 +5794,15 @@ async function loadPlugins(declarations, ctx) {
|
|
|
5548
5794
|
console.warn(`! Plugin ${specifier} has no setup function, skipping`);
|
|
5549
5795
|
continue;
|
|
5550
5796
|
}
|
|
5551
|
-
await plugin.setup({ ...
|
|
5797
|
+
await plugin.setup({ ...fullCtx, options });
|
|
5552
5798
|
console.log(`- Plugin loaded: ${plugin.name ?? specifier}`);
|
|
5553
5799
|
} catch (err) {
|
|
5554
|
-
console.warn(
|
|
5800
|
+
console.warn(
|
|
5801
|
+
`! Failed to load plugin ${specifier}: ${err instanceof Error ? err.message : String(err)}`
|
|
5802
|
+
);
|
|
5555
5803
|
}
|
|
5556
5804
|
}
|
|
5805
|
+
return { handlerWrappers, upgradeWrappers };
|
|
5557
5806
|
}
|
|
5558
5807
|
function resolveDeclaration(decl) {
|
|
5559
5808
|
if (typeof decl === "string") {
|
|
@@ -5590,10 +5839,11 @@ function isProductionMode(rootDir) {
|
|
|
5590
5839
|
return fs6.existsSync(path9.resolve(rootDir, "dist", "faapi-schema.js"));
|
|
5591
5840
|
}
|
|
5592
5841
|
function adjustForProd(patterns, appDir) {
|
|
5593
|
-
const prodPatterns = patterns.map(
|
|
5594
|
-
(p
|
|
5595
|
-
|
|
5596
|
-
|
|
5842
|
+
const prodPatterns = patterns.map((p) => p.replace(/\.ts$/g, ".js")).map((p) => {
|
|
5843
|
+
if (p.startsWith("dist/")) return p;
|
|
5844
|
+
return `dist/${p}`;
|
|
5845
|
+
});
|
|
5846
|
+
const prodAppDir = appDir === "." ? "dist" : `dist/${appDir}`;
|
|
5597
5847
|
return { patterns: prodPatterns, appDir: prodAppDir };
|
|
5598
5848
|
}
|
|
5599
5849
|
async function startCommand(argv) {
|
|
@@ -5637,6 +5887,7 @@ async function startCommand(argv) {
|
|
|
5637
5887
|
await generateTypes(sorted, rootDir, typesPath);
|
|
5638
5888
|
console.log(`- Types generated: ${typesPath}`);
|
|
5639
5889
|
}
|
|
5890
|
+
const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
|
|
5640
5891
|
const server = await startServer({
|
|
5641
5892
|
port: args.port,
|
|
5642
5893
|
routes: sorted,
|
|
@@ -5649,7 +5900,17 @@ async function startCommand(argv) {
|
|
|
5649
5900
|
config: config ?? void 0,
|
|
5650
5901
|
wsRoutes,
|
|
5651
5902
|
middlewares: config?.middlewares,
|
|
5652
|
-
injectors: config?.injectors
|
|
5903
|
+
injectors: config?.injectors,
|
|
5904
|
+
// beforeListen:加载插件并应用 handler 包装(在 server.listen 之前)
|
|
5905
|
+
beforeListen: async (server2) => {
|
|
5906
|
+
const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
|
|
5907
|
+
rootDir,
|
|
5908
|
+
routes: sorted,
|
|
5909
|
+
server: server2,
|
|
5910
|
+
config: pluginConfig
|
|
5911
|
+
});
|
|
5912
|
+
applyPluginWrappers(server2, handlerWrappers, upgradeWrappers);
|
|
5913
|
+
}
|
|
5653
5914
|
});
|
|
5654
5915
|
if (config?.lifecycle?.onReady) {
|
|
5655
5916
|
await config.lifecycle.onReady({
|
|
@@ -5682,15 +5943,6 @@ async function startCommand(argv) {
|
|
|
5682
5943
|
types: args.types
|
|
5683
5944
|
});
|
|
5684
5945
|
}
|
|
5685
|
-
const pluginConfig = config ? Object.fromEntries(
|
|
5686
|
-
Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))
|
|
5687
|
-
) : {};
|
|
5688
|
-
await loadPlugins(config?.plugins, {
|
|
5689
|
-
rootDir,
|
|
5690
|
-
routes: sorted,
|
|
5691
|
-
server,
|
|
5692
|
-
config: pluginConfig
|
|
5693
|
-
});
|
|
5694
5946
|
}
|
|
5695
5947
|
function isFaapiConfigKey(key) {
|
|
5696
5948
|
return FAAPI_CONFIG_KEYS.has(key);
|
|
@@ -5712,8 +5964,6 @@ var init_startCommand = __esm({
|
|
|
5712
5964
|
init_loadPlugins();
|
|
5713
5965
|
FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
5714
5966
|
"port",
|
|
5715
|
-
"appDir",
|
|
5716
|
-
"patterns",
|
|
5717
5967
|
"staticDir",
|
|
5718
5968
|
"cors",
|
|
5719
5969
|
"responseFormat",
|