@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/index.js
CHANGED
|
@@ -1124,8 +1124,16 @@ var init_resolveInjection = __esm({
|
|
|
1124
1124
|
// Phase 2.3
|
|
1125
1125
|
tasks: "tasks",
|
|
1126
1126
|
// 任务子系统:TaskClient(入队/查询)
|
|
1127
|
-
log: "log"
|
|
1127
|
+
log: "log",
|
|
1128
1128
|
// 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
|
|
1129
|
+
// raw 系:恒原始输入(无 schema、无校验、管线永不转换),与 query/params/body
|
|
1130
|
+
// 的「校验转换后」口径二分互补
|
|
1131
|
+
rawQuery: "rawQuery",
|
|
1132
|
+
// URLSearchParams(原始查询参数)
|
|
1133
|
+
rawParams: "rawParams",
|
|
1134
|
+
// Record<string, string>(URL 原始路径段)
|
|
1135
|
+
rawBody: "rawBody"
|
|
1136
|
+
// string | undefined(请求体原始文本)
|
|
1129
1137
|
};
|
|
1130
1138
|
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
1131
1139
|
}
|
|
@@ -1183,7 +1191,19 @@ var init_analyzeInjection = __esm({
|
|
|
1183
1191
|
|
|
1184
1192
|
// src/cli/collectRouteSchemaSources.ts
|
|
1185
1193
|
import path2 from "path";
|
|
1186
|
-
|
|
1194
|
+
import ts6 from "typescript";
|
|
1195
|
+
function findExportedTypeName(sourceFile, name) {
|
|
1196
|
+
for (const statement of sourceFile.statements) {
|
|
1197
|
+
const modifiers = ts6.canHaveModifiers(statement) ? ts6.getModifiers(statement) : void 0;
|
|
1198
|
+
const isExported = modifiers?.some((m) => m.kind === ts6.SyntaxKind.ExportKeyword);
|
|
1199
|
+
if (!isExported) continue;
|
|
1200
|
+
if ((ts6.isInterfaceDeclaration(statement) || ts6.isTypeAliasDeclaration(statement)) && statement.name?.text === name) {
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
function collectRouteSchemaSources(routes, rootDir, wsRoutes) {
|
|
1187
1207
|
const methodsByFile = /* @__PURE__ */ new Map();
|
|
1188
1208
|
for (const route of routes) {
|
|
1189
1209
|
const filePath = rootDir ? path2.resolve(rootDir, route.filePath) : route.filePath;
|
|
@@ -1194,9 +1214,17 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1194
1214
|
}
|
|
1195
1215
|
entry.methods.add(route.method);
|
|
1196
1216
|
}
|
|
1197
|
-
const
|
|
1217
|
+
const wsFiles = [];
|
|
1218
|
+
for (const wsRoute of wsRoutes ?? []) {
|
|
1219
|
+
wsFiles.push({
|
|
1220
|
+
filePath: rootDir ? path2.resolve(rootDir, wsRoute.filePath) : wsRoute.filePath,
|
|
1221
|
+
urlPath: wsRoute.urlPath
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
const allFiles = [.../* @__PURE__ */ new Set([...methodsByFile.keys(), ...wsFiles.map((f) => f.filePath)])];
|
|
1225
|
+
const programByFile = createPrograms(allFiles);
|
|
1198
1226
|
const resolversByFile = /* @__PURE__ */ new Map();
|
|
1199
|
-
for (const filePath of
|
|
1227
|
+
for (const filePath of allFiles) {
|
|
1200
1228
|
resolversByFile.set(filePath, createLazyTypeResolver(programByFile.get(filePath), filePath));
|
|
1201
1229
|
}
|
|
1202
1230
|
const sources = [];
|
|
@@ -1228,6 +1256,50 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1228
1256
|
typeInfo: resolver.resolve(paramsArg.typeName) ?? null
|
|
1229
1257
|
});
|
|
1230
1258
|
}
|
|
1259
|
+
if (inputType === "body") {
|
|
1260
|
+
const queryArg = meta.params.find((p) => p.type === "query");
|
|
1261
|
+
if (queryArg?.typeName) {
|
|
1262
|
+
sources.push({
|
|
1263
|
+
urlPath: entry.urlPath,
|
|
1264
|
+
filePath,
|
|
1265
|
+
schemaName: getSchemaName(method, "query"),
|
|
1266
|
+
typeInfo: resolver.resolve(queryArg.typeName) ?? null
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
} else if (hasBody(method)) {
|
|
1270
|
+
const bodyArg = meta.params.find((p) => p.type === "body") ?? meta.params.find((p) => p.type === "form");
|
|
1271
|
+
if (bodyArg?.typeName) {
|
|
1272
|
+
sources.push({
|
|
1273
|
+
urlPath: entry.urlPath,
|
|
1274
|
+
filePath,
|
|
1275
|
+
schemaName: getSchemaName(method, "body"),
|
|
1276
|
+
typeInfo: resolver.resolve(bodyArg.typeName) ?? null,
|
|
1277
|
+
coerce: bodyArg.type === "form" || void 0
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
for (const wsFile of wsFiles) {
|
|
1284
|
+
const program = programByFile.get(wsFile.filePath);
|
|
1285
|
+
const sourceFile = program?.getSourceFile(wsFile.filePath);
|
|
1286
|
+
if (!sourceFile) continue;
|
|
1287
|
+
const resolver = resolversByFile.get(wsFile.filePath);
|
|
1288
|
+
if (findExportedTypeName(sourceFile, "Query")) {
|
|
1289
|
+
sources.push({
|
|
1290
|
+
urlPath: wsFile.urlPath,
|
|
1291
|
+
filePath: wsFile.filePath,
|
|
1292
|
+
schemaName: getSchemaName("WS", "query"),
|
|
1293
|
+
typeInfo: resolver.resolve("Query") ?? null
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
if (findExportedTypeName(sourceFile, "Params")) {
|
|
1297
|
+
sources.push({
|
|
1298
|
+
urlPath: wsFile.urlPath,
|
|
1299
|
+
filePath: wsFile.filePath,
|
|
1300
|
+
schemaName: getSchemaName("WS", "params"),
|
|
1301
|
+
typeInfo: resolver.resolve("Params") ?? null
|
|
1302
|
+
});
|
|
1231
1303
|
}
|
|
1232
1304
|
}
|
|
1233
1305
|
return { sources, resolversByFile };
|
|
@@ -1714,9 +1786,9 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
1714
1786
|
lines.push(...schemaBlocks);
|
|
1715
1787
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
1716
1788
|
}
|
|
1717
|
-
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
1718
|
-
if (routes.length === 0) return;
|
|
1719
|
-
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir);
|
|
1789
|
+
async function generateSchemaFiles(routes, rootDir, dist, wsRoutes) {
|
|
1790
|
+
if (routes.length === 0 && (wsRoutes?.length ?? 0) === 0) return;
|
|
1791
|
+
const { sources, resolversByFile } = collectRouteSchemaSources(routes, rootDir, wsRoutes);
|
|
1720
1792
|
await generateZodArtifacts(sources, {
|
|
1721
1793
|
generateFileSource: generateSchemaFileSource,
|
|
1722
1794
|
resolversByFile,
|
|
@@ -3036,8 +3108,8 @@ function resolveExport(module, exportName) {
|
|
|
3036
3108
|
import fs4 from "fs";
|
|
3037
3109
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3038
3110
|
var loadTs;
|
|
3039
|
-
function setLoadTimestamp(
|
|
3040
|
-
loadTs =
|
|
3111
|
+
function setLoadTimestamp(ts12) {
|
|
3112
|
+
loadTs = ts12;
|
|
3041
3113
|
}
|
|
3042
3114
|
function getVitestImportActual() {
|
|
3043
3115
|
const vi = globalThis.vi;
|
|
@@ -3084,7 +3156,7 @@ import fs8 from "fs";
|
|
|
3084
3156
|
import fg2 from "fast-glob";
|
|
3085
3157
|
|
|
3086
3158
|
// src/cli/aliasPlugin.ts
|
|
3087
|
-
import
|
|
3159
|
+
import ts8 from "typescript";
|
|
3088
3160
|
import path8 from "path";
|
|
3089
3161
|
import fs7 from "fs";
|
|
3090
3162
|
|
|
@@ -3143,7 +3215,7 @@ function isInsideDir(filePath, dir) {
|
|
|
3143
3215
|
}
|
|
3144
3216
|
|
|
3145
3217
|
// src/utils/readTsconfig.ts
|
|
3146
|
-
import
|
|
3218
|
+
import ts7 from "typescript";
|
|
3147
3219
|
import path7 from "path";
|
|
3148
3220
|
import fs6 from "fs";
|
|
3149
3221
|
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
@@ -3160,9 +3232,9 @@ function readTsconfig(rootDir) {
|
|
|
3160
3232
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
3161
3233
|
return cached.config;
|
|
3162
3234
|
}
|
|
3163
|
-
const configFile =
|
|
3235
|
+
const configFile = ts7.readConfigFile(tsconfigPath, ts7.sys.readFile);
|
|
3164
3236
|
if (configFile.error || !configFile.config) return null;
|
|
3165
|
-
const parsed =
|
|
3237
|
+
const parsed = ts7.parseJsonConfigFileContent(configFile.config, ts7.sys, rootDir);
|
|
3166
3238
|
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
3167
3239
|
const rawPaths = parsed.options.paths;
|
|
3168
3240
|
const config = rawPaths ? (() => {
|
|
@@ -3262,23 +3334,23 @@ function createAliasPlugin(config, options) {
|
|
|
3262
3334
|
}
|
|
3263
3335
|
const importer = args.path;
|
|
3264
3336
|
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
3265
|
-
const sourceFile =
|
|
3337
|
+
const sourceFile = ts8.createSourceFile(importer, source, ts8.ScriptTarget.Latest, false);
|
|
3266
3338
|
const literals = [];
|
|
3267
3339
|
const visit = (node) => {
|
|
3268
|
-
if (
|
|
3340
|
+
if (ts8.isImportDeclaration(node)) {
|
|
3269
3341
|
if (!node.importClause?.isTypeOnly && node.moduleSpecifier) {
|
|
3270
|
-
if (
|
|
3342
|
+
if (ts8.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
|
|
3271
3343
|
}
|
|
3272
|
-
} else if (
|
|
3344
|
+
} else if (ts8.isExportDeclaration(node)) {
|
|
3273
3345
|
if (!node.isTypeOnly && node.moduleSpecifier) {
|
|
3274
|
-
if (
|
|
3346
|
+
if (ts8.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
|
|
3275
3347
|
}
|
|
3276
|
-
} else if (
|
|
3348
|
+
} else if (ts8.isCallExpression(node) && node.expression.kind === ts8.SyntaxKind.ImportKeyword && node.arguments[0] && ts8.isStringLiteral(node.arguments[0])) {
|
|
3277
3349
|
literals.push(node.arguments[0]);
|
|
3278
3350
|
}
|
|
3279
|
-
|
|
3351
|
+
ts8.forEachChild(node, visit);
|
|
3280
3352
|
};
|
|
3281
|
-
|
|
3353
|
+
ts8.forEachChild(sourceFile, visit);
|
|
3282
3354
|
const replacements = [];
|
|
3283
3355
|
const unresolvable = [];
|
|
3284
3356
|
for (const lit of literals) {
|
|
@@ -3586,7 +3658,7 @@ function clearGeneratedSchemas() {
|
|
|
3586
3658
|
state.generatedSchemas.clear();
|
|
3587
3659
|
state.inFlightSchemaGenerations.clear();
|
|
3588
3660
|
}
|
|
3589
|
-
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
3661
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist, wsRoutes) {
|
|
3590
3662
|
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
3591
3663
|
if (inFlight2) {
|
|
3592
3664
|
await inFlight2;
|
|
@@ -3605,13 +3677,15 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
3605
3677
|
return false;
|
|
3606
3678
|
}
|
|
3607
3679
|
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
3608
|
-
|
|
3680
|
+
const fileWsRoutes = (wsRoutes ?? []).filter((r) => r.filePath === routeFilePath);
|
|
3681
|
+
if (fileRoutes.length === 0 && fileWsRoutes.length === 0) {
|
|
3609
3682
|
return false;
|
|
3610
3683
|
}
|
|
3611
3684
|
const sourceRelPath = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3612
3685
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3686
|
+
const sourceWsRoutes = fileWsRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3613
3687
|
const generatePromise = (async () => {
|
|
3614
|
-
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
3688
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist, sourceWsRoutes);
|
|
3615
3689
|
state.generatedSchemas.add(schemaPath);
|
|
3616
3690
|
})();
|
|
3617
3691
|
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
@@ -3743,17 +3817,17 @@ init_generateZodArtifacts();
|
|
|
3743
3817
|
import path15 from "path";
|
|
3744
3818
|
|
|
3745
3819
|
// src/ast/extractToolMetadata.ts
|
|
3746
|
-
import
|
|
3820
|
+
import ts10 from "typescript";
|
|
3747
3821
|
|
|
3748
3822
|
// src/ast/jsDocMetadata.ts
|
|
3749
|
-
import
|
|
3823
|
+
import ts9 from "typescript";
|
|
3750
3824
|
function hasExportModifier(node) {
|
|
3751
|
-
if (!
|
|
3752
|
-
const modifiers =
|
|
3753
|
-
return !!modifiers?.some((m) => m.kind ===
|
|
3825
|
+
if (!ts9.canHaveModifiers(node)) return false;
|
|
3826
|
+
const modifiers = ts9.getModifiers(node);
|
|
3827
|
+
return !!modifiers?.some((m) => m.kind === ts9.SyntaxKind.ExportKeyword);
|
|
3754
3828
|
}
|
|
3755
3829
|
function getJSDocFromNode(node) {
|
|
3756
|
-
const apiDocs =
|
|
3830
|
+
const apiDocs = ts9.getJSDocCommentsAndTags(node).filter((entry) => ts9.isJSDoc(entry));
|
|
3757
3831
|
if (apiDocs.length > 0) return apiDocs[0];
|
|
3758
3832
|
const directDocs = node.jsDoc;
|
|
3759
3833
|
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
@@ -3799,18 +3873,18 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
|
3799
3873
|
}
|
|
3800
3874
|
function findExportedFunction(sourceFile, functionName) {
|
|
3801
3875
|
let result = null;
|
|
3802
|
-
|
|
3876
|
+
ts10.forEachChild(sourceFile, (node) => {
|
|
3803
3877
|
if (result) return;
|
|
3804
|
-
if (
|
|
3878
|
+
if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
3805
3879
|
result = { fn: node, jsDocOwner: node };
|
|
3806
3880
|
return;
|
|
3807
3881
|
}
|
|
3808
|
-
if (
|
|
3882
|
+
if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
|
|
3809
3883
|
for (const decl of node.declarationList.declarations) {
|
|
3810
3884
|
if (result) break;
|
|
3811
|
-
const nameText =
|
|
3885
|
+
const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
3812
3886
|
if (nameText !== functionName || !decl.initializer) continue;
|
|
3813
|
-
if (
|
|
3887
|
+
if (ts10.isArrowFunction(decl.initializer) || ts10.isFunctionExpression(decl.initializer)) {
|
|
3814
3888
|
result = { fn: decl.initializer, jsDocOwner: node };
|
|
3815
3889
|
}
|
|
3816
3890
|
}
|
|
@@ -3822,7 +3896,7 @@ function getFirstParamTypeName(fn, sourceFile) {
|
|
|
3822
3896
|
const firstParam = fn.parameters[0];
|
|
3823
3897
|
if (!firstParam) return void 0;
|
|
3824
3898
|
if (!firstParam.type) return void 0;
|
|
3825
|
-
if (!
|
|
3899
|
+
if (!ts10.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
3826
3900
|
return firstParam.type.typeName.getText(sourceFile);
|
|
3827
3901
|
}
|
|
3828
3902
|
|
|
@@ -4707,17 +4781,24 @@ function encodeSseEvent(event) {
|
|
|
4707
4781
|
out += "\n";
|
|
4708
4782
|
return out;
|
|
4709
4783
|
}
|
|
4710
|
-
function createSseWriter() {
|
|
4784
|
+
function createSseWriter(options = {}) {
|
|
4785
|
+
const { onFirstWrite } = options;
|
|
4711
4786
|
const encoder = new TextEncoder();
|
|
4712
4787
|
let controller = null;
|
|
4713
4788
|
let closed = false;
|
|
4714
4789
|
let aborted = false;
|
|
4790
|
+
let firstWriteTriggered = false;
|
|
4715
4791
|
let drainWaiters = [];
|
|
4716
4792
|
const resolveDrainWaiters = () => {
|
|
4717
4793
|
const waiters = drainWaiters;
|
|
4718
4794
|
drainWaiters = [];
|
|
4719
4795
|
for (const resolve of waiters) resolve();
|
|
4720
4796
|
};
|
|
4797
|
+
const triggerFirstWrite = () => {
|
|
4798
|
+
if (firstWriteTriggered) return;
|
|
4799
|
+
firstWriteTriggered = true;
|
|
4800
|
+
onFirstWrite?.(response);
|
|
4801
|
+
};
|
|
4721
4802
|
const stream = new ReadableStream(
|
|
4722
4803
|
{
|
|
4723
4804
|
start(c) {
|
|
@@ -4751,11 +4832,13 @@ function createSseWriter() {
|
|
|
4751
4832
|
if (closed || !controller) return;
|
|
4752
4833
|
const text = encodeSseEvent(event);
|
|
4753
4834
|
controller.enqueue(encoder.encode(text));
|
|
4835
|
+
triggerFirstWrite();
|
|
4754
4836
|
},
|
|
4755
4837
|
sendRaw(chunk) {
|
|
4756
4838
|
if (closed || !controller) return;
|
|
4757
4839
|
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
4758
4840
|
controller.enqueue(bytes);
|
|
4841
|
+
triggerFirstWrite();
|
|
4759
4842
|
},
|
|
4760
4843
|
sendError(error) {
|
|
4761
4844
|
if (closed || !controller) return;
|
|
@@ -4763,6 +4846,7 @@ function createSseWriter() {
|
|
|
4763
4846
|
const text = encodeSseEvent({ event: "error", data: message });
|
|
4764
4847
|
try {
|
|
4765
4848
|
controller.enqueue(encoder.encode(text));
|
|
4849
|
+
triggerFirstWrite();
|
|
4766
4850
|
} finally {
|
|
4767
4851
|
writer.close();
|
|
4768
4852
|
}
|
|
@@ -4878,6 +4962,26 @@ function formatErrorResponse(error, config) {
|
|
|
4878
4962
|
return jsonOk(body, 500);
|
|
4879
4963
|
}
|
|
4880
4964
|
|
|
4965
|
+
// src/utils/queryToObject.ts
|
|
4966
|
+
var queryCache = /* @__PURE__ */ new WeakMap();
|
|
4967
|
+
function queryToObject(params) {
|
|
4968
|
+
const cached = queryCache.get(params);
|
|
4969
|
+
if (cached) return cached;
|
|
4970
|
+
const result = {};
|
|
4971
|
+
for (const [key, value] of params) {
|
|
4972
|
+
const existing = result[key];
|
|
4973
|
+
if (existing === void 0) {
|
|
4974
|
+
result[key] = value;
|
|
4975
|
+
} else if (Array.isArray(existing)) {
|
|
4976
|
+
existing.push(value);
|
|
4977
|
+
} else {
|
|
4978
|
+
result[key] = [existing, value];
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
queryCache.set(params, result);
|
|
4982
|
+
return result;
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4881
4985
|
// src/runtime/createContext.ts
|
|
4882
4986
|
function resolveRequestId(request) {
|
|
4883
4987
|
const header = request.headers.get("x-request-id");
|
|
@@ -4927,7 +5031,11 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4927
5031
|
log,
|
|
4928
5032
|
request,
|
|
4929
5033
|
params,
|
|
4930
|
-
|
|
5034
|
+
rawParams: params,
|
|
5035
|
+
// ctx.query 为对象口径(createContext 挂原始 string 值对象;handler 声明
|
|
5036
|
+
// query 类型时管线校验后替换为转换值)。原始 URLSearchParams 恒在 rawQuery。
|
|
5037
|
+
query: queryToObject(url.searchParams),
|
|
5038
|
+
rawQuery: url.searchParams,
|
|
4931
5039
|
headers: request.headers,
|
|
4932
5040
|
method: request.method,
|
|
4933
5041
|
path: url.pathname,
|
|
@@ -4975,13 +5083,19 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4975
5083
|
/**
|
|
4976
5084
|
* 创建 SSE writer,用于流式推送事件
|
|
4977
5085
|
*
|
|
4978
|
-
* handler 调用此方法后,通过返回的 writer
|
|
4979
|
-
*
|
|
5086
|
+
* handler 调用此方法后,通过返回的 writer 推送事件。HTTP 服务场景下首次
|
|
5087
|
+
* 写入(send/sendRaw/sendError)即触发 server 层提前接管钩子,把 writer.response
|
|
5088
|
+
* 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。
|
|
5089
|
+
* 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。
|
|
4980
5090
|
*
|
|
4981
5091
|
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
4982
5092
|
*/
|
|
4983
5093
|
sse() {
|
|
4984
|
-
const writer = createSseWriter(
|
|
5094
|
+
const writer = createSseWriter({
|
|
5095
|
+
onFirstWrite: (response) => {
|
|
5096
|
+
ctx.__earlyRespond?.(response);
|
|
5097
|
+
}
|
|
5098
|
+
});
|
|
4985
5099
|
const ctxWithSse = ctx;
|
|
4986
5100
|
ctxWithSse.__sseResponse = writer.response;
|
|
4987
5101
|
ctxWithSse.__sseWriter = writer;
|
|
@@ -5027,26 +5141,6 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
5027
5141
|
return ctx;
|
|
5028
5142
|
}
|
|
5029
5143
|
|
|
5030
|
-
// src/utils/queryToObject.ts
|
|
5031
|
-
var queryCache = /* @__PURE__ */ new WeakMap();
|
|
5032
|
-
function queryToObject(params) {
|
|
5033
|
-
const cached = queryCache.get(params);
|
|
5034
|
-
if (cached) return cached;
|
|
5035
|
-
const result = {};
|
|
5036
|
-
for (const [key, value] of params) {
|
|
5037
|
-
const existing = result[key];
|
|
5038
|
-
if (existing === void 0) {
|
|
5039
|
-
result[key] = value;
|
|
5040
|
-
} else if (Array.isArray(existing)) {
|
|
5041
|
-
existing.push(value);
|
|
5042
|
-
} else {
|
|
5043
|
-
result[key] = [existing, value];
|
|
5044
|
-
}
|
|
5045
|
-
}
|
|
5046
|
-
queryCache.set(params, result);
|
|
5047
|
-
return result;
|
|
5048
|
-
}
|
|
5049
|
-
|
|
5050
5144
|
// src/utils/parseJsonBody.ts
|
|
5051
5145
|
function parseJsonBody(text) {
|
|
5052
5146
|
try {
|
|
@@ -5092,46 +5186,29 @@ init_inputType();
|
|
|
5092
5186
|
async function resolveInputFromUrl(method, request, url) {
|
|
5093
5187
|
const inputType = getInputTypeForMethod(method);
|
|
5094
5188
|
if (inputType === "body") {
|
|
5095
|
-
|
|
5096
|
-
if (contentType.includes("multipart/form-data")) {
|
|
5097
|
-
return parseMultipart(request);
|
|
5098
|
-
}
|
|
5099
|
-
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
5100
|
-
const text2 = await request.text();
|
|
5101
|
-
if (isBlankText(text2)) return null;
|
|
5102
|
-
const params = new URLSearchParams(text2);
|
|
5103
|
-
const obj = {};
|
|
5104
|
-
for (const [key, value] of params) {
|
|
5105
|
-
obj[key] = value;
|
|
5106
|
-
}
|
|
5107
|
-
return obj;
|
|
5108
|
-
}
|
|
5109
|
-
const text = await request.text();
|
|
5110
|
-
if (isBlankText(text)) {
|
|
5111
|
-
return null;
|
|
5112
|
-
}
|
|
5113
|
-
const result = parseJsonBody(text);
|
|
5114
|
-
if (!result.success) {
|
|
5115
|
-
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
5116
|
-
{
|
|
5117
|
-
path: "body",
|
|
5118
|
-
code: "INVALID_FORMAT",
|
|
5119
|
-
expected: "JSON",
|
|
5120
|
-
received: "text",
|
|
5121
|
-
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
5122
|
-
}
|
|
5123
|
-
]);
|
|
5124
|
-
}
|
|
5125
|
-
return result.data;
|
|
5189
|
+
return parseBodyByContentType(request);
|
|
5126
5190
|
}
|
|
5127
|
-
return queryToObject(url.searchParams);
|
|
5128
|
-
}
|
|
5129
|
-
function isBlankText(text) {
|
|
5130
|
-
return text.length === 0 || !/\S/.test(text);
|
|
5191
|
+
return { input: queryToObject(url.searchParams) };
|
|
5131
5192
|
}
|
|
5132
|
-
async function
|
|
5193
|
+
async function parseBodyByContentType(request) {
|
|
5194
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
5195
|
+
if (contentType.includes("multipart/form-data")) {
|
|
5196
|
+
return { input: await parseMultipart(request) };
|
|
5197
|
+
}
|
|
5198
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
5199
|
+
const text2 = await request.text();
|
|
5200
|
+
if (isBlankText(text2)) return { input: null };
|
|
5201
|
+
const params = new URLSearchParams(text2);
|
|
5202
|
+
const obj = {};
|
|
5203
|
+
for (const [key, value] of params) {
|
|
5204
|
+
obj[key] = value;
|
|
5205
|
+
}
|
|
5206
|
+
return { input: obj, rawBody: text2 };
|
|
5207
|
+
}
|
|
5133
5208
|
const text = await request.text();
|
|
5134
|
-
if (isBlankText(text))
|
|
5209
|
+
if (isBlankText(text)) {
|
|
5210
|
+
return { input: null };
|
|
5211
|
+
}
|
|
5135
5212
|
const result = parseJsonBody(text);
|
|
5136
5213
|
if (!result.success) {
|
|
5137
5214
|
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
@@ -5144,7 +5221,13 @@ async function resolveBodyForQueryMethod(request) {
|
|
|
5144
5221
|
}
|
|
5145
5222
|
]);
|
|
5146
5223
|
}
|
|
5147
|
-
return result.data;
|
|
5224
|
+
return { input: result.data, rawBody: text };
|
|
5225
|
+
}
|
|
5226
|
+
function isBlankText(text) {
|
|
5227
|
+
return text.length === 0 || !/\S/.test(text);
|
|
5228
|
+
}
|
|
5229
|
+
async function resolveBodyForQueryMethod(request) {
|
|
5230
|
+
return parseBodyByContentType(request);
|
|
5148
5231
|
}
|
|
5149
5232
|
|
|
5150
5233
|
// src/response/pendingMeta.ts
|
|
@@ -5279,12 +5362,17 @@ function markBuffered(response) {
|
|
|
5279
5362
|
init_resolveInjection();
|
|
5280
5363
|
function getBuiltinInjectionValue(type, ctx, body) {
|
|
5281
5364
|
switch (type) {
|
|
5282
|
-
case "query":
|
|
5283
|
-
|
|
5284
|
-
return validated ?? queryToObject(ctx.query);
|
|
5285
|
-
}
|
|
5365
|
+
case "query":
|
|
5366
|
+
return ctx.query;
|
|
5286
5367
|
case "params":
|
|
5287
5368
|
return ctx.params;
|
|
5369
|
+
// raw 系:恒原始,管线永不覆盖(ctx 字段即注入值)
|
|
5370
|
+
case "rawQuery":
|
|
5371
|
+
return ctx.rawQuery;
|
|
5372
|
+
case "rawParams":
|
|
5373
|
+
return ctx.rawParams;
|
|
5374
|
+
case "rawBody":
|
|
5375
|
+
return ctx.rawBody;
|
|
5288
5376
|
case "headers":
|
|
5289
5377
|
return ctx.headers;
|
|
5290
5378
|
case "context":
|
|
@@ -5800,6 +5888,9 @@ function buildErrorResponse(err, config) {
|
|
|
5800
5888
|
}
|
|
5801
5889
|
}
|
|
5802
5890
|
|
|
5891
|
+
// src/server/handleWsUpgrade.ts
|
|
5892
|
+
init_generateSchemaFiles();
|
|
5893
|
+
|
|
5803
5894
|
// src/middleware/loadMiddlewares.ts
|
|
5804
5895
|
var middlewareCache = /* @__PURE__ */ new Map();
|
|
5805
5896
|
var inFlight = /* @__PURE__ */ new Map();
|
|
@@ -5998,6 +6089,7 @@ function attachWebSocket(options) {
|
|
|
5998
6089
|
server,
|
|
5999
6090
|
routesRef,
|
|
6000
6091
|
rootDir,
|
|
6092
|
+
dist,
|
|
6001
6093
|
config,
|
|
6002
6094
|
globalMiddlewares,
|
|
6003
6095
|
trustedProxy = false,
|
|
@@ -6032,12 +6124,50 @@ function attachWebSocket(options) {
|
|
|
6032
6124
|
const { route, params } = match;
|
|
6033
6125
|
const headers = nodeHttpToWebHeaders(req);
|
|
6034
6126
|
const host = req.headers.host ?? "localhost";
|
|
6035
|
-
const
|
|
6036
|
-
const request = new Request(
|
|
6127
|
+
const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
|
|
6128
|
+
const request = new Request(fullUrl.toString(), { method: "GET", headers });
|
|
6037
6129
|
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
|
|
6038
6130
|
const meta = ctx.meta;
|
|
6039
6131
|
let upgraded = false;
|
|
6040
6132
|
const finalHandler = async () => {
|
|
6133
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
6134
|
+
if (fs16.existsSync(schemaPath)) {
|
|
6135
|
+
if (isDevOnDemandEnabled()) {
|
|
6136
|
+
const devDist = getDevDist();
|
|
6137
|
+
if (devDist) {
|
|
6138
|
+
await ensureSchemaGenerated(
|
|
6139
|
+
schemaPath,
|
|
6140
|
+
route.filePath,
|
|
6141
|
+
routesRef.current,
|
|
6142
|
+
rootDir,
|
|
6143
|
+
dist,
|
|
6144
|
+
routesRef.wsCurrent
|
|
6145
|
+
);
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
const searchParams = fullUrl.searchParams;
|
|
6149
|
+
const rawQueryObject = queryToObject(searchParams);
|
|
6150
|
+
const paramsResult = await validateInput(schemaPath, "WS", "params", params);
|
|
6151
|
+
if (!paramsResult.valid) {
|
|
6152
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", paramsResult.issues);
|
|
6153
|
+
}
|
|
6154
|
+
if (paramsResult.data !== params && typeof paramsResult.data === "object" && paramsResult.data !== null) {
|
|
6155
|
+
ctx.params = {
|
|
6156
|
+
...params,
|
|
6157
|
+
...paramsResult.data
|
|
6158
|
+
};
|
|
6159
|
+
}
|
|
6160
|
+
const queryResult = await validateInput(schemaPath, "WS", "query", rawQueryObject);
|
|
6161
|
+
if (!queryResult.valid) {
|
|
6162
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", queryResult.issues);
|
|
6163
|
+
}
|
|
6164
|
+
if (queryResult.data !== rawQueryObject && typeof queryResult.data === "object" && queryResult.data !== null) {
|
|
6165
|
+
ctx.query = {
|
|
6166
|
+
...rawQueryObject,
|
|
6167
|
+
...queryResult.data
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6041
6171
|
let handlers;
|
|
6042
6172
|
try {
|
|
6043
6173
|
const absoluteFilePath = path18.resolve(rootDir, route.filePath);
|
|
@@ -6245,7 +6375,8 @@ function createServer(options) {
|
|
|
6245
6375
|
globalInjectors,
|
|
6246
6376
|
bodyLimit,
|
|
6247
6377
|
trustedProxy,
|
|
6248
|
-
registries
|
|
6378
|
+
registries,
|
|
6379
|
+
routesRef.wsCurrent
|
|
6249
6380
|
).catch((err) => {
|
|
6250
6381
|
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
6251
6382
|
try {
|
|
@@ -6263,6 +6394,7 @@ function createServer(options) {
|
|
|
6263
6394
|
server,
|
|
6264
6395
|
routesRef,
|
|
6265
6396
|
rootDir,
|
|
6397
|
+
dist,
|
|
6266
6398
|
config,
|
|
6267
6399
|
globalMiddlewares,
|
|
6268
6400
|
trustedProxy,
|
|
@@ -6270,14 +6402,13 @@ function createServer(options) {
|
|
|
6270
6402
|
});
|
|
6271
6403
|
return { server, routesRef };
|
|
6272
6404
|
}
|
|
6273
|
-
function prepareRequest(req,
|
|
6274
|
-
const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
|
|
6405
|
+
function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
|
|
6275
6406
|
const method = request.method.toUpperCase();
|
|
6276
6407
|
const urlPath = url.pathname;
|
|
6277
6408
|
const ctx = createContextFromUrl(
|
|
6278
6409
|
request,
|
|
6279
6410
|
url,
|
|
6280
|
-
{},
|
|
6411
|
+
preMatched?.params ?? {},
|
|
6281
6412
|
config,
|
|
6282
6413
|
getClientIp(req, trustedProxy),
|
|
6283
6414
|
registries
|
|
@@ -6308,19 +6439,33 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
6308
6439
|
}
|
|
6309
6440
|
var mergedInjectorsCache = /* @__PURE__ */ new WeakMap();
|
|
6310
6441
|
function createRoutePipeline(opts) {
|
|
6311
|
-
const {
|
|
6442
|
+
const {
|
|
6443
|
+
routes,
|
|
6444
|
+
method,
|
|
6445
|
+
urlPath,
|
|
6446
|
+
url,
|
|
6447
|
+
ctx,
|
|
6448
|
+
request,
|
|
6449
|
+
rootDir,
|
|
6450
|
+
dist,
|
|
6451
|
+
globalInjectors,
|
|
6452
|
+
preMatched,
|
|
6453
|
+
wsRoutes
|
|
6454
|
+
} = opts;
|
|
6312
6455
|
return async () => {
|
|
6313
|
-
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
6314
|
-
ctx.params = match.params;
|
|
6456
|
+
const match = preMatched ?? resolveRouteOrThrow(routes, method, urlPath);
|
|
6315
6457
|
const { route } = match;
|
|
6316
6458
|
const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);
|
|
6317
6459
|
const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);
|
|
6318
|
-
const input = await resolveInputFromUrl(route.method, request, url);
|
|
6460
|
+
const { input, rawBody } = await resolveInputFromUrl(route.method, request, url);
|
|
6461
|
+
if (rawBody !== void 0) {
|
|
6462
|
+
ctx.rawBody = rawBody;
|
|
6463
|
+
}
|
|
6319
6464
|
const inputType = getInputTypeForMethod(route.method);
|
|
6320
6465
|
if (isDevOnDemandEnabled()) {
|
|
6321
6466
|
const devDist = getDevDist();
|
|
6322
6467
|
if (devDist) {
|
|
6323
|
-
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);
|
|
6468
|
+
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist, wsRoutes);
|
|
6324
6469
|
}
|
|
6325
6470
|
}
|
|
6326
6471
|
const result = await validateInput(schemaPath, route.method, inputType, input);
|
|
@@ -6328,7 +6473,7 @@ function createRoutePipeline(opts) {
|
|
|
6328
6473
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
6329
6474
|
}
|
|
6330
6475
|
if (inputType === "query" && result.data !== input && typeof result.data === "object" && result.data !== null) {
|
|
6331
|
-
ctx.
|
|
6476
|
+
ctx.query = {
|
|
6332
6477
|
...input,
|
|
6333
6478
|
...result.data
|
|
6334
6479
|
};
|
|
@@ -6343,7 +6488,36 @@ function createRoutePipeline(opts) {
|
|
|
6343
6488
|
...paramsResult.data
|
|
6344
6489
|
} : match.params;
|
|
6345
6490
|
}
|
|
6346
|
-
|
|
6491
|
+
if (inputType === "body") {
|
|
6492
|
+
const rawQuery = queryToObject(url.searchParams);
|
|
6493
|
+
const queryResult = await validateInput(schemaPath, route.method, "query", rawQuery);
|
|
6494
|
+
if (!queryResult.valid) {
|
|
6495
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", queryResult.issues);
|
|
6496
|
+
}
|
|
6497
|
+
if (queryResult.data !== rawQuery && typeof queryResult.data === "object" && queryResult.data !== null) {
|
|
6498
|
+
ctx.query = {
|
|
6499
|
+
...rawQuery,
|
|
6500
|
+
...queryResult.data
|
|
6501
|
+
};
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
let body;
|
|
6505
|
+
if (inputType === "query" && hasBody(route.method)) {
|
|
6506
|
+
const parsed = await resolveBodyForQueryMethod(request);
|
|
6507
|
+
if (parsed.rawBody !== void 0) {
|
|
6508
|
+
ctx.rawBody = parsed.rawBody;
|
|
6509
|
+
}
|
|
6510
|
+
const bodyResult = await validateInput(schemaPath, route.method, "body", parsed.input);
|
|
6511
|
+
if (!bodyResult.valid) {
|
|
6512
|
+
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", bodyResult.issues);
|
|
6513
|
+
}
|
|
6514
|
+
body = bodyResult.data;
|
|
6515
|
+
} else if (hasBody(route.method)) {
|
|
6516
|
+
body = result.data;
|
|
6517
|
+
}
|
|
6518
|
+
if (body !== void 0 && body !== null) {
|
|
6519
|
+
ctx.body = body;
|
|
6520
|
+
}
|
|
6347
6521
|
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
6348
6522
|
await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);
|
|
6349
6523
|
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
@@ -6379,25 +6553,37 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
6379
6553
|
}
|
|
6380
6554
|
}
|
|
6381
6555
|
}
|
|
6382
|
-
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries) {
|
|
6556
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
|
|
6383
6557
|
let meta = { headers: {}, setCookies: [] };
|
|
6384
6558
|
let ctx;
|
|
6559
|
+
let sseEarlySent = false;
|
|
6385
6560
|
const abortController = new AbortController();
|
|
6386
6561
|
res.on("close", () => {
|
|
6387
6562
|
if (!res.writableEnded) abortController.abort();
|
|
6388
6563
|
});
|
|
6389
6564
|
try {
|
|
6565
|
+
const { request, url } = toWebRequest(req, bodyLimit, abortController.signal);
|
|
6566
|
+
const method = request.method.toUpperCase();
|
|
6567
|
+
const urlPath = url.pathname;
|
|
6568
|
+
const preMatched = matchRoute(routes, method, urlPath);
|
|
6390
6569
|
const prepared = prepareRequest(
|
|
6391
6570
|
req,
|
|
6571
|
+
request,
|
|
6572
|
+
url,
|
|
6392
6573
|
config,
|
|
6393
|
-
bodyLimit,
|
|
6394
6574
|
trustedProxy,
|
|
6395
6575
|
registries,
|
|
6396
|
-
|
|
6576
|
+
preMatched
|
|
6397
6577
|
);
|
|
6398
6578
|
ctx = prepared.ctx;
|
|
6399
6579
|
meta = prepared.meta;
|
|
6400
|
-
|
|
6580
|
+
ctx.__earlyRespond = (response2) => {
|
|
6581
|
+
if (sseEarlySent) return;
|
|
6582
|
+
sseEarlySent = true;
|
|
6583
|
+
void sendNodeResponse(mergeMeta(response2, meta), res).catch(() => {
|
|
6584
|
+
if (!res.writableEnded) res.destroy();
|
|
6585
|
+
});
|
|
6586
|
+
};
|
|
6401
6587
|
const routePipeline = createRoutePipeline({
|
|
6402
6588
|
routes,
|
|
6403
6589
|
method,
|
|
@@ -6407,12 +6593,25 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
6407
6593
|
request,
|
|
6408
6594
|
rootDir,
|
|
6409
6595
|
dist,
|
|
6410
|
-
globalInjectors
|
|
6596
|
+
globalInjectors,
|
|
6597
|
+
preMatched,
|
|
6598
|
+
wsRoutes
|
|
6411
6599
|
});
|
|
6412
6600
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
6413
|
-
|
|
6601
|
+
if (!sseEarlySent) {
|
|
6602
|
+
await sendSuccessResponse(response, res);
|
|
6603
|
+
}
|
|
6414
6604
|
} catch (err) {
|
|
6415
6605
|
if (res.destroyed || res.writableEnded) return;
|
|
6606
|
+
if (sseEarlySent) {
|
|
6607
|
+
if (onError && ctx) {
|
|
6608
|
+
try {
|
|
6609
|
+
await onError(err, ctx);
|
|
6610
|
+
} catch {
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
return;
|
|
6614
|
+
}
|
|
6416
6615
|
if (err instanceof PayloadTooLargeError) {
|
|
6417
6616
|
res.setHeader("Connection", "close");
|
|
6418
6617
|
res.once("finish", () => req.destroy());
|
|
@@ -6683,7 +6882,7 @@ import path25 from "path";
|
|
|
6683
6882
|
import path23 from "path";
|
|
6684
6883
|
|
|
6685
6884
|
// src/ast/extractAgentMetadata.ts
|
|
6686
|
-
import
|
|
6885
|
+
import ts11 from "typescript";
|
|
6687
6886
|
init_resolveTypeNode();
|
|
6688
6887
|
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
6689
6888
|
const sourceFile = program.getSourceFile(filePath);
|
|
@@ -6728,23 +6927,23 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
6728
6927
|
}
|
|
6729
6928
|
function findConfigExport(sourceFile) {
|
|
6730
6929
|
let result = null;
|
|
6731
|
-
|
|
6930
|
+
ts11.forEachChild(sourceFile, (node) => {
|
|
6732
6931
|
if (result) return;
|
|
6733
|
-
if (
|
|
6932
|
+
if (ts11.isVariableStatement(node) && hasExportModifier(node)) {
|
|
6734
6933
|
for (const decl of node.declarationList.declarations) {
|
|
6735
6934
|
if (result) break;
|
|
6736
|
-
const nameText =
|
|
6935
|
+
const nameText = ts11.isIdentifier(decl.name) ? decl.name.text : "";
|
|
6737
6936
|
if (nameText !== "config" || !decl.initializer) continue;
|
|
6738
|
-
if (
|
|
6937
|
+
if (ts11.isObjectLiteralExpression(decl.initializer)) {
|
|
6739
6938
|
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
6740
|
-
} else if (
|
|
6939
|
+
} else if (ts11.isArrowFunction(decl.initializer)) {
|
|
6741
6940
|
result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
|
|
6742
6941
|
} else {
|
|
6743
6942
|
result = { jsDocOwner: node, objectLiteral: null };
|
|
6744
6943
|
}
|
|
6745
6944
|
}
|
|
6746
6945
|
}
|
|
6747
|
-
if (
|
|
6946
|
+
if (ts11.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
|
|
6748
6947
|
const returnObj = getReturnObjectLiteral(node);
|
|
6749
6948
|
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
6750
6949
|
}
|
|
@@ -6754,12 +6953,12 @@ function findConfigExport(sourceFile) {
|
|
|
6754
6953
|
function getReturnObjectLiteral(fn) {
|
|
6755
6954
|
const body = fn.body;
|
|
6756
6955
|
if (!body) return null;
|
|
6757
|
-
if (
|
|
6956
|
+
if (ts11.isObjectLiteralExpression(body)) {
|
|
6758
6957
|
return body;
|
|
6759
6958
|
}
|
|
6760
|
-
if (
|
|
6959
|
+
if (ts11.isBlock(body)) {
|
|
6761
6960
|
for (const stmt of body.statements) {
|
|
6762
|
-
if (
|
|
6961
|
+
if (ts11.isReturnStatement(stmt) && stmt.expression && ts11.isObjectLiteralExpression(stmt.expression)) {
|
|
6763
6962
|
return stmt.expression;
|
|
6764
6963
|
}
|
|
6765
6964
|
}
|
|
@@ -6774,8 +6973,8 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
6774
6973
|
let maxTurns;
|
|
6775
6974
|
let inputDescription;
|
|
6776
6975
|
for (const prop of objLit.properties) {
|
|
6777
|
-
if (
|
|
6778
|
-
if (!
|
|
6976
|
+
if (ts11.isSpreadAssignment(prop)) continue;
|
|
6977
|
+
if (!ts11.isPropertyAssignment(prop)) {
|
|
6779
6978
|
throw SchemaExtractionError.at(
|
|
6780
6979
|
prop,
|
|
6781
6980
|
"config",
|
|
@@ -6826,13 +7025,13 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
6826
7025
|
return { systemPrompt, tools, agents, model, maxTurns, inputDescription };
|
|
6827
7026
|
}
|
|
6828
7027
|
function getPropertyName(name) {
|
|
6829
|
-
if (
|
|
6830
|
-
if (
|
|
7028
|
+
if (ts11.isIdentifier(name)) return name.text;
|
|
7029
|
+
if (ts11.isStringLiteral(name)) return name.text;
|
|
6831
7030
|
return null;
|
|
6832
7031
|
}
|
|
6833
7032
|
function extractStringValue(expr) {
|
|
6834
7033
|
if (isStringLikeLiteral(expr)) return expr.text;
|
|
6835
|
-
if (
|
|
7034
|
+
if (ts11.isBinaryExpression(expr) && expr.operatorToken.kind === ts11.SyntaxKind.PlusToken) {
|
|
6836
7035
|
const left = extractStringValue(expr.left);
|
|
6837
7036
|
if (left === void 0) return void 0;
|
|
6838
7037
|
const right = extractStringValue(expr.right);
|
|
@@ -6878,17 +7077,17 @@ function requireNumberValue(prop, fieldName, sourceFile) {
|
|
|
6878
7077
|
return value;
|
|
6879
7078
|
}
|
|
6880
7079
|
function extractNumberValue(expr) {
|
|
6881
|
-
if (
|
|
7080
|
+
if (ts11.isNumericLiteral(expr)) {
|
|
6882
7081
|
const num = Number(expr.text);
|
|
6883
7082
|
return Number.isNaN(num) ? void 0 : num;
|
|
6884
7083
|
}
|
|
6885
7084
|
return void 0;
|
|
6886
7085
|
}
|
|
6887
7086
|
function isStringLikeLiteral(node) {
|
|
6888
|
-
return
|
|
7087
|
+
return ts11.isStringLiteral(node) || ts11.isNoSubstitutionTemplateLiteral(node);
|
|
6889
7088
|
}
|
|
6890
7089
|
function extractStringArrayValue(expr) {
|
|
6891
|
-
if (!
|
|
7090
|
+
if (!ts11.isArrayLiteralExpression(expr)) return void 0;
|
|
6892
7091
|
const values = [];
|
|
6893
7092
|
for (const element of expr.elements) {
|
|
6894
7093
|
const value = extractStringValue(element);
|
|
@@ -7865,14 +8064,14 @@ async function createDevApp(options) {
|
|
|
7865
8064
|
void (async () => {
|
|
7866
8065
|
try {
|
|
7867
8066
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
7868
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
8067
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
7869
8068
|
} catch (err) {
|
|
7870
8069
|
console.error("[faapi] Background schema regeneration failed:", err);
|
|
7871
8070
|
}
|
|
7872
8071
|
})();
|
|
7873
8072
|
} else {
|
|
7874
8073
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
7875
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
8074
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
7876
8075
|
}
|
|
7877
8076
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
7878
8077
|
};
|