@faapi/faapi 6.2.0 → 6.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.
@@ -1,3 +1,199 @@
1
+ /**
2
+ * app 实例级任务注册表(与 tool/agent/skill registry 同构,方案 A 实例化)
3
+ *
4
+ * 任务清单来自编译期产物(faapi-tasks.js),reload 时整体替换(hydrate 语义)。
5
+ * 每个 app 持有独立实例,多 app 同进程互不串台;详见 taskRegistry.md。
6
+ */
7
+ interface TaskRegistry {
8
+ /** 全量替换(任务清单来自编译期产物,reload 时整体重新生成) */
9
+ hydrate(tasks: TaskMetadata[]): void;
10
+ /** 按任务名查找 */
11
+ get(name: string): TaskMetadata | undefined;
12
+ /** 所有已注册任务(副本) */
13
+ list(): TaskMetadata[];
14
+ clear(): void;
15
+ }
16
+ declare function createTaskRegistry(): TaskRegistry;
17
+
18
+ /**
19
+ * 任务队列驱动抽象(语义层与存储/调度实现分离的边界)
20
+ *
21
+ * 语义层(taskQueue)负责:任务存在性检查、payload zod 校验、任务模块加载、
22
+ * run 执行包装、任务记录(list);驱动层负责:入队存储、worker 消费、
23
+ * 失败重试、停机 drain。换驱动 = 换存储,业务方写法不变。
24
+ *
25
+ * 内置 memory 驱动(memoryDriver.ts,进程内数组,零依赖);
26
+ * 外部驱动由独立子包提供:`@faapi/task-pgboss`(Postgres)、`@faapi/task-bullmq`(Redis),
27
+ * 主包不依赖它们——按 config.task.driver 动态加载(loadTaskDriver.ts)。
28
+ */
29
+
30
+ /** 驱动层交付给语义层执行的单个任务 */
31
+ interface TaskDriverJob {
32
+ /** 队列系统侧任务 id(memory 为 uuid;pg-boss/bullmq 为其自身 id) */
33
+ id: string;
34
+ name: string;
35
+ payload: unknown;
36
+ /** 第几次执行(从 1 起,含重试) */
37
+ attempt: number;
38
+ /** 停机/取消信号;驱动不支持取消时为永不 abort 的信号 */
39
+ signal: AbortSignal;
40
+ }
41
+ /** 语义层交给驱动层的执行函数(抛错 = 失败,由驱动按入队时的 retries 重试) */
42
+ type TaskDriverProcess = (job: TaskDriverJob) => Promise<unknown>;
43
+ /**
44
+ * 任务队列驱动接口
45
+ */
46
+ interface TaskDriver {
47
+ /**
48
+ * 入队一个任务,返回驱动侧任务 id
49
+ *
50
+ * @param opts.retries 失败重试次数(语义层从任务 meta 取,驱动负责执行重试策略)
51
+ * @throws 驱动已停止 / 连接失败等
52
+ */
53
+ enqueue(name: string, payload: unknown, opts?: {
54
+ delayMs?: number;
55
+ retries?: number;
56
+ }): Promise<string>;
57
+ /**
58
+ * 注册某任务的消费 worker(幂等覆盖)。process 抛错 = 本次失败,驱动决定重试。
59
+ * 调用时机:queue.start() 为注册表中每个任务注册一次。
60
+ */
61
+ startWorker(name: string, opts: {
62
+ concurrency: number;
63
+ process: TaskDriverProcess;
64
+ }): Promise<void> | void;
65
+ /** 停止消费并等待 in-flight 任务(timeoutMs 超时后驱动自行处置),幂等 */
66
+ stop(timeoutMs?: number): Promise<void>;
67
+ /** 仅停止 worker 消费(不断开驱动连接),供 dev reloadTasks 重注册用;可选 */
68
+ stopWorkers?(): Promise<void>;
69
+ }
70
+
71
+ /**
72
+ * 任务元信息(业务方在 task.ts 中 `export const task = {...}` 声明)
73
+ *
74
+ * 所有字段可选;未声明时由运行时使用默认值(concurrency=1、retries=0、无 cron)。
75
+ */
76
+ interface FaapiTaskMeta {
77
+ /** 同名任务的最大并行执行数(默认 1) */
78
+ concurrency?: number;
79
+ /** 失败重试次数(默认 0——失败即 failed,不重试) */
80
+ retries?: number;
81
+ /** cron 表达式(croner 语法,支持秒级)——到点自动入队空 payload */
82
+ cron?: string;
83
+ }
84
+ /**
85
+ * 构建期扫描清单记录(scanTasks 产出,源码路径形式)
86
+ */
87
+ interface TaskManifest {
88
+ /** 任务名:tasks/ 后目录路径段用 . 连接(如 `send-email`、`a.b`) */
89
+ name: string;
90
+ /** 源码相对路径(`src/tasks/<dir>/task.ts`) */
91
+ filePath: string;
92
+ cron?: string;
93
+ concurrency?: number;
94
+ retries?: number;
95
+ }
96
+ /**
97
+ * 运行时任务元数据(faapi-tasks.js 水合到 TaskRegistry 后的形态,产物路径形式)
98
+ */
99
+ interface TaskMetadata {
100
+ name: string;
101
+ /** 产物路径(`<dist>/tasks/<dir>/task.js`),运行时 import 任务模块 */
102
+ filePath: string;
103
+ cron?: string;
104
+ concurrency?: number;
105
+ retries?: number;
106
+ }
107
+ /** 任务记录状态 */
108
+ type TaskJobStatus = 'pending' | 'running' | 'retry' | 'done' | 'failed';
109
+ /**
110
+ * 任务执行记录(内存快照)
111
+ */
112
+ interface TaskJob {
113
+ id: string;
114
+ name: string;
115
+ payload: unknown;
116
+ status: TaskJobStatus;
117
+ /** 已执行次数(含当前正在执行的一次) */
118
+ attempts: number;
119
+ /** run 的返回值(done 时) */
120
+ result?: unknown;
121
+ /** 错误消息(failed 时) */
122
+ error?: string;
123
+ createdAt: number;
124
+ /** 计划执行时间戳(重试/延迟任务与 createdAt 不同) */
125
+ runAt?: number;
126
+ }
127
+ /**
128
+ * 传给任务 run 函数的第二参数
129
+ */
130
+ interface TaskContext {
131
+ /** 优雅停机时对在跑任务 abort 的信号 */
132
+ signal: AbortSignal;
133
+ /** faapi.config.ts 全量配置(含自定义业务配置) */
134
+ config: unknown;
135
+ job: {
136
+ id: string;
137
+ name: string;
138
+ attempt: number;
139
+ };
140
+ }
141
+ /**
142
+ * 任务模块形态(task.ts 编译产物中与执行相关的导出)
143
+ */
144
+ interface TaskModule {
145
+ run?: (payload: unknown, taskCtx: TaskContext) => unknown;
146
+ }
147
+ /**
148
+ * 任务触发客户端(`tasks` 注入参数 / `ctx.tasks` / `app.tasks` 共用)
149
+ */
150
+ interface TaskClient {
151
+ /**
152
+ * 入队一个任务
153
+ *
154
+ * @returns 任务 id
155
+ * @throws 任务不存在 / 队列已停止 / payload 校验失败(ValidationError)
156
+ */
157
+ enqueue(name: string, payload?: unknown, opts?: {
158
+ delayMs?: number;
159
+ }): Promise<{
160
+ id: string;
161
+ }>;
162
+ /** 任务记录快照(可按任务名过滤) */
163
+ list(name?: string): TaskJob[];
164
+ }
165
+ /**
166
+ * 任务队列(TaskClient + 生命周期控制,createAppBase 内部使用)
167
+ */
168
+ interface TaskQueue extends TaskClient {
169
+ /** 开始派发(幂等) */
170
+ start(): void;
171
+ /** 停止接受新任务并等待在跑任务完成(超时 abort),幂等 */
172
+ stop(timeoutMs?: number): Promise<void>;
173
+ /** 重注册 worker(dev reloadTasks 用——驱动连接保持,仅按最新注册表重建消费) */
174
+ reload(): Promise<void>;
175
+ /** 清空任务模块缓存(dev reloadTasks 用) */
176
+ invalidateModules(): void;
177
+ /** 清空 payload schema 缓存(dev reloadTasks 用) */
178
+ invalidateSchemas(): void;
179
+ }
180
+ /** 队列依赖(测试可注入自定义加载器/驱动) */
181
+ interface TaskQueueDeps {
182
+ registry: TaskRegistry;
183
+ rootDir: string;
184
+ /** faapi.config.ts 全量配置,透传给 run 的 TaskContext.config */
185
+ config?: unknown;
186
+ /**
187
+ * 队列驱动(默认 memoryDriver——进程内数组,重启丢任务)
188
+ * 外部驱动:`@faapi/task-pgboss` / `@faapi/task-bullmq` 或自定义 TaskDriver 实例
189
+ */
190
+ driver?: TaskDriver;
191
+ /** 任务模块加载器(默认:import 产物路径) */
192
+ loadTaskModule?: (filePath: string) => Promise<TaskModule>;
193
+ /** payload schema 加载器(默认:import 任务目录 zod.js,取首个 `*Schema` 导出) */
194
+ loadPayloadSchema?: (filePath: string) => Promise<unknown>;
195
+ }
196
+
1
197
  /**
2
198
  * SSE(Server-Sent Events)支持
3
199
  *
@@ -273,12 +469,23 @@ interface AgentHandleStore {
273
469
  get(ctx: FaapiContext): unknown;
274
470
  clear(): void;
275
471
  }
472
+ /** task 客户端工厂函数(由 createAppBase 注册,返回 TaskClient 门面) */
473
+ type TaskHandleFactory = (ctx: FaapiContext) => unknown;
474
+ interface TaskHandleStore {
475
+ /** 注册工厂(null 清理);二次注册覆盖 */
476
+ register(factory: TaskHandleFactory | null): void;
477
+ /** 工厂已注册时返回 TaskClient,未注册返回 undefined */
478
+ get(ctx: FaapiContext): unknown;
479
+ clear(): void;
480
+ }
276
481
  /** 一个 app 实例持有的全套注册表 */
