@faapi/faapi 6.22.0 → 6.24.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 +245 -101
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +42 -51
- package/dist/index.js +166 -137
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-CXWkRx4W.d.ts → routeTypes-D1swYDVC.d.ts} +49 -19
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +80 -47
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -60,13 +60,13 @@ interface ToolPathMeta {
|
|
|
60
60
|
/**
|
|
61
61
|
* Agent 的 LLM 可见核心字段
|
|
62
62
|
*
|
|
63
|
-
* 描述"agent 是什么"——LLM
|
|
64
|
-
* (filePath
|
|
63
|
+
* 描述"agent 是什么"——LLM 真正需要消费的字段,**不含**声明文件定位信息
|
|
64
|
+
* (filePath)。文件型 agent 与 DB-driven skill 都实现此接口。
|
|
65
65
|
*
|
|
66
66
|
* - 文件型 agent:由 [AgentMetadata](./extractAgentMetadata.md) 继承扩展,
|
|
67
|
-
* 额外含 `filePath`
|
|
67
|
+
* 额外含 `filePath`(声明文件定位用)
|
|
68
68
|
* - DB-driven skill:业务方 plugin 从 DB 字段映射到本接口即可,无需填占位值
|
|
69
|
-
* (skill
|
|
69
|
+
* (skill 无源文件,自然不读 filePath)
|
|
70
70
|
*
|
|
71
71
|
* `@faapi/agent` 子包的 `Agent` 类、`agentRegistry` 查询入口、`asTool` 包装
|
|
72
72
|
* 都消费 `AgentCore`,实现"agent 与 skill 走同一运行时链路"。
|
|
@@ -78,6 +78,16 @@ interface AgentCore {
|
|
|
78
78
|
description?: string;
|
|
79
79
|
/** 系统提示词(config 块字面量提取);文件型 agent 必填(构建期校验),DB skill 由业务方自治 */
|
|
80
80
|
systemPrompt?: string;
|
|
81
|
+
/**
|
|
82
|
+
* 系统提示词文件路径(config 块字面量提取),相对产物 resources 目录
|
|
83
|
+
* (`<dist>/resources/`,即 `ctx.resourcesDir`),与 `systemPrompt` 互斥
|
|
84
|
+
* (都声明构建期抛错,二选一必填)
|
|
85
|
+
*
|
|
86
|
+
* 运行时每次 run 读文件内容作为 systemPrompt——dev 改 prompt 文件经 watcher
|
|
87
|
+
* 增量复制后立即生效,无需 reload;`deps.resourcesDir` 未注入或文件读取失败
|
|
88
|
+
* 时抛 `AgentError`(不静默降级)
|
|
89
|
+
*/
|
|
90
|
+
systemPromptFile?: string;
|
|
81
91
|
/** agent 显式声明可用的 tool 引用列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */
|
|
82
92
|
tools?: string[];
|
|
83
93
|
/** 可调用的其他 agent 名列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */
|
|
@@ -99,12 +109,13 @@ interface AgentCore {
|
|
|
99
109
|
/**
|
|
100
110
|
* Agent 完整元数据(文件型 agent)
|
|
101
111
|
*
|
|
102
|
-
* 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM
|
|
103
|
-
* - `filePath` —
|
|
104
|
-
* - `hasRun` — 是否导出 `run` 函数(`Agent.executeSubAgent` 据此决定走自定义 run
|
|
105
|
-
* 还是默认 reactLoop)
|
|
112
|
+
* 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展声明文件定位:
|
|
113
|
+
* - `filePath` — 声明文件源码相对路径(启动日志 / 清单可观测性用)
|
|
106
114
|
*
|
|
107
|
-
*
|
|
115
|
+
* agent 统一为声明式执行(config + 默认 reactLoop)——自定义 `run` 导出已移除,
|
|
116
|
+
* 检测到 run 导出在提取阶段抛迁移错误(编排场景注册 tool)。
|
|
117
|
+
*
|
|
118
|
+
* DB-driven skill 不实现此接口(无源文件),只实现 `AgentCore`。
|
|
108
119
|
*
|
|
109
120
|
* 由 [extractAgentMetadata](./extractAgentMetadata.md) 产出,合并路径推导字段
|
|
110
121
|
* (来自 [scanAgents](../agents/scanAgents.md) 的 `AgentManifest`)与 AST 提取字段
|
|
@@ -112,15 +123,13 @@ interface AgentCore {
|
|
|
112
123
|
*
|
|
113
124
|
* 字段来源:
|
|
114
125
|
* - `name` — `@agent` JSDoc 覆盖值,或 `pathMeta.name`(目录推导)
|
|
115
|
-
* - `filePath`
|
|
126
|
+
* - `filePath` — 由 `pathMeta` 透传
|
|
116
127
|
* - `description` — JSDoc 注释块自由文本(对 LLM 可见)
|
|
117
|
-
* - `systemPrompt` / `tools` / `agents` / `model` / `maxTurns` / `inputDescription` — config 块字面量提取
|
|
128
|
+
* - `systemPrompt` / `systemPromptFile` / `tools` / `agents` / `model` / `maxTurns` / `inputDescription` — config 块字面量提取
|
|
118
129
|
*/
|
|
119
130
|
interface AgentMetadata extends AgentCore {
|
|
120
|
-
/** 源码相对路径(从 `pathMeta` 透传)
|
|
131
|
+
/** 源码相对路径(从 `pathMeta` 透传)——声明文件定位与清单可观测性用 */
|
|
121
132
|
filePath: string;
|
|
122
|
-
/** 是否导出 `run` 函数(从 `pathMeta` 透传),`Agent.executeSubAgent` 据此选择自定义 run / 默认 reactLoop */
|
|
123
|
-
hasRun: boolean;
|
|
124
133
|
}
|
|
125
134
|
/**
|
|
126
135
|
* 路径推导的 agent 元数据(由 [scanAgents](../agents/scanAgents.ts) 计算)
|
|
@@ -133,8 +142,6 @@ interface AgentPathMeta {
|
|
|
133
142
|
name: string;
|
|
134
143
|
/** 源码相对路径(如 `src/agents/researcher/handler.ts`) */
|
|
135
144
|
filePath: string;
|
|
136
|
-
/** 是否导出 `run` 函数(scanAgents 正则检测) */
|
|
137
|
-
hasRun: boolean;
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
/**
|
|
@@ -356,6 +363,8 @@ interface TaskWorkerOptions {
|
|
|
356
363
|
/** signal 由执行器构造(abort/terminate 时触发),宿主只传 config 与 job 信息 */
|
|
357
364
|
taskCtx: {
|
|
358
365
|
config: unknown;
|
|
366
|
+
/** 产物 resources 目录绝对路径(纯字符串可结构化克隆,worker 内挂到 taskCtx) */
|
|
367
|
+
resourcesDir?: string;
|
|
359
368
|
job: {
|
|
360
369
|
id: string;
|
|
361
370
|
name: string;
|
|
@@ -439,7 +448,7 @@ interface AgentRegistry {
|
|
|
439
448
|
hydrate(agents: AgentMetadata[]): void;
|
|
440
449
|
/** LLM 可见元数据(AgentCore) */
|
|
441
450
|
getAgent(name: string): AgentCore | undefined;
|
|
442
|
-
/** 完整元数据(含 filePath
|
|
451
|
+
/** 完整元数据(含 filePath,声明文件定位) */
|
|
443
452
|
getAgentEntry(name: string): AgentMetadata | undefined;
|
|
444
453
|
/** 所有已注册 agent 的 LLM 可见元数据(副本) */
|
|
445
454
|
listAgents(): AgentCore[];
|
|
@@ -602,7 +611,7 @@ interface TaskJob {
|
|
|
602
611
|
* `TaskRegistriesView` 生成快照,worker wrapper 内重建只读视图。
|
|
603
612
|
*/
|
|
604
613
|
interface TaskRegistriesSnapshot {
|
|
605
|
-
/** agent 完整元数据(含 filePath
|
|
614
|
+
/** agent 完整元数据(含 filePath,非仅 LLM 可见字段) */
|
|
606
615
|
agents: AgentMetadata[];
|
|
607
616
|
tools: ToolMetadata[];
|
|
608
617
|
skills: AgentCore[];
|
|
@@ -615,6 +624,14 @@ interface TaskContext {
|
|
|
615
624
|
signal: AbortSignal;
|
|
616
625
|
/** faapi.config.ts 全量配置(含自定义业务配置) */
|
|
617
626
|
config: unknown;
|
|
627
|
+
/**
|
|
628
|
+
* 产物 resources 目录绝对路径(`<rootDir>/<dist>/resources`)
|
|
629
|
+
*
|
|
630
|
+
* 任务读静态文件(prompt 模板等)用:`fs.readFile(path.join(taskCtx.resourcesDir, 'x.md'))`。
|
|
631
|
+
* 进程内与隔离执行两条路径均注入(隔离为纯字符串,可结构化克隆);
|
|
632
|
+
* 直接构造 TaskContext 的测试/自定义执行器可不传。
|
|
633
|
+
*/
|
|
634
|
+
resourcesDir?: string;
|
|
618
635
|
job: {
|
|
619
636
|
id: string;
|
|
620
637
|
name: string;
|
|
@@ -625,7 +642,7 @@ interface TaskContext {
|
|
|
625
642
|
*
|
|
626
643
|
* 进程内执行为活引用;隔离执行为派发时刻的快照视图(worker 内重建)——
|
|
627
644
|
* 执行中途的 reload/DB skill 变更不影响当次执行。
|
|
628
|
-
* 任务内组装 agent 用 `registries.agent.getAgentEntry(name)`(含 filePath
|
|
645
|
+
* 任务内组装 agent 用 `registries.agent.getAgentEntry(name)`(含 filePath)。
|
|
629
646
|
*/
|
|
630
647
|
registries: TaskRegistriesView;
|
|
631
648
|
/**
|
|
@@ -714,6 +731,11 @@ interface TaskQueueDeps {
|
|
|
714
731
|
rootDir: string;
|
|
715
732
|
/** faapi.config.ts 全量配置,透传给 run 的 TaskContext.config */
|
|
716
733
|
config?: unknown;
|
|
734
|
+
/**
|
|
735
|
+
* 产物 resources 目录绝对路径——注入两条执行路径的 TaskContext.resourcesDir;
|
|
736
|
+
* 缺省不注入(直接构造队列的测试/嵌入场景)
|
|
737
|
+
*/
|
|
738
|
+
resourcesDir?: string;
|
|
717
739
|
/**
|
|
718
740
|
* app 注册表只读视图(`createTaskRegistriesView`)——注入两条执行路径的
|
|
719
741
|
* TaskContext.registries;缺省为空视图(直接构造队列的测试/嵌入场景)
|
|
@@ -1025,6 +1047,14 @@ interface FaapiContext {
|
|
|
1025
1047
|
cookies: Record<string, string>;
|
|
1026
1048
|
/** 配置文件中的自定义业务配置(类型可通过 declare module '@faapi/faapi' 增强 FaapiContextConfig) */
|
|
1027
1049
|
config: FaapiContextConfig;
|
|
1050
|
+
/**
|
|
1051
|
+
* 运行时资源根目录绝对路径(<rootDir>/<dist>/resources)
|
|
1052
|
+
*
|
|
1053
|
+
* `src/resources/` 经 dev/build 复制进产物后的位置,handler 读静态文件用:
|
|
1054
|
+
* `await fs.readFile(path.join(ctx.resourcesDir, 'prompts/foo.md'))`。
|
|
1055
|
+
* HTTP/WS 请求链路恒有值;testing 直调(createTestContext)未传时为 undefined。
|
|
1056
|
+
*/
|
|
1057
|
+
resourcesDir?: string;
|
|
1028
1058
|
/**
|
|
1029
1059
|
* 设置响应状态码
|
|
1030
1060
|
*/
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-
|
|
1
|
+
import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-D1swYDVC.js';
|
|
2
2
|
import { Server } from 'node:http';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
|
|
@@ -25,6 +25,8 @@ declare function createTestContext(options: CreateTestContextOptions): FaapiCont
|
|
|
25
25
|
interface CreateTestContextOptions {
|
|
26
26
|
/** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */
|
|
27
27
|
registries?: FaapiContext['registries'];
|
|
28
|
+
/** 运行时资源根目录(测试 handler 读 ctx.resourcesDir 时传入,可选) */
|
|
29
|
+
resourcesDir?: string;
|
|
28
30
|
/** 请求方法,默认 'GET' */
|
|
29
31
|
method?: string;
|
|
30
32
|
/** 请求路径,必填,如 '/api/user'(无需写 host) */
|
package/dist/testing.js
CHANGED
|
@@ -312,14 +312,14 @@ var ValidationError = class extends FaapiError {
|
|
|
312
312
|
issues;
|
|
313
313
|
};
|
|
314
314
|
var RouteNotFoundError = class extends FaapiError {
|
|
315
|
-
constructor(
|
|
316
|
-
super("ROUTE_NOT_FOUND", `Route not found: ${
|
|
315
|
+
constructor(path17) {
|
|
316
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path17}`, 404);
|
|
317
317
|
this.name = "RouteNotFoundError";
|
|
318
318
|
}
|
|
319
319
|
};
|
|
320
320
|
var MethodNotAllowedError = class extends FaapiError {
|
|
321
|
-
constructor(method,
|
|
322
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${
|
|
321
|
+
constructor(method, path17, allowedMethods) {
|
|
322
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path17}`, 405);
|
|
323
323
|
this.allowedMethods = allowedMethods;
|
|
324
324
|
this.name = "MethodNotAllowedError";
|
|
325
325
|
}
|
|
@@ -584,10 +584,18 @@ function formatSetCookie(name, value, options) {
|
|
|
584
584
|
if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
585
585
|
return cookie;
|
|
586
586
|
}
|
|
587
|
-
function createContext(request, params, config = {}, ip = "", registries) {
|
|
588
|
-
return createContextFromUrl(
|
|
587
|
+
function createContext(request, params, config = {}, ip = "", registries, resourcesDir) {
|
|
588
|
+
return createContextFromUrl(
|
|
589
|
+
request,
|
|
590
|
+
new URL(request.url),
|
|
591
|
+
params,
|
|
592
|
+
config,
|
|
593
|
+
ip,
|
|
594
|
+
registries,
|
|
595
|
+
resourcesDir
|
|
596
|
+
);
|
|
589
597
|
}
|
|
590
|
-
function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
|
|
598
|
+
function createContextFromUrl(request, url, params, config = {}, ip = "", registries, resourcesDir) {
|
|
591
599
|
const meta = { headers: {}, setCookies: [] };
|
|
592
600
|
const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
|
|
593
601
|
const cookiesObj = {};
|
|
@@ -615,6 +623,9 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
615
623
|
ua: request.headers.get("user-agent") ?? "",
|
|
616
624
|
cookies: cookiesObj,
|
|
617
625
|
config,
|
|
626
|
+
// 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;
|
|
627
|
+
// testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)
|
|
628
|
+
resourcesDir,
|
|
618
629
|
meta,
|
|
619
630
|
setStatus(status) {
|
|
620
631
|
meta.status = status;
|
|
@@ -720,15 +731,16 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
720
731
|
function createTestContext(options) {
|
|
721
732
|
const {
|
|
722
733
|
method = "GET",
|
|
723
|
-
path:
|
|
734
|
+
path: path17,
|
|
724
735
|
query,
|
|
725
736
|
headers,
|
|
726
737
|
params = {},
|
|
727
738
|
config = {},
|
|
728
739
|
ip = "",
|
|
729
|
-
registries
|
|
740
|
+
registries,
|
|
741
|
+
resourcesDir
|
|
730
742
|
} = options;
|
|
731
|
-
const url = new URL(`http://localhost${
|
|
743
|
+
const url = new URL(`http://localhost${path17}`);
|
|
732
744
|
if (query) {
|
|
733
745
|
for (const [key, value] of Object.entries(query)) {
|
|
734
746
|
if (Array.isArray(value)) {
|
|
@@ -744,7 +756,7 @@ function createTestContext(options) {
|
|
|
744
756
|
method,
|
|
745
757
|
headers
|
|
746
758
|
});
|
|
747
|
-
return createContext(request, params, config, ip, registries);
|
|
759
|
+
return createContext(request, params, config, ip, registries, resourcesDir);
|
|
748
760
|
}
|
|
749
761
|
|
|
750
762
|
// src/response/pendingMeta.ts
|
|
@@ -1327,9 +1339,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
|
|
|
1327
1339
|
}
|
|
1328
1340
|
|
|
1329
1341
|
// src/testServer.ts
|
|
1330
|
-
import
|
|
1342
|
+
import path16 from "path";
|
|
1331
1343
|
import os from "os";
|
|
1332
|
-
import
|
|
1344
|
+
import fs13 from "fs/promises";
|
|
1333
1345
|
|
|
1334
1346
|
// src/router/scanRoutes.ts
|
|
1335
1347
|
import fg from "fast-glob";
|
|
@@ -1341,9 +1353,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
|
1341
1353
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
1342
1354
|
|
|
1343
1355
|
// src/utils/normalizePath.ts
|
|
1344
|
-
function normalizePath(
|
|
1345
|
-
if (!
|
|
1346
|
-
let result =
|
|
1356
|
+
function normalizePath(path17) {
|
|
1357
|
+
if (!path17) return "";
|
|
1358
|
+
let result = path17.replace(/\\/g, "/");
|
|
1347
1359
|
result = result.replace(/\/+/g, "/");
|
|
1348
1360
|
result = result.replace(/\/+$/, "");
|
|
1349
1361
|
if (result && !result.startsWith("/")) {
|
|
@@ -3511,13 +3523,14 @@ function buildAliasPlugins(rootDir) {
|
|
|
3511
3523
|
}
|
|
3512
3524
|
|
|
3513
3525
|
// src/cli/compileSourceFiles.ts
|
|
3526
|
+
var SOURCE_IGNORE = ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts", `${APP_DIR}/resources/**`];
|
|
3514
3527
|
async function compileSourceFiles(options) {
|
|
3515
3528
|
const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
|
|
3516
3529
|
const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
|
|
3517
3530
|
cwd: rootDir,
|
|
3518
3531
|
onlyFiles: true,
|
|
3519
3532
|
absolute: true,
|
|
3520
|
-
ignore:
|
|
3533
|
+
ignore: SOURCE_IGNORE
|
|
3521
3534
|
});
|
|
3522
3535
|
if (entryPoints.length === 0) {
|
|
3523
3536
|
return { compiledFiles: [] };
|
|
@@ -3821,9 +3834,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
3821
3834
|
function mapZodIssues(error) {
|
|
3822
3835
|
return error.issues.map((issue) => {
|
|
3823
3836
|
const code = mapZodCode(issue);
|
|
3824
|
-
const
|
|
3837
|
+
const path17 = issue.path.map(String).join(".") || "";
|
|
3825
3838
|
return {
|
|
3826
|
-
path:
|
|
3839
|
+
path: path17,
|
|
3827
3840
|
code,
|
|
3828
3841
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
3829
3842
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -3873,7 +3886,7 @@ import {
|
|
|
3873
3886
|
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
3874
3887
|
import { readFileSync } from "fs";
|
|
3875
3888
|
import { Readable as Readable2 } from "stream";
|
|
3876
|
-
import
|
|
3889
|
+
import path15 from "path";
|
|
3877
3890
|
|
|
3878
3891
|
// src/router/matchRoute.ts
|
|
3879
3892
|
var httpIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -3915,25 +3928,25 @@ function getWsIndex(routes) {
|
|
|
3915
3928
|
wsIndexCache.set(routes, index);
|
|
3916
3929
|
return index;
|
|
3917
3930
|
}
|
|
3918
|
-
function matchRoute(routes, method,
|
|
3931
|
+
function matchRoute(routes, method, path17) {
|
|
3919
3932
|
const index = getHttpIndex(routes);
|
|
3920
3933
|
const upper = method.toUpperCase();
|
|
3921
|
-
const hit = matchByMethod(index, upper,
|
|
3934
|
+
const hit = matchByMethod(index, upper, path17);
|
|
3922
3935
|
if (hit) return hit;
|
|
3923
3936
|
if (upper === "HEAD") {
|
|
3924
|
-
return matchByMethod(index, "GET",
|
|
3937
|
+
return matchByMethod(index, "GET", path17);
|
|
3925
3938
|
}
|
|
3926
3939
|
return null;
|
|
3927
3940
|
}
|
|
3928
|
-
function matchByMethod(index, method,
|
|
3929
|
-
const staticHit = index.static.get(`${method}|${
|
|
3941
|
+
function matchByMethod(index, method, path17) {
|
|
3942
|
+
const staticHit = index.static.get(`${method}|${path17}`);
|
|
3930
3943
|
if (staticHit) {
|
|
3931
3944
|
return { route: staticHit, params: {} };
|
|
3932
3945
|
}
|
|
3933
3946
|
if (index.dynamics.length === 0) {
|
|
3934
3947
|
return null;
|
|
3935
3948
|
}
|
|
3936
|
-
const pathSegments =
|
|
3949
|
+
const pathSegments = path17.split("/").filter(Boolean);
|
|
3937
3950
|
for (const entry of index.dynamics) {
|
|
3938
3951
|
const route = entry.route;
|
|
3939
3952
|
if (route.method !== method) {
|
|
@@ -3946,31 +3959,31 @@ function matchByMethod(index, method, path16) {
|
|
|
3946
3959
|
}
|
|
3947
3960
|
return null;
|
|
3948
3961
|
}
|
|
3949
|
-
function matchWsRoute(wsRoutes,
|
|
3962
|
+
function matchWsRoute(wsRoutes, path17) {
|
|
3950
3963
|
const index = getWsIndex(wsRoutes);
|
|
3951
|
-
const staticHit = index.static.get(
|
|
3964
|
+
const staticHit = index.static.get(path17);
|
|
3952
3965
|
if (staticHit) {
|
|
3953
3966
|
return { route: staticHit, params: {} };
|
|
3954
3967
|
}
|
|
3955
3968
|
for (const route of index.dynamics) {
|
|
3956
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3969
|
+
const params = matchDynamicPath(route.urlPath, path17, route.paramNames, route.isCatchAll);
|
|
3957
3970
|
if (params !== null) {
|
|
3958
3971
|
return { route, params };
|
|
3959
3972
|
}
|
|
3960
3973
|
}
|
|
3961
3974
|
return null;
|
|
3962
3975
|
}
|
|
3963
|
-
function findAllowedMethods(routes,
|
|
3976
|
+
function findAllowedMethods(routes, path17) {
|
|
3964
3977
|
const index = getHttpIndex(routes);
|
|
3965
3978
|
const methods = /* @__PURE__ */ new Set();
|
|
3966
|
-
const staticMethods = index.methodsByStaticPath.get(
|
|
3979
|
+
const staticMethods = index.methodsByStaticPath.get(path17);
|
|
3967
3980
|
if (staticMethods) {
|
|
3968
3981
|
for (const method of staticMethods) {
|
|
3969
3982
|
methods.add(method);
|
|
3970
3983
|
}
|
|
3971
3984
|
}
|
|
3972
3985
|
if (index.dynamics.length > 0) {
|
|
3973
|
-
const pathSegments =
|
|
3986
|
+
const pathSegments = path17.split("/").filter(Boolean);
|
|
3974
3987
|
for (const entry of index.dynamics) {
|
|
3975
3988
|
const params = matchSegments(
|
|
3976
3989
|
entry.segments,
|
|
@@ -3988,10 +4001,10 @@ function findAllowedMethods(routes, path16) {
|
|
|
3988
4001
|
}
|
|
3989
4002
|
return Array.from(methods);
|
|
3990
4003
|
}
|
|
3991
|
-
function matchDynamicPath(pattern,
|
|
4004
|
+
function matchDynamicPath(pattern, path17, paramNames, isCatchAll) {
|
|
3992
4005
|
return matchSegments(
|
|
3993
4006
|
pattern.split("/").filter(Boolean),
|
|
3994
|
-
|
|
4007
|
+
path17.split("/").filter(Boolean),
|
|
3995
4008
|
paramNames,
|
|
3996
4009
|
isCatchAll
|
|
3997
4010
|
);
|
|
@@ -4613,9 +4626,17 @@ function logger(options = {}) {
|
|
|
4613
4626
|
}
|
|
4614
4627
|
|
|
4615
4628
|
// src/server/handleWsUpgrade.ts
|
|
4616
|
-
import
|
|
4629
|
+
import fs12 from "fs";
|
|
4617
4630
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4631
|
+
import path14 from "path";
|
|
4632
|
+
|
|
4633
|
+
// src/cli/copyResources.ts
|
|
4618
4634
|
import path13 from "path";
|
|
4635
|
+
import fs11 from "fs";
|
|
4636
|
+
var RESOURCES_OUTPUT_DIR = "resources";
|
|
4637
|
+
function resolveResourcesDir(rootDir, dist) {
|
|
4638
|
+
return path13.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
|
|
4639
|
+
}
|
|
4619
4640
|
|
|
4620
4641
|
// src/server/serverUtils.ts
|
|
4621
4642
|
function nodeHttpToWebHeaders(req) {
|
|
@@ -4673,7 +4694,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
4673
4694
|
const dist = getDevDist();
|
|
4674
4695
|
if (dist) {
|
|
4675
4696
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
4676
|
-
if (sourcePath &&
|
|
4697
|
+
if (sourcePath && fs12.existsSync(sourcePath)) {
|
|
4677
4698
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
4678
4699
|
}
|
|
4679
4700
|
}
|
|
@@ -4771,6 +4792,7 @@ function attachWebSocket(options) {
|
|
|
4771
4792
|
trustedProxy = false,
|
|
4772
4793
|
registries
|
|
4773
4794
|
} = options;
|
|
4795
|
+
const resourcesDir = resolveResourcesDir(rootDir, dist);
|
|
4774
4796
|
const wss = new WebSocketServer({ noServer: true });
|
|
4775
4797
|
server.on("upgrade", async (req, socket, head) => {
|
|
4776
4798
|
try {
|
|
@@ -4802,12 +4824,19 @@ function attachWebSocket(options) {
|
|
|
4802
4824
|
const host = req.headers.host ?? "localhost";
|
|
4803
4825
|
const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
|
|
4804
4826
|
const request = new Request(fullUrl.toString(), { method: "GET", headers });
|
|
4805
|
-
const ctx = createContext(
|
|
4827
|
+
const ctx = createContext(
|
|
4828
|
+
request,
|
|
4829
|
+
params,
|
|
4830
|
+
config,
|
|
4831
|
+
getClientIp(req, trustedProxy),
|
|
4832
|
+
registries,
|
|
4833
|
+
resourcesDir
|
|
4834
|
+
);
|
|
4806
4835
|
const meta = ctx.meta;
|
|
4807
4836
|
let upgraded = false;
|
|
4808
4837
|
const finalHandler = async () => {
|
|
4809
4838
|
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
4810
|
-
if (
|
|
4839
|
+
if (fs12.existsSync(schemaPath)) {
|
|
4811
4840
|
if (isDevOnDemandEnabled()) {
|
|
4812
4841
|
const devDist = getDevDist();
|
|
4813
4842
|
if (devDist) {
|
|
@@ -4846,7 +4875,7 @@ function attachWebSocket(options) {
|
|
|
4846
4875
|
}
|
|
4847
4876
|
let handlers;
|
|
4848
4877
|
try {
|
|
4849
|
-
const absoluteFilePath =
|
|
4878
|
+
const absoluteFilePath = path14.resolve(rootDir, route.filePath);
|
|
4850
4879
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
4851
4880
|
} catch (err) {
|
|
4852
4881
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -5005,6 +5034,7 @@ function createServer(options) {
|
|
|
5005
5034
|
trustedProxy = false
|
|
5006
5035
|
} = options;
|
|
5007
5036
|
const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
|
|
5037
|
+
const resourcesDir = resolveResourcesDir(rootDir, dist);
|
|
5008
5038
|
const configMiddlewares = [];
|
|
5009
5039
|
if (compressionOption) {
|
|
5010
5040
|
const compOpts = typeof compressionOption === "object" ? compressionOption : {};
|
|
@@ -5051,7 +5081,8 @@ function createServer(options) {
|
|
|
5051
5081
|
bodyLimit,
|
|
5052
5082
|
trustedProxy,
|
|
5053
5083
|
registries,
|
|
5054
|
-
routesRef.wsCurrent
|
|
5084
|
+
routesRef.wsCurrent,
|
|
5085
|
+
resourcesDir
|
|
5055
5086
|
).catch((err) => {
|
|
5056
5087
|
console.error("[faapi] Request pipeline failed after error response attempt:", err);
|
|
5057
5088
|
try {
|
|
@@ -5077,7 +5108,7 @@ function createServer(options) {
|
|
|
5077
5108
|
});
|
|
5078
5109
|
return { server, routesRef };
|
|
5079
5110
|
}
|
|
5080
|
-
function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
|
|
5111
|
+
function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched, resourcesDir) {
|
|
5081
5112
|
const method = request.method.toUpperCase();
|
|
5082
5113
|
const urlPath = url.pathname;
|
|
5083
5114
|
const ctx = createContextFromUrl(
|
|
@@ -5086,7 +5117,8 @@ function prepareRequest(req, request, url, config, trustedProxy, registries, pre
|
|
|
5086
5117
|
preMatched?.params ?? {},
|
|
5087
5118
|
config,
|
|
5088
5119
|
getClientIp(req, trustedProxy),
|
|
5089
|
-
registries
|
|
5120
|
+
registries,
|
|
5121
|
+
resourcesDir
|
|
5090
5122
|
);
|
|
5091
5123
|
const meta = ctx.meta;
|
|
5092
5124
|
return { request, url, ctx, meta, method, urlPath };
|
|
@@ -5105,7 +5137,7 @@ function getRoutePaths(route, rootDir, dist) {
|
|
|
5105
5137
|
let cached = routePathCache.get(route);
|
|
5106
5138
|
if (!cached) {
|
|
5107
5139
|
cached = {
|
|
5108
|
-
absFilePath:
|
|
5140
|
+
absFilePath: path15.resolve(rootDir, route.filePath),
|
|
5109
5141
|
schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
|
|
5110
5142
|
};
|
|
5111
5143
|
routePathCache.set(route, cached);
|
|
@@ -5228,7 +5260,7 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
|
5228
5260
|
}
|
|
5229
5261
|
}
|
|
5230
5262
|
}
|
|
5231
|
-
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
|
|
5263
|
+
async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes, resourcesDir) {
|
|
5232
5264
|
let meta = { headers: {}, setCookies: [] };
|
|
5233
5265
|
let ctx;
|
|
5234
5266
|
let sseEarlySent = false;
|
|
@@ -5248,7 +5280,8 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
|
|
|
5248
5280
|
config,
|
|
5249
5281
|
trustedProxy,
|
|
5250
5282
|
registries,
|
|
5251
|
-
preMatched
|
|
5283
|
+
preMatched,
|
|
5284
|
+
resourcesDir
|
|
5252
5285
|
);
|
|
5253
5286
|
ctx = prepared.ctx;
|
|
5254
5287
|
meta = prepared.meta;
|
|
@@ -5313,7 +5346,7 @@ async function createTestServer(options) {
|
|
|
5313
5346
|
} = options;
|
|
5314
5347
|
const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
|
|
5315
5348
|
const sorted = sortRoutes(routes);
|
|
5316
|
-
const schemaDist = dist ?
|
|
5349
|
+
const schemaDist = dist ? path16.isAbsolute(dist) ? dist : path16.resolve(rootDir, dist) : await fs13.mkdtemp(path16.join(os.tmpdir(), "faapi-test-schema-"));
|
|
5317
5350
|
await generateSchemaFiles(sorted, rootDir, schemaDist);
|
|
5318
5351
|
const { server } = createServer({
|
|
5319
5352
|
routes: sorted,
|
|
@@ -5345,7 +5378,7 @@ async function createTestServer(options) {
|
|
|
5345
5378
|
await new Promise((resolve) => {
|
|
5346
5379
|
server.close(() => resolve());
|
|
5347
5380
|
});
|
|
5348
|
-
await
|
|
5381
|
+
await fs13.rm(schemaDist, { recursive: true, force: true }).catch(() => {
|
|
5349
5382
|
});
|
|
5350
5383
|
invalidateSchemaCache();
|
|
5351
5384
|
}
|