@faapi/faapi 6.20.0 → 6.21.1
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 +297 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +16 -21
- package/dist/index.js +300 -148
- 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 +263 -114
- 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 = [];
|
|
@@ -1252,6 +1280,28 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1252
1280
|
}
|
|
1253
1281
|
}
|
|
1254
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
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1255
1305
|
return { sources, resolversByFile };
|
|
1256
1306
|
}
|
|
1257
1307
|
var init_collectRouteSchemaSources = __esm({
|
|
@@ -1736,9 +1786,9 @@ function generateSchemaFileSource(sources, resolveType, helpersImportPath) {
|
|
|
1736
1786
|
lines.push(...schemaBlocks);
|
|
1737
1787
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
1738
1788
|
}
|
|
1739
|
-
async function generateSchemaFiles(routes, rootDir, dist) {
|
|
1740
|
-
if (routes.length === 0) return;
|
|
1741
|
-
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);
|
|
1742
1792
|
await generateZodArtifacts(sources, {
|
|
1743
1793
|
generateFileSource: generateSchemaFileSource,
|
|
1744
1794
|
resolversByFile,
|
|
@@ -3058,8 +3108,8 @@ function resolveExport(module, exportName) {
|
|
|
3058
3108
|
import fs4 from "fs";
|
|
3059
3109
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3060
3110
|
var loadTs;
|
|
3061
|
-
function setLoadTimestamp(
|
|
3062
|
-
loadTs =
|
|
3111
|
+
function setLoadTimestamp(ts12) {
|
|
3112
|
+
loadTs = ts12;
|
|
3063
3113
|
}
|
|
3064
3114
|
function getVitestImportActual() {
|
|
3065
3115
|
const vi = globalThis.vi;
|
|
@@ -3106,7 +3156,7 @@ import fs8 from "fs";
|
|
|
3106
3156
|
import fg2 from "fast-glob";
|
|
3107
3157
|
|
|
3108
3158
|
// src/cli/aliasPlugin.ts
|
|
3109
|
-
import
|
|
3159
|
+
import ts8 from "typescript";
|
|
3110
3160
|
import path8 from "path";
|
|
3111
3161
|
import fs7 from "fs";
|
|
3112
3162
|
|
|
@@ -3165,7 +3215,7 @@ function isInsideDir(filePath, dir) {
|
|
|
3165
3215
|
}
|
|
3166
3216
|
|
|
3167
3217
|
// src/utils/readTsconfig.ts
|
|
3168
|
-
import
|
|
3218
|
+
import ts7 from "typescript";
|
|
3169
3219
|
import path7 from "path";
|
|
3170
3220
|
import fs6 from "fs";
|
|
3171
3221
|
var tsconfigCache = /* @__PURE__ */ new Map();
|
|
@@ -3182,9 +3232,9 @@ function readTsconfig(rootDir) {
|
|
|
3182
3232
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
3183
3233
|
return cached.config;
|
|
3184
3234
|
}
|
|
3185
|
-
const configFile =
|
|
3235
|
+
const configFile = ts7.readConfigFile(tsconfigPath, ts7.sys.readFile);
|
|
3186
3236
|
if (configFile.error || !configFile.config) return null;
|
|
3187
|
-
const parsed =
|
|
3237
|
+
const parsed = ts7.parseJsonConfigFileContent(configFile.config, ts7.sys, rootDir);
|
|
3188
3238
|
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
3189
3239
|
const rawPaths = parsed.options.paths;
|
|
3190
3240
|
const config = rawPaths ? (() => {
|
|
@@ -3284,23 +3334,23 @@ function createAliasPlugin(config, options) {
|
|
|
3284
3334
|
}
|
|
3285
3335
|
const importer = args.path;
|
|
3286
3336
|
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
3287
|
-
const sourceFile =
|
|
3337
|
+
const sourceFile = ts8.createSourceFile(importer, source, ts8.ScriptTarget.Latest, false);
|
|
3288
3338
|
const literals = [];
|
|
3289
3339
|
const visit = (node) => {
|
|
3290
|
-
if (
|
|
3340
|
+
if (ts8.isImportDeclaration(node)) {
|
|
3291
3341
|
if (!node.importClause?.isTypeOnly && node.moduleSpecifier) {
|
|
3292
|
-
if (
|
|
3342
|
+
if (ts8.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
|
|
3293
3343
|
}
|
|
3294
|
-
} else if (
|
|
3344
|
+
} else if (ts8.isExportDeclaration(node)) {
|
|
3295
3345
|
if (!node.isTypeOnly && node.moduleSpecifier) {
|
|
3296
|
-
if (
|
|
3346
|
+
if (ts8.isStringLiteral(node.moduleSpecifier)) literals.push(node.moduleSpecifier);
|
|
3297
3347
|
}
|
|
3298
|
-
} else if (
|
|
3348
|
+
} else if (ts8.isCallExpression(node) && node.expression.kind === ts8.SyntaxKind.ImportKeyword && node.arguments[0] && ts8.isStringLiteral(node.arguments[0])) {
|
|
3299
3349
|
literals.push(node.arguments[0]);
|
|
3300
3350
|
}
|
|
3301
|
-
|
|
3351
|
+
ts8.forEachChild(node, visit);
|
|
3302
3352
|
};
|
|
3303
|
-
|
|
3353
|
+
ts8.forEachChild(sourceFile, visit);
|
|
3304
3354
|
const replacements = [];
|
|
3305
3355
|
const unresolvable = [];
|
|
3306
3356
|
for (const lit of literals) {
|
|
@@ -3608,7 +3658,7 @@ function clearGeneratedSchemas() {
|
|
|
3608
3658
|
state.generatedSchemas.clear();
|
|
3609
3659
|
state.inFlightSchemaGenerations.clear();
|
|
3610
3660
|
}
|
|
3611
|
-
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
3661
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist, wsRoutes) {
|
|
3612
3662
|
const inFlight2 = state.inFlightSchemaGenerations.get(schemaPath);
|
|
3613
3663
|
if (inFlight2) {
|
|
3614
3664
|
await inFlight2;
|
|
@@ -3627,13 +3677,15 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
3627
3677
|
return false;
|
|
3628
3678
|
}
|
|
3629
3679
|
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
3630
|
-
|
|
3680
|
+
const fileWsRoutes = (wsRoutes ?? []).filter((r) => r.filePath === routeFilePath);
|
|
3681
|
+
if (fileRoutes.length === 0 && fileWsRoutes.length === 0) {
|
|
3631
3682
|
return false;
|
|
3632
3683
|
}
|
|
3633
3684
|
const sourceRelPath = path14.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
3634
3685
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3686
|
+
const sourceWsRoutes = fileWsRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
3635
3687
|
const generatePromise = (async () => {
|
|
3636
|
-
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
3688
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist, sourceWsRoutes);
|
|
3637
3689
|
state.generatedSchemas.add(schemaPath);
|
|
3638
3690
|
})();
|
|
3639
3691
|
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
@@ -3765,17 +3817,17 @@ init_generateZodArtifacts();
|
|
|
3765
3817
|
import path15 from "path";
|
|
3766
3818
|
|
|
3767
3819
|
// src/ast/extractToolMetadata.ts
|
|
3768
|
-
import
|
|
3820
|
+
import ts10 from "typescript";
|
|
3769
3821
|
|
|
3770
3822
|
// src/ast/jsDocMetadata.ts
|
|
3771
|
-
import
|
|
3823
|
+
import ts9 from "typescript";
|
|
3772
3824
|
function hasExportModifier(node) {
|
|
3773
|
-
if (!
|
|
3774
|
-
const modifiers =
|
|
3775
|
-
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);
|
|
3776
3828
|
}
|
|
3777
3829
|
function getJSDocFromNode(node) {
|
|
3778
|
-
const apiDocs =
|
|
3830
|
+
const apiDocs = ts9.getJSDocCommentsAndTags(node).filter((entry) => ts9.isJSDoc(entry));
|
|
3779
3831
|
if (apiDocs.length > 0) return apiDocs[0];
|
|
3780
3832
|
const directDocs = node.jsDoc;
|
|
3781
3833
|
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
@@ -3821,18 +3873,18 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
|
3821
3873
|
}
|
|
3822
3874
|
function findExportedFunction(sourceFile, functionName) {
|
|
3823
3875
|
let result = null;
|
|
3824
|
-
|
|
3876
|
+
ts10.forEachChild(sourceFile, (node) => {
|
|
3825
3877
|
if (result) return;
|
|
3826
|
-
if (
|
|
3878
|
+
if (ts10.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
3827
3879
|
result = { fn: node, jsDocOwner: node };
|
|
3828
3880
|
return;
|
|
3829
3881
|
}
|
|
3830
|
-
if (
|
|
3882
|
+
if (ts10.isVariableStatement(node) && hasExportModifier(node)) {
|
|
3831
3883
|
for (const decl of node.declarationList.declarations) {
|
|
3832
3884
|
if (result) break;
|
|
3833
|
-
const nameText =
|
|
3885
|
+
const nameText = ts10.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
3834
3886
|
if (nameText !== functionName || !decl.initializer) continue;
|
|
3835
|
-
if (
|
|
3887
|
+
if (ts10.isArrowFunction(decl.initializer) || ts10.isFunctionExpression(decl.initializer)) {
|
|
3836
3888
|
result = { fn: decl.initializer, jsDocOwner: node };
|
|
3837
3889
|
}
|
|
3838
3890
|
}
|
|
@@ -3844,7 +3896,7 @@ function getFirstParamTypeName(fn, sourceFile) {
|
|
|
3844
3896
|
const firstParam = fn.parameters[0];
|
|
3845
3897
|
if (!firstParam) return void 0;
|
|
3846
3898
|
if (!firstParam.type) return void 0;
|
|
3847
|
-
if (!
|
|
3899
|
+
if (!ts10.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
3848
3900
|
return firstParam.type.typeName.getText(sourceFile);
|
|
3849
3901
|
}
|
|
3850
3902
|
|
|
@@ -4729,17 +4781,24 @@ function encodeSseEvent(event) {
|
|
|
4729
4781
|
out += "\n";
|
|
4730
4782
|
return out;
|
|
4731
4783
|
}
|
|
4732
|
-
function createSseWriter() {
|
|
4784
|
+
function createSseWriter(options = {}) {
|
|
4785
|
+
const { onFirstWrite } = options;
|
|
4733
4786
|
const encoder = new TextEncoder();
|
|
4734
4787
|
let controller = null;
|
|
4735
4788
|
let closed = false;
|
|
4736
4789
|
let aborted = false;
|
|
4790
|
+
let firstWriteTriggered = false;
|
|
4737
4791
|
let drainWaiters = [];
|
|
4738
4792
|
const resolveDrainWaiters = () => {
|
|
4739
4793
|
const waiters = drainWaiters;
|
|
4740
4794
|
drainWaiters = [];
|
|
4741
4795
|
for (const resolve of waiters) resolve();
|
|
4742
4796
|
};
|
|
4797
|
+
const triggerFirstWrite = () => {
|
|
4798
|
+
if (firstWriteTriggered) return;
|
|
4799
|
+
firstWriteTriggered = true;
|
|
4800
|
+
onFirstWrite?.(response);
|
|
4801
|
+
};
|
|
4743
4802
|
const stream = new ReadableStream(
|
|
4744
4803
|
{
|
|
4745
4804
|
start(c) {
|
|
@@ -4773,11 +4832,13 @@ function createSseWriter() {
|
|
|
4773
4832
|
if (closed || !controller) return;
|
|
4774
4833
|
const text = encodeSseEvent(event);
|
|
4775
4834
|
controller.enqueue(encoder.encode(text));
|
|
4835
|
+
triggerFirstWrite();
|
|
4776
4836
|
},
|
|
4777
4837
|
sendRaw(chunk) {
|
|
4778
4838
|
if (closed || !controller) return;
|
|
4779
4839
|
const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
|
|
4780
4840
|
controller.enqueue(bytes);
|
|
4841
|
+
triggerFirstWrite();
|
|
4781
4842
|
},
|
|
4782
4843
|
sendError(error) {
|
|
4783
4844
|
if (closed || !controller) return;
|
|
@@ -4785,6 +4846,7 @@ function createSseWriter() {
|
|
|
4785
4846
|
const text = encodeSseEvent({ event: "error", data: message });
|
|
4786
4847
|
try {
|
|
4787
4848
|
controller.enqueue(encoder.encode(text));
|
|
4849
|
+
triggerFirstWrite();
|
|
4788
4850
|
} finally {
|
|
4789
4851
|
writer.close();
|
|
4790
4852
|
}
|
|
@@ -4900,6 +4962,26 @@ function formatErrorResponse(error, config) {
|
|
|
4900
4962
|
return jsonOk(body, 500);
|
|
4901
4963
|
}
|
|
4902
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
|
+
|
|
4903
4985
|
// src/runtime/createContext.ts
|
|
4904
4986
|
function resolveRequestId(request) {
|
|
4905
4987
|
const header = request.headers.get("x-request-id");
|
|
@@ -4949,7 +5031,11 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4949
5031
|
log,
|
|
4950
5032
|
request,
|
|
4951
5033
|
params,
|
|
4952
|
-
|
|
5034
|
+
rawParams: params,
|
|
5035
|
+
// ctx.query 为对象口径(createContext 挂原始 string 值对象;handler 声明
|
|
5036
|
+
// query 类型时管线校验后替换为转换值)。原始 URLSearchParams 恒在 rawQuery。
|
|
5037
|
+
query: queryToObject(url.searchParams),
|
|
5038
|
+
rawQuery: url.searchParams,
|
|
4953
5039
|
headers: request.headers,
|
|
4954
5040
|
method: request.method,
|
|
4955
5041
|
path: url.pathname,
|
|
@@ -4997,13 +5083,19 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4997
5083
|
/**
|
|
4998
5084
|
* 创建 SSE writer,用于流式推送事件
|
|
4999
5085
|
*
|
|
5000
|
-
* handler 调用此方法后,通过返回的 writer
|
|
5001
|
-
*
|
|
5086
|
+
* handler 调用此方法后,通过返回的 writer 推送事件。HTTP 服务场景下首次
|
|
5087
|
+
* 写入(send/sendRaw/sendError)即触发 server 层提前接管钩子,把 writer.response
|
|
5088
|
+
* 接入底层连接——流式期间字节即产即达(见 createServer.md「SSE 提前接管」)。
|
|
5089
|
+
* 无钩子场景(app.inject / 测试直调 invokeHandler)回落为 handler 返回后交出。
|
|
5002
5090
|
*
|
|
5003
5091
|
* 与 ctx.json / ctx.html 互斥:一个 handler 只能用一种响应方式。
|
|
5004
5092
|
*/
|
|
5005
5093
|
sse() {
|
|
5006
|
-
const writer = createSseWriter(
|
|
5094
|
+
const writer = createSseWriter({
|
|
5095
|
+
onFirstWrite: (response) => {
|
|
5096
|
+
ctx.__earlyRespond?.(response);
|
|
5097
|
+
}
|
|
5098
|
+
});
|
|
5007
5099
|
const ctxWithSse = ctx;
|
|
5008
5100
|
ctxWithSse.__sseResponse = writer.response;
|
|
5009
5101
|
ctxWithSse.__sseWriter = writer;
|
|
@@ -5049,26 +5141,6 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
5049
5141
|
return ctx;
|
|
5050
5142
|
}
|
|
5051
5143
|
|
|
5052
|
-
// src/utils/queryToObject.ts
|
|
5053
|
-
var queryCache = /* @__PURE__ */ new WeakMap();
|
|
5054
|
-
function queryToObject(params) {
|
|
5055
|
-
const cached = queryCache.get(params);
|
|
5056
|
-
if (cached) return cached;
|
|
5057
|
-
const result = {};
|
|
5058
|
-
for (const [key, value] of params) {
|
|
5059
|
-
const existing = result[key];
|
|
5060
|
-
if (existing === void 0) {
|
|
5061
|
-
result[key] = value;
|
|
5062
|
-
} else if (Array.isArray(existing)) {
|
|
5063
|
-
existing.push(value);
|
|
5064
|
-
} else {
|
|
5065
|
-
result[key] = [existing, value];
|
|
5066
|
-
}
|
|
5067
|
-
}
|
|
5068
|
-
queryCache.set(params, result);
|
|
5069
|
-
return result;
|
|
5070
|
-
}
|
|
5071
|
-
|
|
5072
5144
|
// src/utils/parseJsonBody.ts
|
|
5073
5145
|
function parseJsonBody(text) {
|
|
5074
5146
|
try {
|
|
@@ -5114,46 +5186,29 @@ init_inputType();
|
|
|
5114
5186
|
async function resolveInputFromUrl(method, request, url) {
|
|
5115
5187
|
const inputType = getInputTypeForMethod(method);
|
|
5116
5188
|
if (inputType === "body") {
|
|
5117
|
-
|
|
5118
|
-
if (contentType.includes("multipart/form-data")) {
|
|
5119
|
-
return parseMultipart(request);
|
|
5120
|
-
}
|
|
5121
|
-
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
5122
|
-
const text2 = await request.text();
|
|
5123
|
-
if (isBlankText(text2)) return null;
|
|
5124
|
-
const params = new URLSearchParams(text2);
|
|
5125
|
-
const obj = {};
|
|
5126
|
-
for (const [key, value] of params) {
|
|
5127
|
-
obj[key] = value;
|
|
5128
|
-
}
|
|
5129
|
-
return obj;
|
|
5130
|
-
}
|
|
5131
|
-
const text = await request.text();
|
|
5132
|
-
if (isBlankText(text)) {
|
|
5133
|
-
return null;
|
|
5134
|
-
}
|
|
5135
|
-
const result = parseJsonBody(text);
|
|
5136
|
-
if (!result.success) {
|
|
5137
|
-
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
5138
|
-
{
|
|
5139
|
-
path: "body",
|
|
5140
|
-
code: "INVALID_FORMAT",
|
|
5141
|
-
expected: "JSON",
|
|
5142
|
-
received: "text",
|
|
5143
|
-
message: "\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON"
|
|
5144
|
-
}
|
|
5145
|
-
]);
|
|
5146
|
-
}
|
|
5147
|
-
return result.data;
|
|
5189
|
+
return parseBodyByContentType(request);
|
|
5148
5190
|
}
|
|
5149
|
-
return queryToObject(url.searchParams);
|
|
5191
|
+
return { input: queryToObject(url.searchParams) };
|
|
5150
5192
|
}
|
|
5151
|
-
function
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
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
|
+
}
|
|
5155
5208
|
const text = await request.text();
|
|
5156
|
-
if (isBlankText(text))
|
|
5209
|
+
if (isBlankText(text)) {
|
|
5210
|
+
return { input: null };
|
|
5211
|
+
}
|
|
5157
5212
|
const result = parseJsonBody(text);
|
|
5158
5213
|
if (!result.success) {
|
|
5159
5214
|
throw new ValidationError("\u8BF7\u6C42\u4F53\u4E0D\u662F\u5408\u6CD5\u7684 JSON", [
|
|
@@ -5166,7 +5221,13 @@ async function resolveBodyForQueryMethod(request) {
|
|
|
5166
5221
|
}
|
|
5167
5222
|
]);
|
|
5168
5223
|
}
|
|
5169
|
-
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);
|
|
5170
5231
|
}
|
|
5171
5232
|
|
|
5172
5233
|
// src/response/pendingMeta.ts
|
|
@@ -5301,12 +5362,17 @@ function markBuffered(response) {
|
|
|
5301
5362
|
init_resolveInjection();
|
|
5302
5363
|
function getBuiltinInjectionValue(type, ctx, body) {
|
|
5303
5364
|
switch (type) {
|
|
5304
|
-
case "query":
|
|
5305
|
-
|
|
5306
|
-
return validated ?? queryToObject(ctx.query);
|
|
5307
|
-
}
|
|
5365
|
+
case "query":
|
|
5366
|
+
return ctx.query;
|
|
5308
5367
|
case "params":
|
|
5309
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;
|
|
5310
5376
|
case "headers":
|
|
5311
5377
|
return ctx.headers;
|
|
5312
5378
|
case "context":
|
|
@@ -5822,6 +5888,9 @@ function buildErrorResponse(err, config) {
|
|
|
5822
5888
|
}
|
|
5823
5889
|
}
|
|
5824
5890
|
|
|
5891
|
+
// src/server/handleWsUpgrade.ts
|
|
5892
|
+
init_generateSchemaFiles();
|
|
5893
|
+
|
|
5825
5894
|
// src/middleware/loadMiddlewares.ts
|
|
5826
5895
|
var middlewareCache = /* @__PURE__ */ new Map();
|
|
5827
5896
|
var inFlight = /* @__PURE__ */ new Map();
|
|
@@ -6020,6 +6089,7 @@ function attachWebSocket(options) {
|
|
|
6020
6089
|
server,
|
|
6021
6090
|
routesRef,
|
|
6022
6091
|
rootDir,
|
|
6092
|
+
dist,
|
|
6023
6093
|
config,
|
|
6024
6094
|
globalMiddlewares,
|
|
6025
6095
|
trustedProxy = false,
|
|
@@ -6054,12 +6124,50 @@ function attachWebSocket(options) {
|
|
|
6054
6124
|
const { route, params } = match;
|
|
6055
6125
|
const headers = nodeHttpToWebHeaders(req);
|
|
6056
6126
|
const host = req.headers.host ?? "localhost";
|
|
6057
|
-
const
|
|
6058
|
-
const request = new Request(
|
|
6127
|
+
const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
|
|
6128
|
+
const request = new Request(fullUrl.toString(), { method: "GET", headers });
|
|
6059
6129
|
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
|
|
6060
6130
|
const meta = ctx.meta;
|
|
6061
6131
|
let upgraded = false;
|
|
6062
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
|
+
}
|
|
6063
6171
|
let handlers;
|
|
6064
6172
|
try {
|
|
6065
6173
|
const absoluteFilePath = path18.resolve(rootDir, route.filePath);
|
|
@@ -6267,7 +6375,8 @@ function createServer(options) {
|
|
|
6267
6375
|
globalInjectors,
|
|
6268
6376
|
bodyLimit,
|
|
6269
6377
|
trustedProxy,
|
|
6270
|
-
registries
|
|
6378
|
+
registries,
|
|
6379
|
+
routesRef.wsCurrent
|
|
6271
6380
|
).catch((err) => {
|
|
6272
6381
|
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
6273
6382
|
try {
|
|
@@ -6285,6 +6394,7 @@ function createServer(options) {
|
|
|
6285
6394
|
server,
|
|
6286
6395
|
routesRef,
|
|
6287
6396
|
rootDir,
|
|
6397
|
+
dist,
|
|
6288
6398
|
config,
|
|
6289
6399
|
globalMiddlewares,
|
|
6290
6400
|
trustedProxy,
|
|
@@ -6292,14 +6402,13 @@ function createServer(options) {
|
|
|
6292
6402
|
});
|
|
6293
6403
|
return { server, routesRef };
|
|
6294
6404
|
}
|
|
6295
|
-
function prepareRequest(req,
|
|
6296
|
-
const { request, url } = toWebRequest(req, bodyLimit, requestSignal);
|
|
6405
|
+
function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
|
|
6297
6406
|
const method = request.method.toUpperCase();
|
|
6298
6407
|
const urlPath = url.pathname;
|
|
6299
6408
|
const ctx = createContextFromUrl(
|
|
6300
6409
|
request,
|
|
6301
6410
|
url,
|
|
6302
|
-
{},
|
|
6411
|
+
preMatched?.params ?? {},
|
|
6303
6412
|
config,
|
|
6304
6413
|
getClientIp(req, trustedProxy),
|
|
6305
6414
|
registries
|
|
@@ -6330,19 +6439,33 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
6330
6439
|
}
|
|
6331
6440
|
var mergedInjectorsCache = /* @__PURE__ */ new WeakMap();
|
|
6332
6441
|
function createRoutePipeline(opts) {
|
|
6333
|
-
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;
|
|
6334
6455
|
return async () => {
|
|
6335
|
-
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
6336
|
-
ctx.params = match.params;
|
|
6456
|
+
const match = preMatched ?? resolveRouteOrThrow(routes, method, urlPath);
|
|
6337
6457
|
const { route } = match;
|
|
6338
6458
|
const { absFilePath, schemaPath } = getRoutePaths(route, rootDir, dist);
|
|
6339
6459
|
const routeModule = await loadRouteModule(absFilePath, route.method, rootDir);
|
|
6340
|
-
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
|
+
}
|
|
6341
6464
|
const inputType = getInputTypeForMethod(route.method);
|
|
6342
6465
|
if (isDevOnDemandEnabled()) {
|
|
6343
6466
|
const devDist = getDevDist();
|
|
6344
6467
|
if (devDist) {
|
|
6345
|
-
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist);
|
|
6468
|
+
await ensureSchemaGenerated(schemaPath, route.filePath, routes, rootDir, dist, wsRoutes);
|
|
6346
6469
|
}
|
|
6347
6470
|
}
|
|
6348
6471
|
const result = await validateInput(schemaPath, route.method, inputType, input);
|
|
@@ -6350,7 +6473,7 @@ function createRoutePipeline(opts) {
|
|
|
6350
6473
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", result.issues);
|
|
6351
6474
|
}
|
|
6352
6475
|
if (inputType === "query" && result.data !== input && typeof result.data === "object" && result.data !== null) {
|
|
6353
|
-
ctx.
|
|
6476
|
+
ctx.query = {
|
|
6354
6477
|
...input,
|
|
6355
6478
|
...result.data
|
|
6356
6479
|
};
|
|
@@ -6372,7 +6495,7 @@ function createRoutePipeline(opts) {
|
|
|
6372
6495
|
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", queryResult.issues);
|
|
6373
6496
|
}
|
|
6374
6497
|
if (queryResult.data !== rawQuery && typeof queryResult.data === "object" && queryResult.data !== null) {
|
|
6375
|
-
ctx.
|
|
6498
|
+
ctx.query = {
|
|
6376
6499
|
...rawQuery,
|
|
6377
6500
|
...queryResult.data
|
|
6378
6501
|
};
|
|
@@ -6381,16 +6504,20 @@ function createRoutePipeline(opts) {
|
|
|
6381
6504
|
let body;
|
|
6382
6505
|
if (inputType === "query" && hasBody(route.method)) {
|
|
6383
6506
|
const parsed = await resolveBodyForQueryMethod(request);
|
|
6384
|
-
if (parsed !== void 0) {
|
|
6385
|
-
|
|
6386
|
-
if (!bodyResult.valid) {
|
|
6387
|
-
throw new ValidationError("\u53C2\u6570\u6821\u9A8C\u5931\u8D25", bodyResult.issues);
|
|
6388
|
-
}
|
|
6389
|
-
body = bodyResult.data;
|
|
6507
|
+
if (parsed.rawBody !== void 0) {
|
|
6508
|
+
ctx.rawBody = parsed.rawBody;
|
|
6390
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;
|
|
6391
6515
|
} else if (hasBody(route.method)) {
|
|
6392
6516
|
body = result.data;
|
|
6393
6517
|
}
|
|
6518
|
+
if (body !== void 0 && body !== null) {
|
|
6519
|
+
ctx.body = body;
|
|
6520
|
+
}
|
|
6394
6521
|
if (route.middlewares === void 0 && route.injectors === void 0 && route.middlewarePaths) {
|
|
6395
6522
|
await ensureMiddlewaresCompiled(route.middlewarePaths, rootDir);
|
|
6396
6523
|
const bundle = await loadMergedMiddlewares(route.middlewarePaths);
|
|
@@ -6426,25 +6553,37 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
6426
6553
|
}
|
|
6427
6554
|
}
|
|
6428
6555
|
}
|
|
6429
|
-
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) {
|
|
6430
6557
|
let meta = { headers: {}, setCookies: [] };
|
|
6431
6558
|
let ctx;
|
|
6559
|
+
let sseEarlySent = false;
|
|
6432
6560
|
const abortController = new AbortController();
|
|
6433
6561
|
res.on("close", () => {
|
|
6434
6562
|
if (!res.writableEnded) abortController.abort();
|
|
6435
6563
|
});
|
|
6436
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);
|
|
6437
6569
|
const prepared = prepareRequest(
|
|
6438
6570
|
req,
|
|
6571
|
+
request,
|
|
6572
|
+
url,
|
|
6439
6573
|
config,
|
|
6440
|
-
bodyLimit,
|
|
6441
6574
|
trustedProxy,
|
|
6442
6575
|
registries,
|
|
6443
|
-
|
|
6576
|
+
preMatched
|
|
6444
6577
|
);
|
|
6445
6578
|
ctx = prepared.ctx;
|
|
6446
6579
|
meta = prepared.meta;
|
|
6447
|
-
|
|
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
|
+
};
|
|
6448
6587
|
const routePipeline = createRoutePipeline({
|
|
6449
6588
|
routes,
|
|
6450
6589
|
method,
|
|
@@ -6454,12 +6593,25 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
6454
6593
|
request,
|
|
6455
6594
|
rootDir,
|
|
6456
6595
|
dist,
|
|
6457
|
-
globalInjectors
|
|
6596
|
+
globalInjectors,
|
|
6597
|
+
preMatched,
|
|
6598
|
+
wsRoutes
|
|
6458
6599
|
});
|
|
6459
6600
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
6460
|
-
|
|
6601
|
+
if (!sseEarlySent) {
|
|
6602
|
+
await sendSuccessResponse(response, res);
|
|
6603
|
+
}
|
|
6461
6604
|
} catch (err) {
|
|
6462
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
|
+
}
|
|
6463
6615
|
if (err instanceof PayloadTooLargeError) {
|
|
6464
6616
|
res.setHeader("Connection", "close");
|
|
6465
6617
|
res.once("finish", () => req.destroy());
|
|
@@ -6730,7 +6882,7 @@ import path25 from "path";
|
|
|
6730
6882
|
import path23 from "path";
|
|
6731
6883
|
|
|
6732
6884
|
// src/ast/extractAgentMetadata.ts
|
|
6733
|
-
import
|
|
6885
|
+
import ts11 from "typescript";
|
|
6734
6886
|
init_resolveTypeNode();
|
|
6735
6887
|
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
6736
6888
|
const sourceFile = program.getSourceFile(filePath);
|
|
@@ -6775,23 +6927,23 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
6775
6927
|
}
|
|
6776
6928
|
function findConfigExport(sourceFile) {
|
|
6777
6929
|
let result = null;
|
|
6778
|
-
|
|
6930
|
+
ts11.forEachChild(sourceFile, (node) => {
|
|
6779
6931
|
if (result) return;
|
|
6780
|
-
if (
|
|
6932
|
+
if (ts11.isVariableStatement(node) && hasExportModifier(node)) {
|
|
6781
6933
|
for (const decl of node.declarationList.declarations) {
|
|
6782
6934
|
if (result) break;
|
|
6783
|
-
const nameText =
|
|
6935
|
+
const nameText = ts11.isIdentifier(decl.name) ? decl.name.text : "";
|
|
6784
6936
|
if (nameText !== "config" || !decl.initializer) continue;
|
|
6785
|
-
if (
|
|
6937
|
+
if (ts11.isObjectLiteralExpression(decl.initializer)) {
|
|
6786
6938
|
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
6787
|
-
} else if (
|
|
6939
|
+
} else if (ts11.isArrowFunction(decl.initializer)) {
|
|
6788
6940
|
result = { jsDocOwner: node, objectLiteral: getReturnObjectLiteral(decl.initializer) };
|
|
6789
6941
|
} else {
|
|
6790
6942
|
result = { jsDocOwner: node, objectLiteral: null };
|
|
6791
6943
|
}
|
|
6792
6944
|
}
|
|
6793
6945
|
}
|
|
6794
|
-
if (
|
|
6946
|
+
if (ts11.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === "config") {
|
|
6795
6947
|
const returnObj = getReturnObjectLiteral(node);
|
|
6796
6948
|
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
6797
6949
|
}
|
|
@@ -6801,12 +6953,12 @@ function findConfigExport(sourceFile) {
|
|
|
6801
6953
|
function getReturnObjectLiteral(fn) {
|
|
6802
6954
|
const body = fn.body;
|
|
6803
6955
|
if (!body) return null;
|
|
6804
|
-
if (
|
|
6956
|
+
if (ts11.isObjectLiteralExpression(body)) {
|
|
6805
6957
|
return body;
|
|
6806
6958
|
}
|
|
6807
|
-
if (
|
|
6959
|
+
if (ts11.isBlock(body)) {
|
|
6808
6960
|
for (const stmt of body.statements) {
|
|
6809
|
-
if (
|
|
6961
|
+
if (ts11.isReturnStatement(stmt) && stmt.expression && ts11.isObjectLiteralExpression(stmt.expression)) {
|
|
6810
6962
|
return stmt.expression;
|
|
6811
6963
|
}
|
|
6812
6964
|
}
|
|
@@ -6821,8 +6973,8 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
6821
6973
|
let maxTurns;
|
|
6822
6974
|
let inputDescription;
|
|
6823
6975
|
for (const prop of objLit.properties) {
|
|
6824
|
-
if (
|
|
6825
|
-
if (!
|
|
6976
|
+
if (ts11.isSpreadAssignment(prop)) continue;
|
|
6977
|
+
if (!ts11.isPropertyAssignment(prop)) {
|
|
6826
6978
|
throw SchemaExtractionError.at(
|
|
6827
6979
|
prop,
|
|
6828
6980
|
"config",
|
|
@@ -6873,13 +7025,13 @@ function extractConfigFields(objLit, sourceFile) {
|
|
|
6873
7025
|
return { systemPrompt, tools, agents, model, maxTurns, inputDescription };
|
|
6874
7026
|
}
|
|
6875
7027
|
function getPropertyName(name) {
|
|
6876
|
-
if (
|
|
6877
|
-
if (
|
|
7028
|
+
if (ts11.isIdentifier(name)) return name.text;
|
|
7029
|
+
if (ts11.isStringLiteral(name)) return name.text;
|
|
6878
7030
|
return null;
|
|
6879
7031
|
}
|
|
6880
7032
|
function extractStringValue(expr) {
|
|
6881
7033
|
if (isStringLikeLiteral(expr)) return expr.text;
|
|
6882
|
-
if (
|
|
7034
|
+
if (ts11.isBinaryExpression(expr) && expr.operatorToken.kind === ts11.SyntaxKind.PlusToken) {
|
|
6883
7035
|
const left = extractStringValue(expr.left);
|
|
6884
7036
|
if (left === void 0) return void 0;
|
|
6885
7037
|
const right = extractStringValue(expr.right);
|
|
@@ -6925,17 +7077,17 @@ function requireNumberValue(prop, fieldName, sourceFile) {
|
|
|
6925
7077
|
return value;
|
|
6926
7078
|
}
|
|
6927
7079
|
function extractNumberValue(expr) {
|
|
6928
|
-
if (
|
|
7080
|
+
if (ts11.isNumericLiteral(expr)) {
|
|
6929
7081
|
const num = Number(expr.text);
|
|
6930
7082
|
return Number.isNaN(num) ? void 0 : num;
|
|
6931
7083
|
}
|
|
6932
7084
|
return void 0;
|
|
6933
7085
|
}
|
|
6934
7086
|
function isStringLikeLiteral(node) {
|
|
6935
|
-
return
|
|
7087
|
+
return ts11.isStringLiteral(node) || ts11.isNoSubstitutionTemplateLiteral(node);
|
|
6936
7088
|
}
|
|
6937
7089
|
function extractStringArrayValue(expr) {
|
|
6938
|
-
if (!
|
|
7090
|
+
if (!ts11.isArrayLiteralExpression(expr)) return void 0;
|
|
6939
7091
|
const values = [];
|
|
6940
7092
|
for (const element of expr.elements) {
|
|
6941
7093
|
const value = extractStringValue(element);
|
|
@@ -7912,14 +8064,14 @@ async function createDevApp(options) {
|
|
|
7912
8064
|
void (async () => {
|
|
7913
8065
|
try {
|
|
7914
8066
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
7915
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
8067
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
7916
8068
|
} catch (err) {
|
|
7917
8069
|
console.error("[faapi] Background schema regeneration failed:", err);
|
|
7918
8070
|
}
|
|
7919
8071
|
})();
|
|
7920
8072
|
} else {
|
|
7921
8073
|
const { generateSchemaFiles: generateSchemaFiles2 } = await Promise.resolve().then(() => (init_generateSchemaFiles(), generateSchemaFiles_exports));
|
|
7922
|
-
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist);
|
|
8074
|
+
await generateSchemaFiles2(sorted, ctx.rootDir, ctx.dist, reScanned.wsRoutes);
|
|
7923
8075
|
}
|
|
7924
8076
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
7925
8077
|
};
|