277
482
  interface AppRegistries {
278
483
  tool: ToolRegistry;
279
484
  agent: AgentRegistry;
280
485
  skill: SkillRegistry;
486
+ task: TaskRegistry;
281
487
  agentHandle: AgentHandleStore;
488
+ taskHandle: TaskHandleStore;
282
489
  }
283
490
  /** 创建一套 app 级注册表(`createAppBase` 每次调用创建独立实例) */
284
491
  declare function createAppRegistries(): AppRegistries;
@@ -342,6 +549,12 @@ interface FaapiContext {
342
549
  * (injectParams 等消费方回退到默认全局实例)
343
550
  */
344
551
  registries?: AppRegistries;
552
+ /**
553
+ * 任务队列客户端(TaskClient:enqueue/list)。
554
+ * 经 createContext 进入请求链路时由框架从 `registries.taskHandle` 工厂注入;
555
+ * 无 app 编排(编程式直调 ctx)时为 undefined
556
+ */
557
+ tasks?: TaskClient;
345
558
  request: Request;
346
559
  params: Record<string, string>;
347
560
  query: URLSearchParams;
@@ -706,4 +919,4 @@ interface RouteInfo {
706
919
  output: RouteOutputSchema | null;
707
920
  }
708
921
 
709
- export { type AppRegistries as A, type CorsOptions as C, type FaapiContext as F, type HelmetOptions as H, type InjectorMap as I, type LoggerOptions as L, type RouteManifest as R, type SkillRegistry as S, type ToolMetadata as T, type WsRouteManifest as W, type FaapiMiddleware as a, type AgentCore as b, type AgentMetadata as c, type AgentHandleFactory as d, type AgentHandleStore as e, type AgentPathMeta as f, type AgentRegistry as g, type AgentToolDescriptor as h, type FaapiContextConfig as i, type FailOptions as j, type Injector as k, type RouteInfo as l, type RouteInputSchema as m, type RouteOutputSchema as n, type RouteParamSchema as o, type SseEvent as p, type SseWriter as q, type ToolCore as r, type ToolPathMeta as s, type ToolRegistry as t, cors as u, createAppRegistries as v, helmet as w, logger as x };
922
+ export { type AppRegistries as A, type TaskDriverProcess as B, type CorsOptions as C, type TaskJob as D, type TaskJobStatus as E, type FaapiContext as F, type TaskMetadata as G, type HelmetOptions as H, type InjectorMap as I, type TaskModule as J, type ToolCore as K, type LoggerOptions as L, type ToolPathMeta as M, type ToolRegistry as N, cors as O, createAppRegistries as P, createTaskRegistry as Q, type RouteManifest as R, type SkillRegistry as S, type TaskClient as T, helmet as U, logger as V, type WsRouteManifest as W, type FaapiMiddleware as a, type TaskQueueDeps as b, type TaskQueue as c, type TaskDriver as d, type TaskRegistry as e, type TaskManifest as f, type ToolMetadata as g, type AgentCore as h, type AgentMetadata as i, type AgentHandleFactory as j, type AgentHandleStore as k, type AgentPathMeta as l, type AgentRegistry as m, type AgentToolDescriptor as n, type FaapiContextConfig as o, type FaapiTaskMeta as p, type FailOptions as q, type Injector as r, type RouteInfo as s, type RouteInputSchema as t, type RouteOutputSchema as u, type RouteParamSchema as v, type SseEvent as w, type SseWriter as x, type TaskContext as y, type TaskDriverJob as z };
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as FaapiContext, a as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions, L as LoggerOptions } from './routeTypes-Bm--uTbm.js';
1
+ import { F as FaapiContext, a as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions, L as LoggerOptions } from './routeTypes-CCveqSnY.js';
2
2
  import { Server } from 'node:http';
