@faapi/faapi 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +214 -111
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +51 -5
- package/dist/index.js +227 -122
- package/dist/index.js.map +1 -1
- package/dist/testing.js +196 -102
- package/dist/testing.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -67,6 +67,45 @@ function createProgram(filePath) {
|
|
|
67
67
|
if (cached) {
|
|
68
68
|
return cached;
|
|
69
69
|
}
|
|
70
|
+
const program = buildProgram([filePath], findTsConfig(filePath));
|
|
71
|
+
programCache.set(filePath, program);
|
|
72
|
+
return program;
|
|
73
|
+
}
|
|
74
|
+
function createPrograms(filePaths) {
|
|
75
|
+
const unique = [...new Set(filePaths)];
|
|
76
|
+
const result = /* @__PURE__ */ new Map();
|
|
77
|
+
const groups = /* @__PURE__ */ new Map();
|
|
78
|
+
const noTsconfigFiles = [];
|
|
79
|
+
for (const filePath of unique) {
|
|
80
|
+
const tsconfigPath = findTsConfig(filePath);
|
|
81
|
+
if (!tsconfigPath) {
|
|
82
|
+
noTsconfigFiles.push(filePath);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const group = groups.get(tsconfigPath);
|
|
86
|
+
if (group) {
|
|
87
|
+
group.files.push(filePath);
|
|
88
|
+
} else {
|
|
89
|
+
groups.set(tsconfigPath, { tsconfigPath, files: [filePath] });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const { tsconfigPath, files } of groups.values()) {
|
|
93
|
+
const cacheKey = `shared::${tsconfigPath}::${[...files].sort().join("|")}`;
|
|
94
|
+
let program = programCache.get(cacheKey);
|
|
95
|
+
if (!program) {
|
|
96
|
+
program = buildProgram(files, tsconfigPath);
|
|
97
|
+
programCache.set(cacheKey, program);
|
|
98
|
+
}
|
|
99
|
+
for (const filePath of files) {
|
|
100
|
+
result.set(filePath, program);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const filePath of noTsconfigFiles) {
|
|
104
|
+
result.set(filePath, createProgram(filePath));
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function buildProgram(entryFiles, tsconfigPath) {
|
|
70
109
|
const options = {
|
|
71
110
|
strict: true,
|
|
72
111
|
target: ts.ScriptTarget.ES2022,
|
|
@@ -75,8 +114,7 @@ function createProgram(filePath) {
|
|
|
75
114
|
skipLibCheck: true,
|
|
76
115
|
noEmit: true
|
|
77
116
|
};
|
|
78
|
-
|
|
79
|
-
const tsconfigPath = findTsConfig(filePath);
|
|
117
|
+
const rootNames = [...entryFiles];
|
|
80
118
|
if (tsconfigPath) {
|
|
81
119
|
const tsOptions = parseTsConfig(tsconfigPath);
|
|
82
120
|
if (tsOptions.module !== void 0) {
|
|
@@ -86,16 +124,14 @@ function createProgram(filePath) {
|
|
|
86
124
|
options.moduleResolution = tsOptions.moduleResolution;
|
|
87
125
|
}
|
|
88
126
|
if (tsOptions.fileNames.length > 0) {
|
|
89
|
-
|
|
90
|
-
rootNames
|
|
91
|
-
|
|
92
|
-
|
|
127
|
+
for (const fileName of tsOptions.fileNames) {
|
|
128
|
+
if (!rootNames.includes(fileName)) {
|
|
129
|
+
rootNames.push(fileName);
|
|
130
|
+
}
|
|
93
131
|
}
|
|
94
132
|
}
|
|
95
133
|
}
|
|
96
|
-
|
|
97
|
-
programCache.set(filePath, program);
|
|
98
|
-
return program;
|
|
134
|
+
return ts.createProgram(rootNames, options);
|
|
99
135
|
}
|
|
100
136
|
var programCache, tsConfigCache;
|
|
101
137
|
var init_createProgram = __esm({
|
|
@@ -1004,12 +1040,11 @@ function collectRouteSchemaSources(routes, rootDir) {
|
|
|
1004
1040
|
}
|
|
1005
1041
|
entry.methods.add(route.method);
|
|
1006
1042
|
}
|
|
1007
|
-
const programByFile =
|
|
1043
|
+
const programByFile = createPrograms([...methodsByFile.keys()]);
|
|
1008
1044
|
const allTypesByFile = /* @__PURE__ */ new Map();
|
|
1009
1045
|
const mergedAllTypes = /* @__PURE__ */ new Map();
|
|
1010
1046
|
for (const filePath of methodsByFile.keys()) {
|
|
1011
|
-
const program =
|
|
1012
|
-
programByFile.set(filePath, program);
|
|
1047
|
+
const program = programByFile.get(filePath);
|
|
1013
1048
|
const allTypes = extractAllTypes(program, filePath);
|
|
1014
1049
|
allTypesByFile.set(filePath, allTypes);
|
|
1015
1050
|
for (const [name, info] of allTypes) {
|
|
@@ -1877,6 +1912,7 @@ var state = createDevOnDemandState();
|
|
|
1877
1912
|
function clearCompiledFiles() {
|
|
1878
1913
|
state.compiledFiles.clear();
|
|
1879
1914
|
state.inFlightCompilations.clear();
|
|
1915
|
+
sourcePathCache.clear();
|
|
1880
1916
|
}
|
|
1881
1917
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
1882
1918
|
const inFlight = state.inFlightCompilations.get(sourceAbsPath);
|
|
@@ -1973,7 +2009,10 @@ async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
|
1973
2009
|
}
|
|
1974
2010
|
}
|
|
1975
2011
|
}
|
|
2012
|
+
var sourcePathCache = /* @__PURE__ */ new Map();
|
|
1976
2013
|
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
2014
|
+
const cached = sourcePathCache.get(prodAbsPath);
|
|
2015
|
+
if (cached) return cached;
|
|
1977
2016
|
const rel = path7.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
1978
2017
|
let relWithoutDist = rel;
|
|
1979
2018
|
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
@@ -1982,8 +2021,14 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
|
1982
2021
|
const srcRel = `src/${relWithoutDist}`;
|
|
1983
2022
|
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
1984
2023
|
const tsAbs = path7.resolve(rootDir, tsRel);
|
|
1985
|
-
|
|
1986
|
-
|
|
2024
|
+
let result;
|
|
2025
|
+
if (fs6.existsSync(tsAbs)) {
|
|
2026
|
+
result = tsAbs;
|
|
2027
|
+
} else {
|
|
2028
|
+
result = path7.resolve(rootDir, srcRel);
|
|
2029
|
+
}
|
|
2030
|
+
sourcePathCache.set(prodAbsPath, result);
|
|
2031
|
+
return result;
|
|
1987
2032
|
}
|
|
1988
2033
|
function isDevOnDemandEnabled() {
|
|
1989
2034
|
return state.enabled;
|
|
@@ -2224,15 +2269,16 @@ function collectToolSchemaSources(tools, rootDir) {
|
|
|
2224
2269
|
}
|
|
2225
2270
|
list.push(tool);
|
|
2226
2271
|
}
|
|
2272
|
+
const programByFile = createPrograms([...toolsByFile.keys()]);
|
|
2227
2273
|
const allTypesByFile = /* @__PURE__ */ new Map();
|
|
2228
2274
|
for (const filePath of toolsByFile.keys()) {
|
|
2229
|
-
const program =
|
|
2275
|
+
const program = programByFile.get(filePath);
|
|
2230
2276
|
const allTypes = extractAllTypes(program, filePath);
|
|
2231
2277
|
allTypesByFile.set(filePath, allTypes);
|
|
2232
2278
|
}
|
|
2233
2279
|
const sources = [];
|
|
2234
2280
|
for (const [filePath, fileTools] of toolsByFile) {
|
|
2235
|
-
const program =
|
|
2281
|
+
const program = programByFile.get(filePath);
|
|
2236
2282
|
for (const tool of fileTools) {
|
|
2237
2283
|
const inputTypeName = tool.inputTypeName;
|
|
2238
2284
|
const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
|
|
@@ -2285,9 +2331,10 @@ async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
|
2285
2331
|
}
|
|
2286
2332
|
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
2287
2333
|
const metadata = [];
|
|
2334
|
+
const programByFile = createPrograms(tools.map((m) => path8.resolve(rootDir, m.filePath)));
|
|
2288
2335
|
for (const manifest of tools) {
|
|
2289
2336
|
const absPath = path8.resolve(rootDir, manifest.filePath);
|
|
2290
|
-
const program =
|
|
2337
|
+
const program = programByFile.get(absPath);
|
|
2291
2338
|
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
2292
2339
|
name: manifest.name,
|
|
2293
2340
|
filePath: manifest.filePath
|
|
@@ -2353,11 +2400,14 @@ function getDist() {
|
|
|
2353
2400
|
}
|
|
2354
2401
|
return process.env.FAAPI_DIST ?? "dist";
|
|
2355
2402
|
}
|
|
2403
|
+
function getToolSchemaPath(tool, rootDir) {
|
|
2404
|
+
const dist = getDist();
|
|
2405
|
+
return getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
|
|
2406
|
+
}
|
|
2356
2407
|
async function loadToolSchema(tool, rootDir) {
|
|
2357
2408
|
if (!tool.inputTypeName) return void 0;
|
|
2358
2409
|
const schemaName = `${tool.inputTypeName}Schema`;
|
|
2359
|
-
const
|
|
2360
|
-
const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
|
|
2410
|
+
const zodPath = getToolSchemaPath(tool, rootDir);
|
|
2361
2411
|
if (!existsSync2(zodPath)) return void 0;
|
|
2362
2412
|
try {
|
|
2363
2413
|
const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
|
|
@@ -2701,7 +2751,7 @@ var PayloadTooLargeError = class extends FaapiError {
|
|
|
2701
2751
|
};
|
|
2702
2752
|
|
|
2703
2753
|
// src/cli/createAppCore.ts
|
|
2704
|
-
import
|
|
2754
|
+
import fs15 from "fs";
|
|
2705
2755
|
import path15 from "path";
|
|
2706
2756
|
import { PassThrough, Readable as Readable3 } from "stream";
|
|
2707
2757
|
|
|
@@ -2758,15 +2808,50 @@ import { Readable as Readable2 } from "stream";
|
|
|
2758
2808
|
import path12 from "path";
|
|
2759
2809
|
|
|
2760
2810
|
// src/router/matchRoute.ts
|
|
2761
|
-
|
|
2811
|
+
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
2812
|
+
var wsIndexCache = /* @__PURE__ */ new WeakMap();
|
|
2813
|
+
function getHttpIndex(routes) {
|
|
2814
|
+
let index = httpIndexCache.get(routes);
|
|
2815
|
+
if (index) return index;
|
|
2816
|
+
index = { static: /* @__PURE__ */ new Map(), methodsByStaticPath: /* @__PURE__ */ new Map(), dynamics: [] };
|
|
2762
2817
|
for (const route of routes) {
|
|
2763
|
-
if (route.
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2818
|
+
if (route.isDynamic) {
|
|
2819
|
+
index.dynamics.push(route);
|
|
2820
|
+
} else {
|
|
2821
|
+
index.static.set(`${route.method}|${route.urlPath}`, route);
|
|
2822
|
+
let methods = index.methodsByStaticPath.get(route.urlPath);
|
|
2823
|
+
if (!methods) {
|
|
2824
|
+
methods = /* @__PURE__ */ new Set();
|
|
2825
|
+
index.methodsByStaticPath.set(route.urlPath, methods);
|
|
2769
2826
|
}
|
|
2827
|
+
methods.add(route.method);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
httpIndexCache.set(routes, index);
|
|
2831
|
+
return index;
|
|
2832
|
+
}
|
|
2833
|
+
function getWsIndex(routes) {
|
|
2834
|
+
let index = wsIndexCache.get(routes);
|
|
2835
|
+
if (index) return index;
|
|
2836
|
+
index = { static: /* @__PURE__ */ new Map(), dynamics: [] };
|
|
2837
|
+
for (const route of routes) {
|
|
2838
|
+
if (route.isDynamic) {
|
|
2839
|
+
index.dynamics.push(route);
|
|
2840
|
+
} else {
|
|
2841
|
+
index.static.set(route.urlPath, route);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
wsIndexCache.set(routes, index);
|
|
2845
|
+
return index;
|
|
2846
|
+
}
|
|
2847
|
+
function matchRoute(routes, method, path19) {
|
|
2848
|
+
const index = getHttpIndex(routes);
|
|
2849
|
+
const staticHit = index.static.get(`${method}|${path19}`);
|
|
2850
|
+
if (staticHit) {
|
|
2851
|
+
return { route: staticHit, params: {} };
|
|
2852
|
+
}
|
|
2853
|
+
for (const route of index.dynamics) {
|
|
2854
|
+
if (route.method !== method) {
|
|
2770
2855
|
continue;
|
|
2771
2856
|
}
|
|
2772
2857
|
const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
|
|
@@ -2777,13 +2862,12 @@ function matchRoute(routes, method, path19) {
|
|
|
2777
2862
|
return null;
|
|
2778
2863
|
}
|
|
2779
2864
|
function matchWsRoute(wsRoutes, path19) {
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
}
|
|
2865
|
+
const index = getWsIndex(wsRoutes);
|
|
2866
|
+
const staticHit = index.static.get(path19);
|
|
2867
|
+
if (staticHit) {
|
|
2868
|
+
return { route: staticHit, params: {} };
|
|
2869
|
+
}
|
|
2870
|
+
for (const route of index.dynamics) {
|
|
2787
2871
|
const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
|
|
2788
2872
|
if (params !== null) {
|
|
2789
2873
|
return { route, params };
|
|
@@ -2791,6 +2875,23 @@ function matchWsRoute(wsRoutes, path19) {
|
|
|
2791
2875
|
}
|
|
2792
2876
|
return null;
|
|
2793
2877
|
}
|
|
2878
|
+
function findAllowedMethods(routes, path19) {
|
|
2879
|
+
const index = getHttpIndex(routes);
|
|
2880
|
+
const methods = /* @__PURE__ */ new Set();
|
|
2881
|
+
const staticMethods = index.methodsByStaticPath.get(path19);
|
|
2882
|
+
if (staticMethods) {
|
|
2883
|
+
for (const method of staticMethods) {
|
|
2884
|
+
methods.add(method);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
for (const route of index.dynamics) {
|
|
2888
|
+
const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
|
|
2889
|
+
if (params !== null) {
|
|
2890
|
+
methods.add(route.method);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
return Array.from(methods);
|
|
2894
|
+
}
|
|
2794
2895
|
function matchDynamicPath(pattern, path19, paramNames, isCatchAll) {
|
|
2795
2896
|
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2796
2897
|
const pathSegments = path19.split("/").filter(Boolean);
|
|
@@ -2838,9 +2939,6 @@ function matchDynamicPath(pattern, path19, paramNames, isCatchAll) {
|
|
|
2838
2939
|
return params;
|
|
2839
2940
|
}
|
|
2840
2941
|
|
|
2841
|
-
// src/loader/loadRouteModule.ts
|
|
2842
|
-
import fs12 from "fs";
|
|
2843
|
-
|
|
2844
2942
|
// src/loader/validateRouteModule.ts
|
|
2845
2943
|
function validateRouteModule(value, method, filePath) {
|
|
2846
2944
|
if (typeof value !== "function") {
|
|
@@ -2856,7 +2954,7 @@ async function loadRouteModule(filePath, method, rootDir) {
|
|
|
2856
2954
|
const dist = getDevDist();
|
|
2857
2955
|
if (dist) {
|
|
2858
2956
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2859
|
-
if (sourcePath
|
|
2957
|
+
if (sourcePath) {
|
|
2860
2958
|
try {
|
|
2861
2959
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2862
2960
|
} catch (compileErr) {
|
|
@@ -3096,7 +3194,9 @@ function formatSetCookie(name, value, options) {
|
|
|
3096
3194
|
return cookie;
|
|
3097
3195
|
}
|
|
3098
3196
|
function createContext(request, params, config = {}, ip = "") {
|
|
3099
|
-
|
|
3197
|
+
return createContextFromUrl(request, new URL(request.url), params, config, ip);
|
|
3198
|
+
}
|
|
3199
|
+
function createContextFromUrl(request, url, params, config = {}, ip = "") {
|
|
3100
3200
|
const meta = { headers: {}, setCookies: [] };
|
|
3101
3201
|
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
3102
3202
|
const cookiesObj = {};
|
|
@@ -3251,7 +3351,7 @@ async function parseMultipart(request) {
|
|
|
3251
3351
|
|
|
3252
3352
|
// src/runtime/resolveInput.ts
|
|
3253
3353
|
init_inputType();
|
|
3254
|
-
async function
|
|
3354
|
+
async function resolveInputFromUrl(method, request, url) {
|
|
3255
3355
|
const inputType = getInputTypeForMethod(method);
|
|
3256
3356
|
if (inputType === "body") {
|
|
3257
3357
|
const contentType = request.headers.get("content-type") ?? "";
|
|
@@ -3286,7 +3386,6 @@ async function resolveInput(method, request) {
|
|
|
3286
3386
|
}
|
|
3287
3387
|
return result.data;
|
|
3288
3388
|
}
|
|
3289
|
-
const url = new URL(request.url);
|
|
3290
3389
|
return queryToObject(url.searchParams);
|
|
3291
3390
|
}
|
|
3292
3391
|
|
|
@@ -3658,11 +3757,13 @@ function mapReceivedFromMessage(message) {
|
|
|
3658
3757
|
init_inputType();
|
|
3659
3758
|
|
|
3660
3759
|
// src/utils/getClientIp.ts
|
|
3661
|
-
function getClientIp(req) {
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3760
|
+
function getClientIp(req, trustedProxy = false) {
|
|
3761
|
+
if (trustedProxy) {
|
|
3762
|
+
const xff = req.headers["x-forwarded-for"];
|
|
3763
|
+
if (typeof xff === "string" && xff.length > 0) {
|
|
3764
|
+
const first = xff.split(",")[0]?.trim();
|
|
3765
|
+
if (first) return first;
|
|
3766
|
+
}
|
|
3666
3767
|
}
|
|
3667
3768
|
const remote = req.socket?.remoteAddress;
|
|
3668
3769
|
if (remote) {
|
|
@@ -3675,7 +3776,7 @@ function getClientIp(req) {
|
|
|
3675
3776
|
}
|
|
3676
3777
|
|
|
3677
3778
|
// src/server/handleWsUpgrade.ts
|
|
3678
|
-
import
|
|
3779
|
+
import fs12 from "fs";
|
|
3679
3780
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3680
3781
|
import path11 from "path";
|
|
3681
3782
|
|
|
@@ -3803,7 +3904,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
3803
3904
|
const dist = getDevDist();
|
|
3804
3905
|
if (dist) {
|
|
3805
3906
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3806
|
-
if (sourcePath &&
|
|
3907
|
+
if (sourcePath && fs12.existsSync(sourcePath)) {
|
|
3807
3908
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3808
3909
|
}
|
|
3809
3910
|
}
|
|
@@ -3861,7 +3962,7 @@ async function sendResponseToSocket(socket, response) {
|
|
|
3861
3962
|
socket.destroy();
|
|
3862
3963
|
}
|
|
3863
3964
|
function attachWebSocket(options) {
|
|
3864
|
-
const { server, routesRef, rootDir, config, globalMiddlewares } = options;
|
|
3965
|
+
const { server, routesRef, rootDir, config, globalMiddlewares, trustedProxy = false } = options;
|
|
3865
3966
|
const wss = new WebSocketServer({ noServer: true });
|
|
3866
3967
|
server.on("upgrade", async (req, socket, head) => {
|
|
3867
3968
|
const currentWsRoutes = routesRef.wsCurrent;
|
|
@@ -3877,7 +3978,7 @@ function attachWebSocket(options) {
|
|
|
3877
3978
|
const host = req.headers.host ?? "localhost";
|
|
3878
3979
|
const url = `http://${host}${req.url ?? "/"}`;
|
|
3879
3980
|
const request = new Request(url, { method: "GET", headers });
|
|
3880
|
-
const ctx = createContext(request, params, config, getClientIp(req));
|
|
3981
|
+
const ctx = createContext(request, params, config, getClientIp(req, trustedProxy));
|
|
3881
3982
|
const meta = ctx.meta;
|
|
3882
3983
|
let upgraded = false;
|
|
3883
3984
|
const finalHandler = async () => {
|
|
@@ -3947,16 +4048,26 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
3947
4048
|
const headers = nodeHttpToWebHeaders(req);
|
|
3948
4049
|
const method = req.method ?? "GET";
|
|
3949
4050
|
if (method === "GET" || method === "HEAD") {
|
|
3950
|
-
return new Request(url.toString(), { method, headers });
|
|
4051
|
+
return { request: new Request(url.toString(), { method, headers }), url };
|
|
4052
|
+
}
|
|
4053
|
+
const contentLength = req.headers["content-length"];
|
|
4054
|
+
if (contentLength !== void 0) {
|
|
4055
|
+
const declared = Number(Array.isArray(contentLength) ? contentLength[0] : contentLength);
|
|
4056
|
+
if (Number.isFinite(declared) && declared > bodyLimit) {
|
|
4057
|
+
throw new PayloadTooLargeError(bodyLimit);
|
|
4058
|
+
}
|
|
3951
4059
|
}
|
|
3952
4060
|
const stream = Readable2.toWeb(req);
|
|
3953
4061
|
const limitedStream = limitStreamSize(stream, bodyLimit);
|
|
3954
|
-
return
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
4062
|
+
return {
|
|
4063
|
+
request: new Request(url.toString(), {
|
|
4064
|
+
method,
|
|
4065
|
+
headers,
|
|
4066
|
+
body: limitedStream,
|
|
4067
|
+
duplex: "half"
|
|
4068
|
+
}),
|
|
4069
|
+
url
|
|
4070
|
+
};
|
|
3960
4071
|
}
|
|
3961
4072
|
function limitStreamSize(stream, maxSize) {
|
|
3962
4073
|
let totalSize = 0;
|
|
@@ -4008,22 +4119,6 @@ function limitStreamSize(stream, maxSize) {
|
|
|
4008
4119
|
}
|
|
4009
4120
|
});
|
|
4010
4121
|
}
|
|
4011
|
-
function findAllowedMethods(routes, path19) {
|
|
4012
|
-
const methods = /* @__PURE__ */ new Set();
|
|
4013
|
-
for (const route of routes) {
|
|
4014
|
-
if (route.urlPath === path19) {
|
|
4015
|
-
methods.add(route.method);
|
|
4016
|
-
continue;
|
|
4017
|
-
}
|
|
4018
|
-
if (route.isDynamic) {
|
|
4019
|
-
const params = matchDynamicPath(route.urlPath, path19, route.paramNames, route.isCatchAll);
|
|
4020
|
-
if (params !== null) {
|
|
4021
|
-
methods.add(route.method);
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
}
|
|
4025
|
-
return Array.from(methods);
|
|
4026
|
-
}
|
|
4027
4122
|
function createServer(options) {
|
|
4028
4123
|
const {
|
|
4029
4124
|
routes,
|
|
@@ -4038,7 +4133,8 @@ function createServer(options) {
|
|
|
4038
4133
|
helmet: helmetOption,
|
|
4039
4134
|
logger: loggerOption,
|
|
4040
4135
|
bodyLimit = DEFAULT_BODY_LIMIT,
|
|
4041
|
-
http2: http2Option
|
|
4136
|
+
http2: http2Option,
|
|
4137
|
+
trustedProxy = false
|
|
4042
4138
|
} = options;
|
|
4043
4139
|
const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
|
|
4044
4140
|
const configMiddlewares = [];
|
|
@@ -4050,6 +4146,10 @@ function createServer(options) {
|
|
|
4050
4146
|
}
|
|
4051
4147
|
const loggerMiddlewareInst = loggerOption === false ? null : loggerOption === true || loggerOption === void 0 ? logger() : logger(loggerOption);
|
|
4052
4148
|
if (loggerMiddlewareInst) configMiddlewares.push(loggerMiddlewareInst);
|
|
4149
|
+
const outerMiddlewares = [...configMiddlewares];
|
|
4150
|
+
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
4151
|
+
outerMiddlewares.push(...globalMiddlewares);
|
|
4152
|
+
}
|
|
4053
4153
|
const server = (() => {
|
|
4054
4154
|
if (http2Option) {
|
|
4055
4155
|
const h2Opts = typeof http2Option === "object" ? http2Option : {};
|
|
@@ -4069,29 +4169,29 @@ function createServer(options) {
|
|
|
4069
4169
|
dist,
|
|
4070
4170
|
req,
|
|
4071
4171
|
res,
|
|
4072
|
-
|
|
4172
|
+
outerMiddlewares,
|
|
4073
4173
|
onError,
|
|
4074
4174
|
config,
|
|
4075
|
-
globalMiddlewares,
|
|
4076
4175
|
globalInjectors,
|
|
4077
|
-
bodyLimit
|
|
4176
|
+
bodyLimit,
|
|
4177
|
+
trustedProxy
|
|
4078
4178
|
).catch(() => {
|
|
4079
4179
|
res.statusCode = 500;
|
|
4080
4180
|
res.end();
|
|
4081
4181
|
});
|
|
4082
4182
|
});
|
|
4083
4183
|
if (routesRef.wsCurrent.length > 0) {
|
|
4084
|
-
attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares });
|
|
4184
|
+
attachWebSocket({ server, routesRef, rootDir, config, globalMiddlewares, trustedProxy });
|
|
4085
4185
|
}
|
|
4086
4186
|
return { server, routesRef };
|
|
4087
4187
|
}
|
|
4088
|
-
function prepareRequest(req, config, bodyLimit) {
|
|
4089
|
-
const request = toWebRequest(req, bodyLimit);
|
|
4188
|
+
function prepareRequest(req, config, bodyLimit, trustedProxy) {
|
|
4189
|
+
const { request, url } = toWebRequest(req, bodyLimit);
|
|
4090
4190
|
const method = request.method.toUpperCase();
|
|
4091
|
-
const urlPath =
|
|
4092
|
-
const ctx =
|
|
4191
|
+
const urlPath = url.pathname;
|
|
4192
|
+
const ctx = createContextFromUrl(request, url, {}, config, getClientIp(req, trustedProxy));
|
|
4093
4193
|
const meta = ctx.meta;
|
|
4094
|
-
return { request, ctx, meta, method, urlPath };
|
|
4194
|
+
return { request, url, ctx, meta, method, urlPath };
|
|
4095
4195
|
}
|
|
4096
4196
|
function resolveRouteOrThrow(routes, method, urlPath) {
|
|
4097
4197
|
const match = matchRoute(routes, method, urlPath);
|
|
@@ -4103,14 +4203,14 @@ function resolveRouteOrThrow(routes, method, urlPath) {
|
|
|
4103
4203
|
throw new RouteNotFoundError(urlPath);
|
|
4104
4204
|
}
|
|
4105
4205
|
function createRoutePipeline(opts) {
|
|
4106
|
-
const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
4206
|
+
const { routes, method, urlPath, url, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
4107
4207
|
return async () => {
|
|
4108
4208
|
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
4109
4209
|
ctx.params = match.params;
|
|
4110
4210
|
const { route } = match;
|
|
4111
4211
|
const absoluteFilePath = path12.resolve(rootDir, route.filePath);
|
|
4112
4212
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
4113
|
-
const input = await
|
|
4213
|
+
const input = await resolveInputFromUrl(route.method, request, url);
|
|
4114
4214
|
const inputType = getInputTypeForMethod(route.method);
|
|
4115
4215
|
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
4116
4216
|
if (isDevOnDemandEnabled()) {
|
|
@@ -4142,33 +4242,33 @@ async function sendSuccessResponse(response, res) {
|
|
|
4142
4242
|
await sendNodeResponse(response, res);
|
|
4143
4243
|
}
|
|
4144
4244
|
async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
4145
|
-
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx
|
|
4146
|
-
if (onError) {
|
|
4245
|
+
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx?.config), meta), res);
|
|
4246
|
+
if (onError && ctx) {
|
|
4147
4247
|
try {
|
|
4148
4248
|
await onError(err, ctx);
|
|
4149
4249
|
} catch {
|
|
4150
4250
|
}
|
|
4151
4251
|
}
|
|
4152
4252
|
}
|
|
4153
|
-
async function handleRequest(routes, rootDir, dist, req, res,
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
routes,
|
|
4157
|
-
method,
|
|
4158
|
-
urlPath,
|
|
4159
|
-
ctx,
|
|
4160
|
-
request,
|
|
4161
|
-
rootDir,
|
|
4162
|
-
dist,
|
|
4163
|
-
globalMiddlewares,
|
|
4164
|
-
globalInjectors
|
|
4165
|
-
});
|
|
4166
|
-
const outerMiddlewares = [];
|
|
4167
|
-
if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
|
|
4168
|
-
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
4169
|
-
outerMiddlewares.push(...globalMiddlewares);
|
|
4170
|
-
}
|
|
4253
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy) {
|
|
4254
|
+
let meta = { headers: {}, setCookies: [] };
|
|
4255
|
+
let ctx;
|
|
4171
4256
|
try {
|
|
4257
|
+
const prepared = prepareRequest(req, config, bodyLimit, trustedProxy);
|
|
4258
|
+
ctx = prepared.ctx;
|
|
4259
|
+
meta = prepared.meta;
|
|
4260
|
+
const { request, url, method, urlPath } = prepared;
|
|
4261
|
+
const routePipeline = createRoutePipeline({
|
|
4262
|
+
routes,
|
|
4263
|
+
method,
|
|
4264
|
+
urlPath,
|
|
4265
|
+
url,
|
|
4266
|
+
ctx,
|
|
4267
|
+
request,
|
|
4268
|
+
rootDir,
|
|
4269
|
+
dist,
|
|
4270
|
+
globalInjectors
|
|
4271
|
+
});
|
|
4172
4272
|
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
4173
4273
|
await sendSuccessResponse(response, res);
|
|
4174
4274
|
} catch (err) {
|
|
@@ -4205,7 +4305,7 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
4205
4305
|
}
|
|
4206
4306
|
|
|
4207
4307
|
// src/cli/generateRoutes.ts
|
|
4208
|
-
import
|
|
4308
|
+
import fs13 from "fs";
|
|
4209
4309
|
import path13 from "path";
|
|
4210
4310
|
async function hydrateRoutes(manifest) {
|
|
4211
4311
|
const hydrateRoute = (serialized) => ({
|
|
@@ -4232,7 +4332,7 @@ async function hydrateRoutes(manifest) {
|
|
|
4232
4332
|
|
|
4233
4333
|
// src/cli/generateAgentArtifacts.ts
|
|
4234
4334
|
import path14 from "path";
|
|
4235
|
-
import
|
|
4335
|
+
import fs14 from "fs/promises";
|
|
4236
4336
|
|
|
4237
4337
|
// src/ast/extractAgentMetadata.ts
|
|
4238
4338
|
import ts8 from "typescript";
|
|
@@ -4447,11 +4547,11 @@ function serializeAgents(agents, dist = "dist") {
|
|
|
4447
4547
|
}
|
|
4448
4548
|
async function writeAgentsModule(manifest, outputPath) {
|
|
4449
4549
|
const dir = path14.dirname(outputPath);
|
|
4450
|
-
await
|
|
4550
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
4451
4551
|
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
4452
4552
|
export const agents = ${JSON.stringify(manifest, null, 2)};
|
|
4453
4553
|
`;
|
|
4454
|
-
await
|
|
4554
|
+
await fs14.writeFile(outputPath, content, "utf-8");
|
|
4455
4555
|
}
|
|
4456
4556
|
function hydrateAgents(manifest) {
|
|
4457
4557
|
return manifest.map((a) => ({
|
|
@@ -4468,9 +4568,10 @@ function hydrateAgents(manifest) {
|
|
|
4468
4568
|
}
|
|
4469
4569
|
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
4470
4570
|
const metadata = [];
|
|
4571
|
+
const programByFile = createPrograms(agents.map((m) => path14.resolve(rootDir, m.filePath)));
|
|
4471
4572
|
for (const manifest of agents) {
|
|
4472
4573
|
const absPath = path14.resolve(rootDir, manifest.filePath);
|
|
4473
|
-
const program =
|
|
4574
|
+
const program = programByFile.get(absPath);
|
|
4474
4575
|
const result = extractAgentMetadata(program, absPath, {
|
|
4475
4576
|
name: manifest.name,
|
|
4476
4577
|
filePath: manifest.filePath,
|
|
@@ -4554,7 +4655,7 @@ var AGENTS_FILE2 = "faapi-agents.js";
|
|
|
4554
4655
|
var PATTERNS = ["src/api/**/*.ts"];
|
|
4555
4656
|
async function loadAndHydrateTools(rootDir, dist) {
|
|
4556
4657
|
const toolsPath = path15.resolve(rootDir, dist, TOOLS_FILE2);
|
|
4557
|
-
if (!
|
|
4658
|
+
if (!fs15.existsSync(toolsPath)) {
|
|
4558
4659
|
return [];
|
|
4559
4660
|
}
|
|
4560
4661
|
const serialized = await importWithCacheBust(toolsPath);
|
|
@@ -4564,7 +4665,7 @@ async function loadAndHydrateTools(rootDir, dist) {
|
|
|
4564
4665
|
}
|
|
4565
4666
|
async function loadAndHydrateAgents(rootDir, dist) {
|
|
4566
4667
|
const agentsPath = path15.resolve(rootDir, dist, AGENTS_FILE2);
|
|
4567
|
-
if (!
|
|
4668
|
+
if (!fs15.existsSync(agentsPath)) {
|
|
4568
4669
|
return [];
|
|
4569
4670
|
}
|
|
4570
4671
|
const serialized = await importWithCacheBust(agentsPath);
|
|
@@ -4603,6 +4704,7 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
4603
4704
|
"bodyLimit",
|
|
4604
4705
|
"logger",
|
|
4605
4706
|
"http2",
|
|
4707
|
+
"trustedProxy",
|
|
4606
4708
|
"response"
|
|
4607
4709
|
]);
|
|
4608
4710
|
function isFaapiConfigKey(key) {
|
|
@@ -4612,7 +4714,7 @@ async function createAppBase(options) {
|
|
|
4612
4714
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
4613
4715
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
4614
4716
|
const routesPath = path15.resolve(rootDir, dist, ROUTES_FILE);
|
|
4615
|
-
if (!
|
|
4717
|
+
if (!fs15.existsSync(routesPath)) {
|
|
4616
4718
|
throw new Error(
|
|
4617
4719
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
4618
4720
|
);
|
|
@@ -4647,7 +4749,8 @@ async function createAppBase(options) {
|
|
|
4647
4749
|
helmet: config?.helmet,
|
|
4648
4750
|
logger: config?.logger,
|
|
4649
4751
|
bodyLimit: config?.bodyLimit,
|
|
4650
|
-
http2: config?.http2
|
|
4752
|
+
http2: config?.http2,
|
|
4753
|
+
trustedProxy: config?.trustedProxy
|
|
4651
4754
|
});
|
|
4652
4755
|
const { handlerWrappers, upgradeWrappers } = await loadPlugins(config?.plugins, {
|
|
4653
4756
|
rootDir,
|
|
@@ -4834,7 +4937,7 @@ async function createAppBase(options) {
|
|
|
4834
4937
|
// src/router/scanRoutes.ts
|
|
4835
4938
|
import fg2 from "fast-glob";
|
|
4836
4939
|
import path16 from "path";
|
|
4837
|
-
import
|
|
4940
|
+
import fs16 from "fs";
|
|
4838
4941
|
|
|
4839
4942
|
// src/router/constants.ts
|
|
4840
4943
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
@@ -4914,7 +5017,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
4914
5017
|
const mwJsPath = path16.join(currentDir, "middlewares.js");
|
|
4915
5018
|
const absTsPath = path16.resolve(rootDir, mwTsPath);
|
|
4916
5019
|
const absJsPath = path16.resolve(rootDir, mwJsPath);
|
|
4917
|
-
const absMwPath =
|
|
5020
|
+
const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
|
|
4918
5021
|
if (absMwPath) {
|
|
4919
5022
|
const relMwPath = path16.relative(rootDir, absMwPath);
|
|
4920
5023
|
const prodAbsPath = path16.resolve(rootDir, toProdFilePath3(relMwPath, dist));
|
|
@@ -4924,7 +5027,7 @@ function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
|
4924
5027
|
for (const ext of [".ts", ".js"]) {
|
|
4925
5028
|
const mwPath = path16.join(currentDir, `middlewares${ext}`);
|
|
4926
5029
|
const absMwPath = path16.resolve(rootDir, mwPath);
|
|
4927
|
-
if (
|
|
5030
|
+
if (fs16.existsSync(absMwPath)) {
|
|
4928
5031
|
paths.push(absMwPath);
|
|
4929
5032
|
break;
|
|
4930
5033
|
}
|
|
@@ -4971,7 +5074,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
4971
5074
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
4972
5075
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
4973
5076
|
}
|
|
4974
|
-
const source = await
|
|
5077
|
+
const source = await fs16.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4975
5078
|
const exportNames = extractExportsFromSource(source);
|
|
4976
5079
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
4977
5080
|
for (const method of methods) {
|
|
@@ -5008,7 +5111,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
5008
5111
|
// src/tools/scanTools.ts
|
|
5009
5112
|
import fg3 from "fast-glob";
|
|
5010
5113
|
import path17 from "path";
|
|
5011
|
-
import
|
|
5114
|
+
import fs17 from "fs";
|
|
5012
5115
|
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
5013
5116
|
var TOOL_EXPORT_RE = new RegExp(
|
|
5014
5117
|
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
@@ -5059,7 +5162,7 @@ async function scanTools(rootDir, patterns) {
|
|
|
5059
5162
|
continue;
|
|
5060
5163
|
}
|
|
5061
5164
|
const absPath = path17.resolve(rootDir, normalizedFile);
|
|
5062
|
-
const source = await
|
|
5165
|
+
const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5063
5166
|
const exportNames = extractToolExportsFromSource(source);
|
|
5064
5167
|
const namespace = filePathToToolNamespace(normalizedFile);
|
|
5065
5168
|
for (const fnName of exportNames) {
|
|
@@ -5084,7 +5187,7 @@ async function scanTools(rootDir, patterns) {
|
|
|
5084
5187
|
// src/agents/scanAgents.ts
|
|
5085
5188
|
import fg4 from "fast-glob";
|
|
5086
5189
|
import path18 from "path";
|
|
5087
|
-
import
|
|
5190
|
+
import fs18 from "fs";
|
|
5088
5191
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
5089
5192
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
5090
5193
|
function extractAgentNameFromPath(filePath) {
|
|
@@ -5117,7 +5220,7 @@ async function scanAgents(rootDir, patterns) {
|
|
|
5117
5220
|
continue;
|
|
5118
5221
|
}
|
|
5119
5222
|
const absPath = path18.resolve(rootDir, normalizedFile);
|
|
5120
|
-
const source = await
|
|
5223
|
+
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
5121
5224
|
const { hasRun } = detectAgentExports(source);
|
|
5122
5225
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
5123
5226
|
const prevFile = seen.get(name);
|
|
@@ -5197,6 +5300,7 @@ export {
|
|
|
5197
5300
|
createDevApp,
|
|
5198
5301
|
createProdApp,
|
|
5199
5302
|
createProgram,
|
|
5303
|
+
createPrograms,
|
|
5200
5304
|
extractTypeInfo,
|
|
5201
5305
|
getAgent,
|
|
5202
5306
|
getAgentEntry,
|
|
@@ -5204,6 +5308,7 @@ export {
|
|
|
5204
5308
|
getInputTypeForMethod,
|
|
5205
5309
|
getSkill,
|
|
5206
5310
|
getTool,
|
|
5311
|
+
getToolSchemaPath,
|
|
5207
5312
|
helmet,
|
|
5208
5313
|
hydrateSkillRegistry,
|
|
5209
5314
|
invalidateProgramCache,
|