@faapi/faapi 6.19.0 → 6.21.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 +339 -138
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -21
- package/dist/index.js +341 -142
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-DTqdo28V.d.ts → routeTypes-gyz4PMh7.d.ts} +46 -6
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +304 -108
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -711,8 +711,8 @@ var init_parseRouteFile = __esm({
|
|
|
711
711
|
// src/utils/importWithCacheBust.ts
|
|
712
712
|
import fs8 from "fs";
|
|
713
713
|
import { pathToFileURL } from "url";
|
|
714
|
-
function setLoadTimestamp(
|
|
715
|
-
loadTs =
|
|
714
|
+
function setLoadTimestamp(ts12) {
|
|
715
|
+
loadTs = ts12;
|
|
716
716
|
}
|
|
717
717
|
function getVitestImportActual() {
|
|
718
718
|
const vi = globalThis.vi;
|
|
@@ -2513,8 +2513,16 @@ var init_resolveInjection = __esm({
|
|
|
2513
2513
|
// Phase 2.3
|
|
2514
2514
|
tasks: "tasks",
|
|
2515
2515
|
// 任务子系统:TaskClient(入队/查询)
|
|
2516
|
-
log: "log"
|
|
2516
|
+
log: "log",
|
|
2517
2517
|
// 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
|
|
2518
|
+
// raw 系:恒原始输入(无 schema、无校验、管线永不转换),与 query/params/body
|
|
2519
|
+
// 的「校验转换后」口径二分互补
|
|
2520
|
+
rawQuery: "rawQuery",
|
|
2521
|
+
// URLSearchParams(原始查询参数)
|
|
2522
|
+
rawParams: "rawParams",
|
|
2523
|
+
// Record<string, string>(URL 原始路径段)
|
|
2524
|
+
rawBody: "rawBody"
|
|
2525
|
+
// string | undefined(请求体原始文本)
|
|
2518
2526
|
};
|
|
2519
2527
|
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
2520
2528
|
}
|
|
@@ -2572,7 +2580,19 @@ var init_analyzeInjection = __esm({
|
|
|
2572
2580
|
|
|
2573
2581
|
// src/cli/collectRouteSchemaSources.ts
|
|
2574
2582
|
import path11 from "path";
|
|
2575
|
-
|
|
2583
|
+
import ts8 from "typescript";
|
|
2584
|
+
function findExportedTypeName(sourceFile, name) {
|
|
2585
|
+
for (const statement of sourceFile.statements) {
|
|
2586
|
+
const modifiers = ts8.canHaveModifiers(statement) ? ts8.getModifiers(statement) : void 0;
|
|
2587
|
+
const isExported = modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
|
|
2588
|
+
if (!isExported) continue;
|
|
2589
|
+
if ((ts8.isInterfaceDeclaration(statement) || ts8.isTypeAliasDeclaration(statement)) && statement.name?.text === name) {
|
|
2590
|
+
return true;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return false;
|
|
2594
|
+
}
|
|
2595
|
+
function collectRouteSchemaSources(routes, rootDir, wsRoutes) {
|
|
2576
2596
|
const methodsByFile = /* @__PURE__ */ new Map();
|
|
2577
2597
|
for (const route of routes) {
|
|
2578
2598
|
const filePath = rootDir ? path11.resolve(rootDir, route.filePath) : route.filePath;
|
|
@@ -2583,9 +2603,17 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
2583
2603
|
}
|
|
2584
2604
|
entry.methods.add(route.method);
|
|
2585
2605
|
}
|
|
2586
|
-
const
|
|
2606
|
+
const wsFiles = [];
|
|
2607
|
+
for (const wsRoute of wsRoutes ?? []) {
|
|
2608
|
+
wsFiles.push({
|
|
2609
|
+
filePath: rootDir ? path11.resolve(rootDir, wsRoute.filePath) : wsRoute.filePath,
|
|
2610
|
+
urlPath: wsRoute.urlPath
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
const allFiles = [.../* @__PURE__ */ new Set([...methodsByFile.keys(), ...wsFiles.map((f) => f.filePath)])];
|
|
2614
|
+
const programByFile = createPrograms(allFiles);
|
|
2587
2615
|
const resolversByFile = /* @__PURE__ */ new Map();
|
|
2588
|
-
for (const filePath of
|
|
2616
|
+
for (const filePath of allFiles) {
|
|
2589
2617
|
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
2590
2618
|
}
|
|
2591
2619
|
const sources = [];
|
|
@@ -2617,6 +2645,50 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
2617
2645
|
typeInfo: resolver.resolve(paramsArg.typeName) ?? null
|
|
2618
2646
|
});
|
|
2619
2647
|
}
|
|
2648
|
+
if (inputType === "body") {
|
|
2649
|
+
const queryArg = meta.params.find((p) => p.type === "query");
|
|
2650
|
+
if (queryArg?.typeName) {
|
|
2651
|
+
sources.push({
|
|
2652
|
+
urlPath: entry.urlPath,
|
|
2653
|
+
filePath,
|
|
2654
|
+
schemaName: getSchemaName(method, "query"),
|
|
2655
|
+
typeInfo: resolver.resolve(queryArg.typeName) ?? null
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
} else if (hasBody(method)) {
|
|
2659
|
+
const bodyArg = meta.params.find((p) => p.type === "body") ?? meta.params.find((p) => p.type === "form");
|
|
2660
|
+
if (bodyArg?.typeName) {
|
|
2661
|
+
sources.push({
|
|
2662
|
+
urlPath: entry.urlPath,
|
|
2663
|
+
filePath,
|
|
2664
|
+
schemaName: getSchemaName(method, "body"),
|
|
2665
|
+
typeInfo: resolver.resolve(bodyArg.typeName) ?? null,
|
|
2666
|
+
coerce: bodyArg.type === "form" || void 0
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
for (const wsFile of wsFiles) {
|
|
2673
|
+
const program = programByFile.get(wsFile.filePath);
|
|
2674
|
+
const sourceFile = program?.getSourceFile(wsFile.filePath);
|
|
2675
|
+
if (!sourceFile) continue;
|
|
2676
|
+
const resolver = resolversByFile.get(wsFile.filePath);
|
|
2677
|
+
if (findExportedTypeName(sourceFile, "Query")) {
|
|
2678
|
+
sources.push({
|
|
2679
|
+
urlPath: wsFile.urlPath,
|
|
2680
|
+
filePath: wsFile.filePath,
|
|
2681
|
+
schemaName: getSchemaName("WS", "query"),
|
|
2682
|
+
typeInfo: resolver.resolve("Query") ?? null
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
if (findExportedTypeName(sourceFile, "Params")) {
|
|
2686
|
+
sources.push({
|
|
2687
|
+
urlPath: wsFile.urlPath,
|
|
2688
|
+
filePath: wsFile.filePath,
|
|
2689
|
+
schemaName: getSchemaName("WS", "params"),
|
|
2690
|
+
typeInfo: resolver.resolve("Params") ?? null
|
|
2691
|
+
});
|
|
2620
2692
|
}
|
|
2621
2693
|
}
|
|
2622
2694
|
return { sources, resolversByFile };
|
|
@@ -2705,9 +2777,9 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
2705
2777
|
lines.push(...schemaBlocks);
|
|
2706
2778
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
2707
2779
|
}
|
|
2708
|
-
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
2709
|
-
if (routes.length === 0) return;
|
|
2710
|
-
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
2780
|
+
async function generateSchemaFiles(routes, rootDir, dist, wsRoutes) {
|
|
2781
|
+
if (routes.length === 0 && (wsRoutes?.length ?? 0) === 0) return;
|
|
2782
|
+
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir, wsRoutes);
|
|
2711
2783
|
await generateZodArtifacts(sources, {
|
|
2712
2784
|
generateFileSource: generateSchemaFileSource,
|
|
2713
2785
|
resolversByFile,
|
|
@@ -2778,14 +2850,14 @@ var init_generateZodArtifacts = __esm({
|
|
|
2778
2850
|
});
|
|
2779
2851
|
|
|
2780
2852
|
// src/ast/jsDocMetadata.ts
|
|
2781
|
-
import
|
|
2853
|
+
import ts9 from "typescript";
|
|
2782
2854
|
function hasExportModifier(node) {
|
|
2783
|
-
if (!
|
|
2784
|
-
const modifiers =
|
|
2785
|
-
return !!modifiers?.some((m) => m.kind ===
|
|
2855
|
+
if (!ts9.canHaveModifiers(node)) return false;
|
|
2856
|
+
const modifiers = ts9.getModifiers(node);
|
|
2857
|
+
return !!modifiers?.some((m) => m.kind === ts9.SyntaxKind.ExportKeyword);
|
|
2786
2858
|
}
|
|
2787
2859
|
function getJSDocFromNode(node) {
|
|
2788
|
-
const apiDocs =
|
|
2860
|
+
const apiDocs = ts9.getJSDocCommentsAndTags(node).filter((entry) => ts9.isJSDoc(entry));
|
|
2789
2861
|
if (apiDocs.length > 0) return apiDocs[0];
|
|
2790
2862
|
const directDocs = node.jsDoc;
|
|
2791
2863
|
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
@@ -2816,7 +2888,7 @@ var init_jsDocMetadata = __esm({
|
|
|
2816
2888
|
});
|
|
2817
2889
|
|
|
2818
2890
|
// src/ast/extractToolMetadata.ts
|
|
2819
|
-
import
|
|
2891
|
+
import ts10 from "typescript";
|
|
2820
2892
|
function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
2821
2893
|
const sourceFile = program.getSourceFile(filePath);
|
|
2822
2894
|
if (!sourceFile) return null;
|
|
@@ -2837,18 +2909,18 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
|
2837
2909
|
}
|
|
2838
2910
|
function findExportedFunction(sourceFile, functionName) {
|
|
2839
2911
|
let result = null;
|
|
2840
|
-
|
|
2912
|
+
ts10.forEachChild(sourceFile, (node) => {
|
|
2841
2913
|
if (result) return;
|
|
2842
|
-
if (
|
|
2914
|
+
if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
2843
2915
|
result = { fn: node, jsDocOwner: node };
|
|
2844
2916
|
return;
|
|
2845
2917
|
}
|
|
2846
|
-
if (
|
|
2918
|
+
if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
|
|
2847
2919
|
for (const decl of node.declarationList.declarations) {
|
|
2848
2920
|
if (result) break;
|
|
2849
|
-
const nameText =
|
|
2921
|
+
const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
2850
2922
|
if (nameText !== functionName || !decl.initializer) continue;
|
|
2851
|
-
if (
|
|
2923
|
+
if (ts10.isArrowFunction(decl.initializer) || ts10.isFunctionExpression(decl.initializer)) {
|
|
2852
2924
|
result = { fn: decl.initializer, jsDocOwner: node };
|
|
2853
2925
|
}
|
|
2854
2926
|
}
|
|
@@ -2860,7 +2932,7 @@ function getFirstParamTypeName(fn, sourceFile) {
|
|
|
2860
2932
|
const firstParam = fn.parameters[0];
|
|
2861
2933
|
if (!firstParam) return void 0;
|
|
2862
2934
|
if (!firstParam.type) return void 0;
|
|
2863
|
-
if (!
|
|
2935
|
+
if (!ts10.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
2864
2936
|
return firstParam.type.typeName.getText(sourceFile);
|
|
2865
2937
|
}
|
|
2866
2938
|
var init_extractToolMetadata = __esm({
|
|
@@ -3070,7 +3142,7 @@ var init_scanAgents = __esm({
|
|
|
3070
3142
|
});
|
|
3071
3143
|
|
|
3072
3144
|
// src/ast/extractAgentMetadata.ts
|
|
3073
|
-
import
|
|
3145
|
+
import ts11 from "typescript";
|
|
3074
3146
|
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
3075
3147
|
const sourceFile = program.getSourceFile(filePath);
|
|
3076
3148
|
if (!sourceFile) return null;
|
|
@@ -3114,23 +3186,23 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
3114
3186
|
}
|
|
3115
3187
|
function findConfigExport(sourceFile) {
|
|
3116
3188
|
let result = null;
|
|
3117
|
-
|
|
3189
|
+
ts11.forEachChild(sourceFile, (node) => {
|
|
3118
3190
|
if (result) return;
|
|
3119
|
-
if (
|
|
3191
|
+
if (ts11.isVariableStatement(node) && hasExportModifier(node)) {
|
|
3120
3192
|
for (const decl of node.declarationList.declarations) {
|
|
3121
3193
|
if (result) break;
|
|
3122
|
-
const nameText =
|
|
3194
|
+
const nameText = ts11.isIdentifier(decl.name) ? decl.name.text : "";
|
|
3123
3195
|
if (nameText !== "config" || !decl.initializer) continue;
|
|
3124
|
-
if (
|
|
3196
|
+
if (ts11.isObjectLiteralExpression(decl.initializer)) {
|
|
3125
3197
|
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
3126
|
-
} else if (
|
|
3198
|
+
} else if (ts11.isArrowFunction(decl.initializer)) {
|
|
3127
3199
|
result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
|
|
3128
3200
|
} else {
|
|
3129
3201
|
result = { jsDocOwner: node, objectLiteral: null };
|
|
3130
3202
|
}
|
|
3131
3203
|
}
|
|
3132
3204
|
}
|
|
3133
|
-
if (
|
|
3205
|
+
if (ts11.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
|
|
3134
3206
|
const returnObj = getReturnObjectLiteral(node);
|
|
3135
3207
|
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
3136
3208
|
}
|
|
@@ -3140,12 +3212,12 @@ function findConfigExport(sourceFile) {
|
|
|
3140
3212
|
function getReturnObjectLiteral(fn) {
|
|
3141
3213
|
const body = fn.body;
|
|
3142
3214
|
if (!body) return null;
|
|
3143
|
-
if (
|
|
3215
|
+
if (ts11.isObjectLiteralExpression(body)) {
|
|
3144
3216
|
return body;
|
|
3145
3217
|
}
|
|
3146
|
-
if (
|
|
3218
|
+
if (ts11.isBlock(body)) {
|
|
3147
3219
|
for (const stmt of body.statements) {
|
|
3148
|
-
if (
|
|
3220
|
+
if (ts11.isReturnStatement(stmt) && stmt.expression && ts11.isObjectLiteralExpression(stmt.expression)) {
|
|
3149
3221
|
return stmt.expression;
|
|
3150
3222
|
}
|
|
3151
3223
|
}
|
|
@@ -3160,8 +3232,8 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
3160
3232
|
let maxTurns;
|
|
3161
3233
|
let inputDescription;
|
|
3162
3234
|
for (const prop of objLit.properties) {
|
|
3163
|
-
if (
|
|
3164
|
-
if (!
|
|
3235
|
+
if (ts11.isSpreadAssignment(prop)) continue;
|
|
3236
|
+
if (!ts11.isPropertyAssignment(prop)) {
|
|
3165
3237
|
throw SchemaExtractionError.at(
|
|
3166
3238
|
prop,
|
|
3167
3239
|
"config",
|
|
@@ -3212,13 +3284,13 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
3212
3284
|
return { systemPrompt, tools, agents, model, maxTurns, inputDescription };
|
|
3213
3285
|
}
|
|
3214
3286
|
function getPropertyName(name) {
|
|
3215
|
-
if (
|
|
3216
|
-
if (
|
|
3287
|
+
if (ts11.isIdentifier(name)) return name.text;
|
|
3288
|
+
if (ts11.isStringLiteral(name)) return name.text;
|
|
3217
3289
|
return null;
|
|
3218
3290
|
}
|
|
3219
3291
|
function extractStringValue(expr) {
|
|
3220
3292
|
if (isStringLikeLiteral(expr)) return expr.text;
|
|
3221
|
-
if (
|
|
3293
|
+
if (ts11.isBinaryExpression(expr) && expr.operatorToken.kind === ts11.SyntaxKind.PlusToken) {
|
|
3222
3294
|
const left = extractStringValue(expr.left);
|
|
3223
3295
|
if (left === void 0) return void 0;
|
|
3224
3296
|
const right = extractStringValue(expr.right);
|
|
@@ -3264,17 +3336,17 @@ function requireNumberValue(prop, fieldName, sourceFile) {
|
|
|
3264
3336
|
return value;
|
|
3265
3337
|
}
|
|
3266
3338
|
function extractNumberValue(expr) {
|
|
3267
|
-
if (
|
|
3339
|
+
if (ts11.isNumericLiteral(expr)) {
|
|
3268
3340
|
const num = Number(expr.text);
|
|
3269
3341
|
return Number.isNaN(num) ? void 0 : num;
|
|
3270
3342
|
}
|
|
3271
3343
|
return void 0;
|
|
3272
3344
|
}
|
|
3273
3345
|
function isStringLikeLiteral(node) {
|
|
3274
|
-
return
|
|
3346
|
+
return ts11.isStringLiteral(node) || ts11.isNoSubstitutionTemplateLiteral(node);
|
|
3275
3347
|
}
|
|
3276
3348
|
function extractStringArrayValue(expr) {
|
|
3277
|
-
if (!
|
|
3349
|
+
if (!ts11.isArrayLiteralExpression(expr)) return void 0;
|
|
3278
3350
|
const values = [];
|
|
3279
3351
|
for (const element of expr.elements) {
|
|
3280
3352
|
const value = extractStringValue(element);
|
|
@@ -3762,7 +3834,7 @@ function clearGeneratedSchemas() {
|
|
|
3762
3834
|
state.generatedSchemas.clear();
|
|
3763
3835
|
state.inFlightSchemaGenerations.clear();
|
|
3764
3836
|
}
|
|
3765
|
-
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
3837
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist, wsRoutes) {
|
|
3766
3838
|
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
3767
3839
|
if (inFlight2) {
|
|
3768
3840
|
await inFlight2;
|
|
@@ -3781,13 +3853,15 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
3781
3853
|
return false;
|
|
3782
3854
|
}
|
|
3783
3855
|
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
3784
|
-
|
|
3856
|
+
const fileWsRoutes = (wsRoutes ?? []).filter((r) => r.filePath === routeFilePath);
|
|
3857
|
+
if (fileRoutes.length === 0 && fileWsRoutes.length === 0) {
|
|
3785
3858
|
return false;
|
|
3786
3859
|
}
|
|
3787
3860
|
const sourceRelPath = path20.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3788
3861
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3862
|
+
const sourceWsRoutes = fileWsRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3789
3863
|
const generatePromise = (async () => {
|
|
3790
|
-
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
3864
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist, sourceWsRoutes);
|
|
3791
3865
|
state.generatedSchemas.add(schemaPath);
|
|
3792
3866
|
})();
|
|
3793
3867
|
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
@@ -6292,17 +6366,24 @@ function encodeSseEvent(event) {
|
|
|
6292
6366
|
out += "\n";
|
|
6293
6367
|
return out;
|
|
6294
6368
|
}
|
|
6295
|
-
function createSseWriter() {
|
|
6369
|
+
function createSseWriter(options = {}) {
|
|
6370
|
+
const { onFirstWrite } = options;
|
|
6296
6371
|
const encoder = new TextEncoder();
|
|
6297
6372
|
let controller = null;
|
|
6298
6373
|
let closed = false;
|
|
6299
6374
|
let aborted = false;
|
|
6375
|
+
let firstWriteTriggered = false;
|
|
6300
6376
|
let drainWaiters = [];
|
|
6301
6377
|
const resolveDrainWaiters = () => {
|
|
6302
6378
|
const waiters = drainWaiters;
|
|
6303
6379
|
drainWaiters = [];
|
|
6304
6380
|
for (const resolve3 of waiters) resolve3();
|
|
6305
6381
|
};
|
|
6382
|
+
const triggerFirstWrite = () => {
|
|
6383
|
+
if (firstWriteTriggered) return;
|
|
6384
|
+
firstWriteTriggered = true;
|
|
6385
|
+
onFirstWrite?.(response);
|
|
6386
|
+
};
|
|
6306
6387
|
const stream = new ReadableStream(
|
|
6307
6388
|
{
|
|
6308
6389
|
start(c) {
|
|
@@ -6336,11 +6417,13 @@ function createSseWriter() {
|
|
|
6336
6417
|
if (closed || !controller) return;
|
|
6337
6418
|
const text = encodeSseEvent(event);
|
|
6338
6419
|
controller.enqueue(encoder.encode(text));
|
|
6420
|
+
triggerFirstWrite();
|
|
6339
6421
|
},
|
|
6340
6422
|
sendRaw(chunk) {
|
|
6341
6423
|
if (closed || !controller) return;
|
|
6342
6424
|
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
6343
6425
|
controller.enqueue(bytes);
|
|
6426
|
+
triggerFirstWrite();
|
|
6344
6427
|
},
|
|
6345
6428
|
sendError(error) {
|
|
6346
6429
|
if (closed || !controller) return;
|
|
@@ -6348,6 +6431,7 @@ function createSseWriter() {
|
|
|
6348
6431
|
const text = encodeSseEvent({ event: "error", data: message });
|
|
6349
6432
|
try {
|
|
6350
6433
|
controller.enqueue(encoder.encode(text));
|
|
6434
|
+
triggerFirstWrite();
|
|
6351
6435
|
} finally {
|
|
6352
6436
|
writer.close();
|
|
6353
6437
|
}
|
|
@@ -6795,6 +6879,32 @@ var init_logger = __esm({
|
|
|
6795
6879
|
}
|
|
6796
6880
|
});
|
|
6797
6881
|
|
|
6882
|
+
// src/utils/queryToObject.ts
|
|
6883
|
+
function queryToObject(params) {
|
|
6884
|
+
const cached = queryCache.get(params);
|
|
6885
|
+
if (cached) return cached;
|
|
6886
|
+
const result = {};
|
|
6887
|
+
for (const [key, value] of params) {
|
|
6888
|
+
const existing = result[key];
|
|
6889
|
+
if (existing === void 0) {
|
|
6890
|
+
result[key] = value;
|
|
6891
|
+
} else if (Array.isArray(existing)) {
|
|
6892
|
+
existing.push(value);
|
|
6893
|
+
} else {
|
|
6894
|
+
result[key] = [existing, value];
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
queryCache.set(params, result);
|
|
6898
|
+
return result;
|
|
6899
|
+
}
|
|
6900
|
+
var queryCache;
|
|
6901
|
+
var init_queryToObject = __esm({
|
|
6902
|
+
"src/utils/queryToObject.ts"() {
|
|
6903
|
+
"use strict";
|
|
6904
|
+
queryCache = /* @__PURE__ */ new WeakMap();
|
|
6905
|
+
}
|
|
6906
|
+
});
|
|
6907
|
+
|
|
6798
6908
|
// src/runtime/createContext.ts
|
|
6799
6909
|
function resolveRequestId(request) {
|
|
6800
6910
|
const header = request.headers.get("x-request-id");
|
|
@@ -6844,7 +6954,11 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
6844
6954
|
log,
|
|
6845
6955
|
request,
|
|
6846
6956
|
params,
|
|
6847
|
-
|
|
6957
|
+
rawParams: params,
|
|
6958
|
+
// ctx.query 为对象口径(createContext 挂原始 string 值对象;handler 声明
|
|
6959
|
+
// query 类型时管线校验后替换为转换值)。原始 URLSearchParams 恒在 rawQuery。
|
|
6960
|
+
query: queryToObject(url.searchParams),
|
|
6961
|
+
rawQuery: url.searchParams,
|
|
6848
6962
|
headers: request.headers,
|
|
6849
6963
|
method: request.method,
|
|
6850
6964
|
path: url.pathname,
|
|
@@ -6892,13 +7006,19 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
6892
7006
|
/**
|
|
6893
7007
|
* 创建 SSE writer,用于流式推送事件
|
|
6894
7008
|
*
|
|
6895
|
-
* handler 调用此方法后,通过返回的 writer
|
|
6896
|
-
*
|
|
7009
|
+
* handler 调用此方法后,通过返回的 writer 推送事件。HTTP 服务场景下首次
|
|
7010
|
+
* 写入(send/sendRaw/sendError)即触发 server 层提前接管钩子,把 writer.response
|
|
7011
|
+
* 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。
|
|
7012
|
+
* 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。
|
|
6897
7013
|
*
|
|
6898
7014
|
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
6899
7015
|
*/
|
|
6900
7016
|
sse() {
|
|
6901
|
-
const writer = createSseWriter(
|
|
7017
|
+
const writer = createSseWriter({
|
|
7018
|
+
onFirstWrite: (response) => {
|
|
7019
|
+
ctx.__earlyRespond?.(response);
|
|
7020
|
+
}
|
|
7021
|
+
});
|
|
6902
7022
|
const ctxWithSse = ctx;
|
|
6903
7023
|
ctxWithSse.__sseResponse = writer.response;
|
|
6904
7024
|
ctxWithSse.__sseWriter = writer;
|
|
@@ -6950,32 +7070,7 @@ var init_createContext = __esm({
|
|
|
6950
7070
|
init_sse();
|
|
6951
7071
|
init_responseFormatter();
|
|
6952
7072
|
init_logger();
|
|
6953
|
-
|
|
6954
|
-
});
|
|
6955
|
-
|
|
6956
|
-
// src/utils/queryToObject.ts
|
|
6957
|
-
function queryToObject(params) {
|
|
6958
|
-
const cached = queryCache.get(params);
|
|
6959
|
-
if (cached) return cached;
|
|
6960
|
-
const result = {};
|
|
6961
|
-
for (const [key, value] of params) {
|
|
6962
|
-
const existing = result[key];
|
|
6963
|
-
if (existing === void 0) {
|
|
6964
|
-
result[key] = value;
|
|
6965
|
-
} else if (Array.isArray(existing)) {
|
|
6966
|
-
existing.push(value);
|
|
6967
|
-
} else {
|
|
6968
|
-
result[key] = [existing, value];
|
|
6969
|
-
}
|
|
6970
|
-
}
|
|
6971
|
-
queryCache.set(params, result);
|
|
6972
|
-
return result;
|
|
6973
|
-
}
|
|
6974
|
-
var queryCache;
|
|
6975
|
-
var init_queryToObject = __esm({
|
|
6976
|
-
"src/utils/queryToObject.ts"() {
|
|
6977
|
-
"use strict";
|
|
6978
|
-
queryCache = /* @__PURE__ */ new WeakMap();
|
|
7073
|
+
init_queryToObject();
|
|
6979
7074
|
}
|
|
6980
7075
|
});
|
|
6981
7076
|
|
|
@@ -7033,46 +7128,29 @@ var init_parseMultipart = __esm({
|
|
|
7033
7128
|
async function resolveInputFromUrl(method, request, url) {
|
|
7034
7129
|
const inputType = getInputTypeForMethod(method);
|
|
7035
7130
|
if (inputType === "body") {
|
|
7036
|
-
|
|
7037
|
-
if (contentType.includes("multipart/form-data")) {
|
|
7038
|
-
return parseMultipart(request);
|
|
7039
|
-
}
|
|
7040
|
-
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
7041
|
-
const text2 = await request.text();
|
|
7042
|
-
if (isBlankText(text2)) return null;
|
|
7043
|
-
const params = new URLSearchParams(text2);
|
|
7044
|
-
const obj = {};
|
|
7045
|
-
for (const [key, value] of params) {
|
|
7046
|
-
obj[key] = value;
|
|
7047
|
-
}
|
|
7048
|
-
return obj;
|
|
7049
|
-
}
|
|
7050
|
-
const text = await request.text();
|
|
7051
|
-
if (isBlankText(text)) {
|
|
7052
|
-
return null;
|
|
7053
|
-
}
|
|
7054
|
-
const result = parseJsonBody(text);
|
|
7055
|
-
if (!result.success) {
|
|
7056
|
-
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
7057
|
-
{
|
|
7058
|
-
path: "body",
|
|
7059
|
-
code: "INVALID_FORMAT",
|
|
7060
|
-
expected: "JSON",
|
|
7061
|
-
received: "text",
|
|
7062
|
-
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
7063
|
-
}
|
|
7064
|
-
]);
|
|
7065
|
-
}
|
|
7066
|
-
return result.data;
|
|
7131
|
+
return parseBodyByContentType(request);
|
|
7067
7132
|
}
|
|
7068
|
-
return queryToObject(url.searchParams);
|
|
7133
|
+
return { input: queryToObject(url.searchParams) };
|
|
7069
7134
|
}
|
|
7070
|
-
function
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7135
|
+
async function parseBodyByContentType(request) {
|
|
7136
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
7137
|
+
if (contentType.includes("multipart/form-data")) {
|
|
7138
|
+
return { input: await parseMultipart(request) };
|
|
7139
|
+
}
|
|
7140
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
7141
|
+
const text2 = await request.text();
|
|
7142
|
+
if (isBlankText(text2)) return { input: null };
|
|
7143
|
+
const params = new URLSearchParams(text2);
|
|
7144
|
+
const obj = {};
|
|
7145
|
+
for (const [key, value] of params) {
|
|
7146
|
+
obj[key] = value;
|
|
7147
|
+
}
|
|
7148
|
+
return { input: obj, rawBody: text2 };
|
|
7149
|
+
}
|
|
7074
7150
|
const text = await request.text();
|
|
7075
|
-
if (isBlankText(text))
|
|
7151
|
+
if (isBlankText(text)) {
|
|
7152
|
+
return { input: null };
|
|
7153
|
+
}
|
|
7076
7154
|
const result = parseJsonBody(text);
|
|
7077
7155
|
if (!result.success) {
|
|
7078
7156
|
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
@@ -7085,7 +7163,13 @@ async function resolveBodyForQueryMethod(request) {
|
|
|
7085
7163
|
}
|
|
7086
7164
|
]);
|
|
7087
7165
|
}
|
|
7088
|
-
return result.data;
|
|
7166
|
+
return { input: result.data, rawBody: text };
|
|
7167
|
+
}
|
|
7168
|
+
function isBlankText(text) {
|
|
7169
|
+
return text.length === 0 || !/\S/.test(text);
|
|
7170
|
+
}
|
|
7171
|
+
async function resolveBodyForQueryMethod(request) {
|
|
7172
|
+
return parseBodyByContentType(request);
|
|
7089
7173
|
}
|
|
7090
7174
|
var init_resolveInput = __esm({
|
|
7091
7175
|
"src/runtime/resolveInput.ts"() {
|
|
@@ -7483,12 +7567,17 @@ var init_agentHandle = __esm({
|
|
|
7483
7567
|
// src/injection/injectParams.ts
|
|
7484
7568
|
function getBuiltinInjectionValue(type, ctx, body) {
|
|
7485
7569
|
switch (type) {
|
|
7486
|
-
case "query":
|
|
7487
|
-
|
|
7488
|
-
return validated ?? queryToObject(ctx.query);
|
|
7489
|
-
}
|
|
7570
|
+
case "query":
|
|
7571
|
+
return ctx.query;
|
|
7490
7572
|
case "params":
|
|
7491
7573
|
return ctx.params;
|
|
7574
|
+
// raw 系:恒原始,管线永不覆盖(ctx 字段即注入值)
|
|
7575
|
+
case "rawQuery":
|
|
7576
|
+
return ctx.rawQuery;
|
|
7577
|
+
case "rawParams":
|
|
7578
|
+
return ctx.rawParams;
|
|
7579
|
+
case "rawBody":
|
|
7580
|
+
return ctx.rawBody;
|
|
7492
7581
|
case "headers":
|
|
7493
7582
|
return ctx.headers;
|
|
7494
7583
|
case "context":
|
|
@@ -7554,7 +7643,6 @@ var init_injectParams = __esm({
|
|
|
7554
7643
|
"src/injection/injectParams.ts"() {
|
|
7555
7644
|
"use strict";
|
|
7556
7645
|
init_resolveInjection();
|
|
7557
|
-
init_queryToObject();
|
|
7558
7646
|
init_agentRegistry();
|
|
7559
7647
|
init_agentHandle();
|
|
7560
7648
|
}
|
|
@@ -8402,6 +8490,7 @@ function attachWebSocket(options) {
|
|
|
8402
8490
|
server,
|
|
8403
8491
|
routesRef,
|
|
8404
8492
|
rootDir,
|
|
8493
|
+
dist,
|
|
8405
8494
|
config,
|
|
8406
8495
|
globalMiddlewares,
|
|
8407
8496
|
trustedProxy = false,
|
|
@@ -8436,12 +8525,50 @@ function attachWebSocket(options) {
|
|
|
8436
8525
|
const { route, params } = match;
|
|
8437
8526
|
const headers = nodeHttpToWebHeaders(req);
|
|
8438
8527
|
const host = req.headers.host ?? "localhost";
|
|
8439
|
-
const
|
|
8440
|
-
const request = new Request(
|
|
8528
|
+
const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
|
|
8529
|
+
const request = new Request(fullUrl.toString(), { method: "GET", headers });
|
|
8441
8530
|
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
|
|
8442
8531
|
const meta = ctx.meta;
|
|
8443
8532
|
let upgraded = false;
|
|
8444
8533
|
const finalHandler = async () => {
|
|
8534
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
8535
|
+
if (fs19.existsSync(schemaPath)) {
|
|
8536
|
+
if (isDevOnDemandEnabled()) {
|
|
8537
|
+
const devDist = getDevDist();
|
|
8538
|
+
if (devDist) {
|
|
8539
|
+
await ensureSchemaGenerated(
|
|
8540
|
+
schemaPath,
|
|
8541
|
+
route.filePath,
|
|
8542
|
+
routesRef.current,
|
|
8543
|
+
rootDir,
|
|
8544
|
+
dist,
|
|
8545
|
+
routesRef.wsCurrent
|
|
8546
|
+
);
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
const searchParams = fullUrl.searchParams;
|
|
8550
|
+
const rawQueryObject = queryToObject(searchParams);
|
|
8551
|
+
const paramsResult = await validateInput(schemaPath, "WS", "params", params);
|
|
8552
|
+
if (!paramsResult.valid) {
|
|
8553
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", paramsResult.issues);
|
|
8554
|
+
}
|
|
8555
|
+
if (paramsResult.data !== params && typeof paramsResult.data === "object" && paramsResult.data !== null) {
|
|
8556
|
+
ctx.params = {
|
|
8557
|
+
...params,
|
|
8558
|
+
...paramsResult.data
|
|
8559
|
+
};
|
|
8560
|
+
}
|
|
8561
|
+
const queryResult = await validateInput(schemaPath, "WS", "query", rawQueryObject);
|
|
8562
|
+
if (!queryResult.valid) {
|
|
8563
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", queryResult.issues);
|
|
8564
|
+
}
|
|
8565
|
+
if (queryResult.data !== rawQueryObject && typeof queryResult.data === "object" && queryResult.data !== null) {
|
|
8566
|
+
ctx.query = {
|
|
8567
|
+
...rawQueryObject,
|
|
8568
|
+
...queryResult.data
|
|
8569
|
+
};
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8445
8572
|
let handlers;
|
|
8446
8573
|
try {
|
|
8447
8574
|
const absoluteFilePath = path25.resolve(rootDir, route.filePath);
|
|
@@ -8505,7 +8632,11 @@ var init_handleWsUpgrade = __esm({
|
|
|
8505
8632
|
init_invokeHandler();
|
|
8506
8633
|
init_importWithCacheBust();
|
|
8507
8634
|
init_getClientIp();
|
|
8635
|
+
init_queryToObject();
|
|
8508
8636
|
init_serverUtils();
|
|
8637
|
+
init_validateInput();
|
|
8638
|
+
init_httpErrors();
|
|
8639
|
+
init_generateSchemaFiles();
|
|
8509
8640
|
init_compileOnDemand();
|
|
8510
8641
|
init_loadMiddlewares();
|
|
8511
8642
|
init_pendingMeta();
|
|
@@ -8669,7 +8800,8 @@ function createServer(options) {
|
|
|
8669
8800
|
globalInjectors,
|
|
8670
8801
|
bodyLimit,
|
|
8671
8802
|
trustedProxy,
|
|
8672
|
-
registries
|
|
8803
|
+
registries,
|
|
8804
|
+
routesRef.wsCurrent
|
|
8673
8805
|
).catch((err) => {
|
|
8674
8806
|
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
8675
8807
|
try {
|
|
@@ -8687,6 +8819,7 @@ function createServer(options) {
|
|
|
8687
8819
|
server,
|
|
8688
8820
|
routesRef,
|
|
8689
8821
|
rootDir,
|
|
8822
|
+
dist,
|
|
8690
8823
|
config,
|
|
8691
8824
|
globalMiddlewares,
|
|
8692
8825
|
trustedProxy,
|
|
@@ -8694,14 +8827,13 @@ function createServer(options) {
|
|
|
8694
8827
|
});
|
|
8695
8828
|
return { server, routesRef };
|
|
8696
8829
|
}
|
|
8697
|
-
function prepareRequest(req,
|
|
8698
|
-
const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
|
|
8830
|
+
function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
|
|
8699
8831
|
const method = request.method.toUpperCase();
|
|
8700
8832
|
const urlPath = url.pathname;
|
|
8701
8833
|
const ctx = createContextFromUrl(
|
|
8702
8834
|
request,
|
|
8703
8835
|
url,
|
|
8704
|
-
{},
|
|
8836
|
+
preMatched?.params ?? {},
|
|
8705
8837
|
config,
|
|
8706
8838
|
getClientIp(req, trustedProxy),
|
|
8707
8839
|
registries
|
|
@@ -8730,19 +8862,33 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
8730
8862
|
return cached;
|
|
8731
8863
|
}
|
|
8732
8864
|
function createRoutePipeline(opts) {
|
|
8733
|
-
const {
|
|
8865
|
+
const {
|
|
8866
|
+
routes,
|
|
8867
|
+
method,
|
|
8868
|
+
urlPath,
|
|
8869
|
+
url,
|
|
8870
|
+
ctx,
|
|
8871
|
+
request,
|
|
8872
|
+
rootDir,
|
|
8873
|
+
dist,
|
|
8874
|
+
globalInjectors,
|
|
8875
|
+
preMatched,
|
|
8876
|
+
wsRoutes
|
|
8877
|
+
} = opts;
|
|
8734
8878
|
return async () => {
|
|
8735
|
-
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
8736
|
-
ctx.params = match.params;
|
|
8879
|
+
const match = preMatched ?? resolveRouteOrThrow(routes, method, urlPath);
|
|
8737
8880
|
const { route } = match;
|
|
8738
8881
|
const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);
|
|
8739
8882
|
const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);
|
|
8740
|
-
const input = await resolveInputFromUrl(route.method, request, url);
|
|
8883
|
+
const { input, rawBody } = await resolveInputFromUrl(route.method, request, url);
|
|
8884
|
+
if (rawBody !== void 0) {
|
|
8885
|
+
ctx.rawBody = rawBody;
|
|
8886
|
+
}
|
|
8741
8887
|
const inputType = getInputTypeForMethod(route.method);
|
|
8742
8888
|
if (isDevOnDemandEnabled()) {
|
|
8743
8889
|
const devDist = getDevDist();
|
|
8744
8890
|
if (devDist) {
|
|
8745
|
-
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);
|
|
8891
|
+
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist, wsRoutes);
|
|
8746
8892
|
}
|
|
8747
8893
|
}
|
|
8748
8894
|
const result = await validateInput(schemaPath, route.method, inputType, input);
|
|
@@ -8750,7 +8896,7 @@ function createRoutePipeline(opts) {
|
|
|
8750
8896
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
8751
8897
|
}
|
|
8752
8898
|
if (inputType === "query" && result.data !== input && typeof result.data === "object" && result.data !== null) {
|
|
8753
|
-
ctx.
|
|
8899
|
+
ctx.query = {
|
|
8754
8900
|
...input,
|
|
8755
8901
|
...result.data
|
|
8756
8902
|
};
|
|
@@ -8765,7 +8911,36 @@ function createRoutePipeline(opts) {
|
|
|
8765
8911
|
...paramsResult.data
|
|
8766
8912
|
} : match.params;
|
|
8767
8913
|
}
|
|
8768
|
-
|
|
8914
|
+
if (inputType === "body") {
|
|
8915
|
+
const rawQuery = queryToObject(url.searchParams);
|
|
8916
|
+
const queryResult = await validateInput(schemaPath, route.method, "query", rawQuery);
|
|
8917
|
+
if (!queryResult.valid) {
|
|
8918
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", queryResult.issues);
|
|
8919
|
+
}
|
|
8920
|
+
if (queryResult.data !== rawQuery && typeof queryResult.data === "object" && queryResult.data !== null) {
|
|
8921
|
+
ctx.query = {
|
|
8922
|
+
...rawQuery,
|
|
8923
|
+
...queryResult.data
|
|
8924
|
+
};
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
let body;
|
|
8928
|
+
if (inputType === "query" && hasBody(route.method)) {
|
|
8929
|
+
const parsed = await resolveBodyForQueryMethod(request);
|
|
8930
|
+
if (parsed.rawBody !== void 0) {
|
|
8931
|
+
ctx.rawBody = parsed.rawBody;
|
|
8932
|
+
}
|
|
8933
|
+
const bodyResult = await validateInput(schemaPath, route.method, "body", parsed.input);
|
|
8934
|
+
if (!bodyResult.valid) {
|
|
8935
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", bodyResult.issues);
|
|
8936
|
+
}
|
|
8937
|
+
body = bodyResult.data;
|
|
8938
|
+
} else if (hasBody(route.method)) {
|
|
8939
|
+
body = result.data;
|
|
8940
|
+
}
|
|
8941
|
+
if (body !== void 0 && body !== null) {
|
|
8942
|
+
ctx.body = body;
|
|
8943
|
+
}
|
|
8769
8944
|
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
8770
8945
|
await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);
|
|
8771
8946
|
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
@@ -8801,25 +8976,37 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
8801
8976
|
}
|
|
8802
8977
|
}
|
|
8803
8978
|
}
|
|
8804
|
-
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries) {
|
|
8979
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
|
|
8805
8980
|
let meta = { headers: {}, setCookies: [] };
|
|
8806
8981
|
let ctx;
|
|
8982
|
+
let sseEarlySent = false;
|
|
8807
8983
|
const abortController = new AbortController();
|
|
8808
8984
|
res.on("close", () => {
|
|
8809
8985
|
if (!res.writableEnded) abortController.abort();
|
|
8810
8986
|
});
|
|
8811
8987
|
try {
|
|
8988
|
+
const { request, url } = toWebRequest(req, bodyLimit, abortController.signal);
|
|
8989
|
+
const method = request.method.toUpperCase();
|
|
8990
|
+
const urlPath = url.pathname;
|
|
8991
|
+
const preMatched = matchRoute(routes, method, urlPath);
|
|
8812
8992
|
const prepared = prepareRequest(
|
|
8813
8993
|
req,
|
|
8994
|
+
request,
|
|
8995
|
+
url,
|
|
8814
8996
|
config,
|
|
8815
|
-
bodyLimit,
|
|
8816
8997
|
trustedProxy,
|
|
8817
8998
|
registries,
|
|
8818
|
-
|
|
8999
|
+
preMatched
|
|
8819
9000
|
);
|
|
8820
9001
|
ctx = prepared.ctx;
|
|
8821
9002
|
meta = prepared.meta;
|
|
8822
|
-
|
|
9003
|
+
ctx.__earlyRespond = (response2) => {
|
|
9004
|
+
if (sseEarlySent) return;
|
|
9005
|
+
sseEarlySent = true;
|
|
9006
|
+
void sendNodeResponse(mergeMeta(response2, meta), res).catch(() => {
|
|
9007
|
+
if (!res.writableEnded) res.destroy();
|
|
9008
|
+
});
|
|
9009
|
+
};
|
|
8823
9010
|
const routePipeline = createRoutePipeline({
|
|
8824
9011
|
routes,
|
|
8825
9012
|
method,
|
|
@@ -8829,12 +9016,25 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
8829
9016
|
request,
|
|
8830
9017
|
rootDir,
|
|
8831
9018
|
dist,
|
|
8832
|
-
globalInjectors
|
|
9019
|
+
globalInjectors,
|
|
9020
|
+
preMatched,
|
|
9021
|
+
wsRoutes
|
|
8833
9022
|
});
|
|
8834
9023
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
8835
|
-
|
|
9024
|
+
if (!sseEarlySent) {
|
|
9025
|
+
await sendSuccessResponse(response, res);
|
|
9026
|
+
}
|
|
8836
9027
|
} catch (err) {
|
|
8837
9028
|
if (res.destroyed || res.writableEnded) return;
|
|
9029
|
+
if (sseEarlySent) {
|
|
9030
|
+
if (onError && ctx) {
|
|
9031
|
+
try {
|
|
9032
|
+
await onError(err, ctx);
|
|
9033
|
+
} catch {
|
|
9034
|
+
}
|
|
9035
|
+
}
|
|
9036
|
+
return;
|
|
9037
|
+
}
|
|
8838
9038
|
if (err instanceof PayloadTooLargeError) {
|
|
8839
9039
|
res.setHeader("Connection", "close");
|
|
8840
9040
|
res.once("finish", () => req.destroy());
|
|
@@ -8855,6 +9055,7 @@ var init_createServer = __esm({
|
|
|
8855
9055
|
init_httpErrors();
|
|
8856
9056
|
init_validateInput();
|
|
8857
9057
|
init_inputType();
|
|
9058
|
+
init_queryToObject();
|
|
8858
9059
|
init_getClientIp();
|
|
8859
9060
|
init_cors();
|
|
8860
9061
|
init_helmet();
|
|
@@ -10261,14 +10462,14 @@ async function createDevApp(options) {
|
|
|
10261
10462
|
void (async () => {
|
|
10262
10463
|
try {
|
|
10263
10464
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
10264
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
10465
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
10265
10466
|
} catch (err) {
|
|
10266
10467
|
console.error("[faapi] Background schema regeneration failed:", err);
|
|
10267
10468
|
}
|
|
10268
10469
|
})();
|
|
10269
10470
|
} else {
|
|
10270
10471
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
10271
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
10472
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
10272
10473
|
}
|
|
10273
10474
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
10274
10475
|
};
|
|
@@ -10503,7 +10704,7 @@ async function buildCommand(options) {
|
|
|
10503
10704
|
console.log(` Found ${sorted.length} routes, ${wsRoutes.length} WS routes`);
|
|
10504
10705
|
reportRouteConflicts(sorted);
|
|
10505
10706
|
console.log("\n[4/8] Generating schema...");
|
|
10506
|
-
await generateSchemaFiles(sorted, rootDir, outdir);
|
|
10707
|
+
await generateSchemaFiles(sorted, rootDir, outdir, wsRoutes);
|
|
10507
10708
|
console.log(` Schema: zod.js files under ${path33.resolve(rootDir, outdir)}`);
|
|
10508
10709
|
console.log("\n[5/8] Generating routes manifest...");
|
|
10509
10710
|
const routesPath = path33.resolve(rootDir, outdir, "faapi-routes.js");
|