3
3
  import { WebSocket } from 'ws';
4
4
 
package/dist/testing.js CHANGED
@@ -371,6 +371,7 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
371
371
  };
372
372
  if (registries) {
373
373
  ctx.registries = registries;
374
+ ctx.tasks = registries.taskHandle.get(ctx);
374
375
  }
375
376
  const extend = config?.extendContext;
376
377
  if (typeof extend === "function") {
@@ -550,8 +551,10 @@ var PARAM_TYPE_MAP = {
550
551
  fields: "fields",
551
552
  agent: "agent",
552
553
  // Phase 2.3
553
- agents: "agents"
554
+ agents: "agents",
554
555
  // Phase 2.3
556
+ tasks: "tasks"
557
+ // 任务子系统:TaskClient(入队/查询)
555
558
  };
556
559
  var injectionCache = /* @__PURE__ */ new WeakMap();
557
560
  function resolveInjection(fn) {
@@ -645,6 +648,29 @@ function queryToObject(params) {
645
648
  return result;
646
649
  }
647
650
 
651
+ // src/task/taskRegistry.ts
652
+ function createTaskRegistry() {
653
+ let registry = /* @__PURE__ */ new Map();
654
+ return {
655
+ hydrate(tasks) {
656
+ const next = /* @__PURE__ */ new Map();
657
+ for (const task of tasks) {
658
+ next.set(task.name, task);
659
+ }
660
+ registry = next;
661
+ },
662
+ get(name) {
663
+ return registry.get(name);
664
+ },
665
+ list() {
666
+ return Array.from(registry.values());
667
+ },
668
+ clear() {
669
+ registry = /* @__PURE__ */ new Map();
670
+ }
671
+ };
672
+ }
673
+
648
674
  // src/injection/registries.ts
649
675
  function createToolRegistry() {
650
676
  let registry = /* @__PURE__ */ new Map();
@@ -767,12 +793,29 @@ function createAgentHandleStore() {
767
793
  }
768
794
  };
769
795
  }
796
+ function createTaskHandleStore() {
797
+ let currentFactory = null;
798
+ return {
799
+ register(factory) {
800
+ currentFactory = factory;
801
+ },
802
+ get(ctx) {
803
+ if (currentFactory === null) return void 0;
804
+ return currentFactory(ctx);
805
+ },
806
+ clear() {
807
+ currentFactory = null;
808
+ }
809
+ };
810
+ }
770
811
  function createAppRegistries() {
771
812
  const tool = createToolRegistry();
772
813
  const agent = createAgentRegistry(tool);
773
814
  const skill = createSkillRegistry();
815
+ const task = createTaskRegistry();
774
816
  const agentHandle = createAgentHandleStore();
775
- return { tool, agent, skill, agentHandle };
817
+ const taskHandle = createTaskHandleStore();
818
+ return { tool, agent, skill, task, agentHandle, taskHandle };
776
819
  }
777
820
  var defaultRegistries = createAppRegistries();
778
821
 
@@ -826,6 +869,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
826
869
  // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
827
870
  case "agent":
828
871
  return ctx.registries ? ctx.registries.agentHandle.get(ctx) : getAgentHandle(ctx);
872
+ // 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
873
+ case "tasks":
874
+ return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
829
875
  default:
830
876
  return void 0;
831
877
  }