@faapi/faapi 6.33.0 → 6.34.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 +215 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/{contextTypes-D1ORxB1P.d.ts → contextTypes-Bsc1ljvn.d.ts} +163 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +214 -26
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/workerEntry.d.ts +23 -2
- package/dist/workerEntry.js +48 -0
- package/dist/workerEntry.js.map +1 -1
- package/package.json +1 -1
|
@@ -196,6 +196,8 @@ interface TaskDriverJob {
|
|
|
196
196
|
attempt: number;
|
|
197
197
|
/** 停机/取消信号;驱动不支持取消时为永不 abort 的信号 */
|
|
198
198
|
signal: AbortSignal;
|
|
199
|
+
/** 所属任务组(组投递的成员任务携带;驱动在交付时从组标识载体还原) */
|
|
200
|
+
groupId?: string;
|
|
199
201
|
}
|
|
200
202
|
/** 语义层交给驱动层的执行函数(抛错 = 失败,由驱动按入队时的 retries 重试) */
|
|
201
203
|
type TaskDriverProcess = (job: TaskDriverJob) => Promise<unknown>;
|
|
@@ -217,6 +219,8 @@ interface TaskDriverRecord {
|
|
|
217
219
|
result?: unknown;
|
|
218
220
|
/** 失败/取消原因 */
|
|
219
221
|
error?: string;
|
|
222
|
+
/** 所属任务组(组投递的成员任务携带;驱动从组标识载体还原后返回) */
|
|
223
|
+
groupId?: string;
|
|
220
224
|
createdAt: number;
|
|
221
225
|
/** 计划执行时间戳(延迟任务) */
|
|
222
226
|
runAt?: number;
|
|
@@ -244,6 +248,12 @@ interface TaskDriver {
|
|
|
244
248
|
dedupId?: string;
|
|
245
249
|
timeoutMs?: number;
|
|
246
250
|
graceMs?: number;
|
|
251
|
+
/**
|
|
252
|
+
* 所属任务组(组投递成员携带,可选)——驱动以此把组标识随任务持久化
|
|
253
|
+
* (载体为驱动内部实现,两内置驱动以载荷包装形态存储),交付/查询时还原为
|
|
254
|
+
* `TaskDriverJob.groupId` / `TaskDriverRecord.groupId`
|
|
255
|
+
*/
|
|
256
|
+
groupId?: string;
|
|
247
257
|
}): Promise<string>;
|
|
248
258
|
/**
|
|
249
259
|
* 注册某任务的消费 worker(幂等覆盖)。process 抛错 = 本次失败,驱动决定重试。
|
|
@@ -282,6 +292,60 @@ interface TaskDriver {
|
|
|
282
292
|
* 任务不存在 / 状态不允许时由驱动抛错。
|
|
283
293
|
*/
|
|
284
294
|
retry?(name: string, id: string): Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* 任务组记账(可选能力,整对象实现——未实现时 TaskClient.enqueueGroup /
|
|
297
|
+
* getGroup 显式抛错)。语义与两内置驱动的存储映射见 taskGroups.md。
|
|
298
|
+
*
|
|
299
|
+
* 记账语义约束(实现须满足):
|
|
300
|
+
* - 成员落定**幂等**——同一成员重复 settle 不重复计数(成员行 settled 守卫)
|
|
301
|
+
* - settle/unsettle 的计数与成员状态翻转须原子(单条 SQL / Lua)
|
|
302
|
+
* - 记账状态持久化在驱动存储,跨实例/重启正确
|
|
303
|
+
*/
|
|
304
|
+
groups?: TaskDriverGroupOps;
|
|
305
|
+
}
|
|
306
|
+
/** 组记账 create 的声明(enqueueGroup 语义层透传) */
|
|
307
|
+
interface TaskDriverGroupCreate {
|
|
308
|
+
id: string;
|
|
309
|
+
/** 成员任务名(一组一个任务名) */
|
|
310
|
+
task: string;
|
|
311
|
+
total: number;
|
|
312
|
+
onComplete?: string;
|
|
313
|
+
onFailure: TaskGroupOnFailure;
|
|
314
|
+
}
|
|
315
|
+
/** settle 的返回:记账后快照 + 是否本次落定使组到达全部落定 */
|
|
316
|
+
type TaskDriverGroupSettleResult = TaskGroupSnapshot & {
|
|
317
|
+
/** 本次落定后 settled >= total */
|
|
318
|
+
isLast: boolean;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* 任务组记账驱动能力(TaskDriver.groups)
|
|
322
|
+
*/
|
|
323
|
+
interface TaskDriverGroupOps {
|
|
324
|
+
/**
|
|
325
|
+
* 创建组记账。同 id 重复创建:参数完全一致幂等跳过;不一致抛错
|
|
326
|
+
* (组标识是业务关联键,形状漂移属调用方错误)
|
|
327
|
+
*/
|
|
328
|
+
create(decl: TaskDriverGroupCreate): Promise<void>;
|
|
329
|
+
/**
|
|
330
|
+
* 记账一次成员落定(幂等——已落定成员重复 settle 计数不变)
|
|
331
|
+
*
|
|
332
|
+
* @param outcome 成员最终终态
|
|
333
|
+
* @returns 记账后快照;isLast = 本次落定后 settled >= total
|
|
334
|
+
* @throws 组不存在
|
|
335
|
+
*/
|
|
336
|
+
settle(groupId: string, jobId: string, outcome: TaskGroupOutcome): Promise<TaskDriverGroupSettleResult>;
|
|
337
|
+
/** 逆向记账:成员重试回 pending 时撤销一次落定(成员未落定时 no-op) */
|
|
338
|
+
unsettle(groupId: string, jobId: string): Promise<void>;
|
|
339
|
+
/** 标记完成回调已入队(幂等) */
|
|
340
|
+
markCompletionEnqueued(groupId: string): Promise<void>;
|
|
341
|
+
/** 组快照;不存在返回 undefined */
|
|
342
|
+
get(groupId: string): Promise<TaskGroupSnapshot | undefined>;
|
|
343
|
+
/**
|
|
344
|
+
* fail-fast:取消组内未落定成员(等待/延迟中的不再执行;在跑的自然跑完),
|
|
345
|
+
* 实际取消成功的成员落定为 cancelled(幂等守卫防与运行实例的落定竞态重复计数),
|
|
346
|
+
* 返回记账后快照。对 run-to-completion 组为 no-op(返回当前快照)。
|
|
347
|
+
*/
|
|
348
|
+
cancelRemaining(groupId: string): Promise<TaskGroupSnapshot>;
|
|
285
349
|
}
|
|
286
350
|
|
|
287
351
|
/**
|
|
@@ -1588,6 +1652,12 @@ interface TaskWorkerOptions {
|
|
|
1588
1652
|
* 不传则日志条目被忽略。宽限期(取消判定后)到达的条目不采纳(超时判定即终局)
|
|
1589
1653
|
*/
|
|
1590
1654
|
onLog?: (entry: LogEntry) => void;
|
|
1655
|
+
/**
|
|
1656
|
+
* 任务客户端 RPC 分派(worker 内 `taskCtx.tasks.*` 经 `{ type: 'tasks-call' }`
|
|
1657
|
+
* 消息回传宿主执行,结果/错误按 seq 回传)——语义层注入(闭包持有队列本体);
|
|
1658
|
+
* 不传则 worker 内 taskCtx.tasks 的调用以错误拒绝(直接构造执行器的测试场景)
|
|
1659
|
+
*/
|
|
1660
|
+
onTasksCall?: (method: string, args: unknown[]) => Promise<unknown>;
|
|
1591
1661
|
}
|
|
1592
1662
|
declare function runTaskInWorker(options: TaskWorkerOptions): Promise<unknown>;
|
|
1593
1663
|
|
|
@@ -1657,6 +1727,49 @@ interface TaskMetadata {
|
|
|
1657
1727
|
* 驱动重试时记录回到 running 继续流转。
|
|
1658
1728
|
*/
|
|
1659
1729
|
type TaskJobStatus = 'pending' | 'running' | 'retry' | 'done' | 'failed' | 'cancelled';
|
|
1730
|
+
/**
|
|
1731
|
+
* 组内成员落定的终态(TaskJobStatus 的终态子集——落定 = 最终终态,
|
|
1732
|
+
* 重试等待中的失败不算)
|
|
1733
|
+
*/
|
|
1734
|
+
type TaskGroupOutcome = 'done' | 'failed' | 'cancelled';
|
|
1735
|
+
/**
|
|
1736
|
+
* 组级失败语义(enqueueGroup 声明):
|
|
1737
|
+
* - 'run-to-completion'(默认):成员最终失败不影响其余成员,回调照常在全部落定时触发
|
|
1738
|
+
* - 'fail-fast':首个成员最终失败即取消组内未落定成员(在跑的自然跑完),取消成员落定 cancelled
|
|
1739
|
+
*/
|
|
1740
|
+
type TaskGroupOnFailure = 'fail-fast' | 'run-to-completion';
|
|
1741
|
+
/**
|
|
1742
|
+
* 完成回调任务的 payload 契约(fan-in)——全部成员落定时框架以该形状入队
|
|
1743
|
+
* onComplete 任务(走 enqueue 同一 payload 校验通道,回调任务 Payload 声明须兼容,
|
|
1744
|
+
* 建议 `interface Payload extends TaskGroupSummary {}`)
|
|
1745
|
+
*/
|
|
1746
|
+
interface TaskGroupSummary {
|
|
1747
|
+
/** 组标识(业务关联键原样透传) */
|
|
1748
|
+
groupId: string;
|
|
1749
|
+
/** 成员任务名 */
|
|
1750
|
+
task: string;
|
|
1751
|
+
/** 成员总数 */
|
|
1752
|
+
total: number;
|
|
1753
|
+
/** 按最终终态分桶计数 */
|
|
1754
|
+
done: number;
|
|
1755
|
+
failed: number;
|
|
1756
|
+
cancelled: number;
|
|
1757
|
+
/** 已落定数(done + failed + cancelled) */
|
|
1758
|
+
settled: number;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* 组记账快照(getGroup 返回;驱动侧存储的实时投影)
|
|
1762
|
+
*/
|
|
1763
|
+
interface TaskGroupSnapshot extends TaskGroupSummary {
|
|
1764
|
+
/** 完成回调任务名(未声明时无 fan-in,仅记账) */
|
|
1765
|
+
onComplete?: string;
|
|
1766
|
+
/** 组级失败语义 */
|
|
1767
|
+
onFailure: TaskGroupOnFailure;
|
|
1768
|
+
/** 'settled' = 全部落定 */
|
|
1769
|
+
status: 'open' | 'settled';
|
|
1770
|
+
/** 完成回调是否已入队(false 且 status settled = 回调未入队——可经同 groupId 重投自愈) */
|
|
1771
|
+
completionEnqueued: boolean;
|
|
1772
|
+
}
|
|
1660
1773
|
/**
|
|
1661
1774
|
* 任务执行记录(内存快照)
|
|
1662
1775
|
*/
|
|
@@ -1673,6 +1786,8 @@ interface TaskJob {
|
|
|
1673
1786
|
error?: string;
|
|
1674
1787
|
/** 最近一次进度上报值(run 内 `taskCtx.progress(value)` 写入;派发时清空上一轮) */
|
|
1675
1788
|
progress?: unknown;
|
|
1789
|
+
/** 所属任务组(组投递的成员任务携带;非组任务无此字段) */
|
|
1790
|
+
groupId?: string;
|
|
1676
1791
|
createdAt: number;
|
|
1677
1792
|
/** 计划执行时间戳(重试/延迟任务与 createdAt 不同) */
|
|
1678
1793
|
runAt?: number;
|
|
@@ -1699,6 +1814,12 @@ interface TaskRegistriesSnapshot {
|
|
|
1699
1814
|
interface TaskContext {
|
|
1700
1815
|
/** 优雅停机时对在跑任务 abort 的信号 */
|
|
1701
1816
|
signal: AbortSignal;
|
|
1817
|
+
/**
|
|
1818
|
+
* 任务客户端(与 `ctx.tasks` / `app.tasks` 同一 app 实例队列)——任务内入队 /
|
|
1819
|
+
* 组投递 / 查询不绕 `getApp()`。隔离路径为 postMessage RPC 代理(语义一致,
|
|
1820
|
+
* 参数与返回值须可结构化克隆)
|
|
1821
|
+
*/
|
|
1822
|
+
tasks: TaskClient;
|
|
1702
1823
|
/**
|
|
1703
1824
|
* faapi.config.ts 全量配置(含自定义业务配置),类型经 `FaapiContextConfig`
|
|
1704
1825
|
* 声明合并增强——与 handler `ctx.config` 同一类型、同一对象(活引用)。
|
|
@@ -1757,6 +1878,13 @@ interface TaskContext {
|
|
|
1757
1878
|
interface IsolatedTaskContext {
|
|
1758
1879
|
/** 优雅停机时对在跑任务 abort 的信号(abort 后宽限期内未退出 terminate 硬杀) */
|
|
1759
1880
|
signal: AbortSignal;
|
|
1881
|
+
/**
|
|
1882
|
+
* 任务客户端代理——全方法经 postMessage RPC 回传宿主执行(宿主走完整 enqueue
|
|
1883
|
+
* 通道:存在性检查 + payload 校验 + 驱动入队 + 本地记录),与进程内路径零语义差;
|
|
1884
|
+
* **参数与返回值须可结构化克隆**(不可克隆按执行错误处理,与 progress/log 同口径);
|
|
1885
|
+
* 挂起的调用随任务超时两段式取消一并终止,无独立超时
|
|
1886
|
+
*/
|
|
1887
|
+
tasks: TaskClient;
|
|
1760
1888
|
job: {
|
|
1761
1889
|
id: string;
|
|
1762
1890
|
name: string;
|
|
@@ -1841,6 +1969,40 @@ interface TaskClient {
|
|
|
1841
1969
|
* @throws 驱动未实现 TaskDriver.retry
|
|
1842
1970
|
*/
|
|
1843
1971
|
retry(name: string, id: string): Promise<void>;
|
|
1972
|
+
/**
|
|
1973
|
+
* 组投递:一次投递 N 个同构子任务并挂同一组标识(组投递 / 记账 / fan-in 完整
|
|
1974
|
+
* 语义见 taskGroups.md)
|
|
1975
|
+
*
|
|
1976
|
+
* - payloads 全量校验前置(任一失败整组不投递);空数组抛错
|
|
1977
|
+
* - opts.groupId 缺省自动生成;同 id 幂等重投(成员 dedupId 自动派生
|
|
1978
|
+
* `faapi-group:<groupId>:<index>`,替代业务自拼序号;长任务扇出建议 groupId
|
|
1979
|
+
* 从业务键派生——发起任务重试即天然补投自愈)
|
|
1980
|
+
* - opts.onComplete:全部成员落定时框架自动入队的回调任务名(payload =
|
|
1981
|
+
* TaskGroupSummary 契约);opts.onFailure:'fail-fast' | 'run-to-completion'
|
|
1982
|
+
* (缺省后者)
|
|
1983
|
+
*
|
|
1984
|
+
* @returns groupId 与各成员任务 id
|
|
1985
|
+
* @throws 任务不存在 / payloads 为空 / payload 校验失败(ValidationError)/
|
|
1986
|
+
* 队列已停止 / 驱动未实现组记账(TaskDriver.groups 缺失)
|
|
1987
|
+
*/
|
|
1988
|
+
enqueueGroup(name: string, payloads: unknown[], opts?: {
|
|
1989
|
+
groupId?: string;
|
|
1990
|
+
onComplete?: string;
|
|
1991
|
+
onFailure?: TaskGroupOnFailure;
|
|
1992
|
+
delayMs?: number;
|
|
1993
|
+
}): Promise<{
|
|
1994
|
+
groupId: string;
|
|
1995
|
+
jobs: {
|
|
1996
|
+
id: string;
|
|
1997
|
+
}[];
|
|
1998
|
+
}>;
|
|
1999
|
+
/**
|
|
2000
|
+
* 组记账快照(计数器为驱动侧存储的实时投影——跨实例/重启正确)
|
|
2001
|
+
*
|
|
2002
|
+
* @returns 组不存在返回 undefined
|
|
2003
|
+
* @throws 驱动未实现组记账(TaskDriver.groups 缺失)
|
|
2004
|
+
*/
|
|
2005
|
+
getGroup(groupId: string): Promise<TaskGroupSnapshot | undefined>;
|
|
1844
2006
|
}
|
|
1845
2007
|
/**
|
|
1846
2008
|
* 任务队列(TaskClient + 生命周期控制,createAppBase 内部使用)
|
|
@@ -2307,4 +2469,4 @@ interface FaapiContext {
|
|
|
2307
2469
|
deleteCookie(name: string): void;
|
|
2308
2470
|
}
|
|
2309
2471
|
|
|
2310
|
-
export { type SseOptions as $, type AgentCore as A, type LlmChannelStore as B, type CreateLoggerOptions as C, type LlmComplete as D, type LlmCompleteOptions as E, type FaapiMiddleware as F, type LlmConfig as G, type HelmetOptions as H, type Injector as I, type LlmModelConfig as J, type LogEntry as K, type LogConfig as L, type LogLevel as M, type LogSink as N, type PluginDeclaration as O, type PluginContext as P, type RequestHandler as Q, type RouteManifest as R, type ResponseConfig as S, type TaskQueueDeps as T, type RouteInfo as U, type RouteInputSchema as V, type WsRouteManifest as W, type RouteOutputSchema as X, type RouteParamSchema as Y, type SkillRegistry as Z, type SseEvent as _, type TaskQueue as a, type SseWriter as a0, type TaskBullMqOptions as a1, type TaskConfig as a2, type TaskContext as a3, type
|
|
2472
|
+
export { type SseOptions as $, type AgentCore as A, type LlmChannelStore as B, type CreateLoggerOptions as C, type LlmComplete as D, type LlmCompleteOptions as E, type FaapiMiddleware as F, type LlmConfig as G, type HelmetOptions as H, type Injector as I, type LlmModelConfig as J, type LogEntry as K, type LogConfig as L, type LogLevel as M, type LogSink as N, type PluginDeclaration as O, type PluginContext as P, type RequestHandler as Q, type RouteManifest as R, type ResponseConfig as S, type TaskQueueDeps as T, type RouteInfo as U, type RouteInputSchema as V, type WsRouteManifest as W, type RouteOutputSchema as X, type RouteParamSchema as Y, type SkillRegistry as Z, type SseEvent as _, type TaskQueue as a, type SseWriter as a0, type TaskBullMqOptions as a1, type TaskConfig as a2, type TaskContext as a3, type TaskDriverGroupCreate as a4, type TaskDriverGroupOps as a5, type TaskDriverGroupSettleResult as a6, type TaskDriverJob as a7, type TaskDriverProcess as a8, type TaskDriverRecord as a9, type TaskFailedHandler as aa, type TaskFailedInfo as ab, type TaskGroupOnFailure as ac, type TaskGroupOutcome as ad, type TaskGroupSnapshot as ae, type TaskGroupSummary as af, type TaskJob as ag, type TaskJobStatus as ah, type TaskMetadata as ai, type TaskModule as aj, type TaskPgBossOptions as ak, type TaskRegistriesSnapshot as al, type TaskRegistriesView as am, type ToolCore as an, type ToolPathMeta as ao, type ToolRegistry as ap, type UpgradeHandler as aq, cors as ar, createAppRegistries as as, createTaskRegistriesView as at, createTaskRegistry as au, helmet as av, type TaskDriver as b, type TaskRegistry as c, type TaskManifest as d, type AgentMetadata as e, type ToolMetadata as f, type FaapiContext as g, type FaapiConfig as h, type Logger as i, type AppRegistries as j, type TaskClient as k, type AgentConfig as l, type AgentHandleFactory as m, type AgentHandleStore as n, type AgentPathMeta as o, type AgentRegistry as p, type AgentToolDescriptor as q, type CorsOptions as r, type FaapiContextConfig as s, type FaapiPlugin as t, type FaapiTaskMeta as u, type FailOptions as v, type InjectorMap as w, type IsolatedTaskContext as x, type LifecycleContext as y, type LifecycleHooks as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FaapiMiddleware, R as RouteManifest, W as WsRouteManifest, T as TaskQueueDeps, a as TaskQueue, b as TaskDriver, c as TaskRegistry, d as TaskManifest, A as AgentCore, e as AgentMetadata, f as ToolMetadata, g as FaapiContext, h as FaapiConfig, L as LogConfig, C as CreateLoggerOptions, i as Logger, j as AppRegistries, k as TaskClient } from './contextTypes-
|
|
2
|
-
export { l as AgentConfig, m as AgentHandleFactory, n as AgentHandleStore, o as AgentPathMeta, p as AgentRegistry, q as AgentToolDescriptor, r as CorsOptions, s as FaapiContextConfig, t as FaapiPlugin, u as FaapiTaskMeta, v as FailOptions, H as HelmetOptions, I as Injector, w as InjectorMap, x as IsolatedTaskContext, y as LifecycleContext, z as LifecycleHooks, B as LlmChannelStore, D as LlmComplete, E as LlmCompleteOptions, G as LlmConfig, J as LlmModelConfig, K as LogEntry, M as LogLevel, N as LogSink, P as PluginContext, O as PluginDeclaration, Q as RequestHandler, S as ResponseConfig, U as RouteInfo, V as RouteInputSchema, X as RouteOutputSchema, Y as RouteParamSchema, Z as SkillRegistry, _ as SseEvent, $ as SseOptions, a0 as SseWriter, a1 as TaskBullMqOptions, a2 as TaskConfig, a3 as TaskContext, a4 as
|
|
1
|
+
import { F as FaapiMiddleware, R as RouteManifest, W as WsRouteManifest, T as TaskQueueDeps, a as TaskQueue, b as TaskDriver, c as TaskRegistry, d as TaskManifest, A as AgentCore, e as AgentMetadata, f as ToolMetadata, g as FaapiContext, h as FaapiConfig, L as LogConfig, C as CreateLoggerOptions, i as Logger, j as AppRegistries, k as TaskClient } from './contextTypes-Bsc1ljvn.js';
|
|
2
|
+
export { l as AgentConfig, m as AgentHandleFactory, n as AgentHandleStore, o as AgentPathMeta, p as AgentRegistry, q as AgentToolDescriptor, r as CorsOptions, s as FaapiContextConfig, t as FaapiPlugin, u as FaapiTaskMeta, v as FailOptions, H as HelmetOptions, I as Injector, w as InjectorMap, x as IsolatedTaskContext, y as LifecycleContext, z as LifecycleHooks, B as LlmChannelStore, D as LlmComplete, E as LlmCompleteOptions, G as LlmConfig, J as LlmModelConfig, K as LogEntry, M as LogLevel, N as LogSink, P as PluginContext, O as PluginDeclaration, Q as RequestHandler, S as ResponseConfig, U as RouteInfo, V as RouteInputSchema, X as RouteOutputSchema, Y as RouteParamSchema, Z as SkillRegistry, _ as SseEvent, $ as SseOptions, a0 as SseWriter, a1 as TaskBullMqOptions, a2 as TaskConfig, a3 as TaskContext, a4 as TaskDriverGroupCreate, a5 as TaskDriverGroupOps, a6 as TaskDriverGroupSettleResult, a7 as TaskDriverJob, a8 as TaskDriverProcess, a9 as TaskDriverRecord, aa as TaskFailedHandler, ab as TaskFailedInfo, ac as TaskGroupOnFailure, ad as TaskGroupOutcome, ae as TaskGroupSnapshot, af as TaskGroupSummary, ag as TaskJob, ah as TaskJobStatus, ai as TaskMetadata, aj as TaskModule, ak as TaskPgBossOptions, al as TaskRegistriesSnapshot, am as TaskRegistriesView, an as ToolCore, ao as ToolPathMeta, ap as ToolRegistry, aq as UpgradeHandler, ar as cors, as as createAppRegistries, at as createTaskRegistriesView, au as createTaskRegistry, av as helmet } from './contextTypes-Bsc1ljvn.js';
|
|
3
3
|
import ts from 'typescript';
|
|
4
4
|
import { Server } from 'node:http';
|
|
5
5
|
import 'node:net';
|
package/dist/index.js
CHANGED
|
@@ -2194,6 +2194,53 @@ async function runTaskInWorker(options) {
|
|
|
2194
2194
|
worker.on(
|
|
2195
2195
|
"message",
|
|
2196
2196
|
(msg) => {
|
|
2197
|
+
if (msg?.type === "tasks-call") {
|
|
2198
|
+
const { seq, method, args } = msg;
|
|
2199
|
+
const reply = (body) => {
|
|
2200
|
+
try {
|
|
2201
|
+
worker.postMessage({ type: "tasks-result", seq, ...body });
|
|
2202
|
+
} catch (err) {
|
|
2203
|
+
try {
|
|
2204
|
+
worker.postMessage({
|
|
2205
|
+
type: "tasks-result",
|
|
2206
|
+
seq,
|
|
2207
|
+
ok: false,
|
|
2208
|
+
error: {
|
|
2209
|
+
name: "Error",
|
|
2210
|
+
message: `taskCtx.tasks.${method} result is not cloneable: ${String(err)}`,
|
|
2211
|
+
stack: void 0,
|
|
2212
|
+
props: {}
|
|
2213
|
+
}
|
|
2214
|
+
});
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
if (!options.onTasksCall) {
|
|
2220
|
+
reply({
|
|
2221
|
+
ok: false,
|
|
2222
|
+
error: {
|
|
2223
|
+
name: "Error",
|
|
2224
|
+
message: `taskCtx.tasks.${method} is unavailable: no host handler was provided for this execution`,
|
|
2225
|
+
stack: void 0,
|
|
2226
|
+
props: {}
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
options.onTasksCall(method, args ?? []).then((value) => reply({ ok: true, value })).catch((err) => {
|
|
2232
|
+
reply({
|
|
2233
|
+
ok: false,
|
|
2234
|
+
error: {
|
|
2235
|
+
name: err instanceof Error ? err.name : "Error",
|
|
2236
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2237
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
2238
|
+
props: {}
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2197
2244
|
if (phase === "grace") {
|
|
2198
2245
|
if (msg?.type === "done") {
|
|
2199
2246
|
finish(
|
|
@@ -2605,6 +2652,17 @@ function createLogger(scope, options) {
|
|
|
2605
2652
|
|
|
2606
2653
|
// src/task/taskQueue.ts
|
|
2607
2654
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
2655
|
+
var GROUP_MEMBER_DEDUP = (groupId, index) => `faapi-group:${groupId}:${index}`;
|
|
2656
|
+
var GROUP_COMPLETION_DEDUP = (groupId) => `faapi-group:${groupId}:complete`;
|
|
2657
|
+
var TASK_CLIENT_METHODS = /* @__PURE__ */ new Set([
|
|
2658
|
+
"enqueue",
|
|
2659
|
+
"enqueueGroup",
|
|
2660
|
+
"list",
|
|
2661
|
+
"listQueued",
|
|
2662
|
+
"cancel",
|
|
2663
|
+
"retry",
|
|
2664
|
+
"getGroup"
|
|
2665
|
+
]);
|
|
2608
2666
|
var MAX_FINISHED_RECORDS = 1e3;
|
|
2609
2667
|
function createTaskQueue(deps) {
|
|
2610
2668
|
const { registry, rootDir, driver } = deps;
|
|
@@ -2652,6 +2710,54 @@ function createTaskQueue(deps) {
|
|
|
2652
2710
|
throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
|
|
2653
2711
|
}
|
|
2654
2712
|
}
|
|
2713
|
+
function assertGroupSupport() {
|
|
2714
|
+
if (!driver.groups) {
|
|
2715
|
+
throw new Error(
|
|
2716
|
+
"[faapi] Task driver does not support group accounting (TaskDriver.groups is not implemented). Group enqueue/settle requires a driver that implements it \u2014 see driverTypes.md / taskGroups.md."
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
async function maybeFireCompletion(snap) {
|
|
2721
|
+
if (!driver.groups) return;
|
|
2722
|
+
if (!snap.onComplete || snap.completionEnqueued || snap.settled < snap.total) return;
|
|
2723
|
+
const summary = {
|
|
2724
|
+
groupId: snap.groupId,
|
|
2725
|
+
task: snap.task,
|
|
2726
|
+
total: snap.total,
|
|
2727
|
+
done: snap.done,
|
|
2728
|
+
failed: snap.failed,
|
|
2729
|
+
cancelled: snap.cancelled,
|
|
2730
|
+
settled: snap.settled
|
|
2731
|
+
};
|
|
2732
|
+
try {
|
|
2733
|
+
await enqueueCore(snap.onComplete, summary, {
|
|
2734
|
+
dedupId: GROUP_COMPLETION_DEDUP(snap.groupId)
|
|
2735
|
+
});
|
|
2736
|
+
await driver.groups.markCompletionEnqueued(snap.groupId);
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
console.error(
|
|
2739
|
+
`[faapi] task group "${snap.groupId}" completion enqueue failed (group is settled; re-invoke enqueueGroup with the same groupId to re-arm the callback):`,
|
|
2740
|
+
err
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
async function settleGroup(record, outcome, willRetry) {
|
|
2745
|
+
const groupId = record.groupId;
|
|
2746
|
+
if (!groupId || !driver.groups) return;
|
|
2747
|
+
if (outcome !== "done" && willRetry) return;
|
|
2748
|
+
try {
|
|
2749
|
+
const snap = await driver.groups.settle(groupId, record.id, outcome);
|
|
2750
|
+
if (outcome === "failed" && snap.onFailure === "fail-fast") {
|
|
2751
|
+
await maybeFireCompletion(await driver.groups.cancelRemaining(groupId));
|
|
2752
|
+
}
|
|
2753
|
+
await maybeFireCompletion(snap);
|
|
2754
|
+
} catch (err) {
|
|
2755
|
+
console.error(
|
|
2756
|
+
`[faapi] task group accounting failed for "${groupId}" (job ${record.id}, outcome ${outcome}):`,
|
|
2757
|
+
err
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2655
2761
|
function snapshotRegistries() {
|
|
2656
2762
|
return {
|
|
2657
2763
|
agents: registriesView.agent.listAgents().map((core) => registriesView.agent.getAgentEntry(core.name)).filter((entry) => entry !== void 0),
|
|
@@ -2690,11 +2796,13 @@ function createTaskQueue(deps) {
|
|
|
2690
2796
|
attempts: 0,
|
|
2691
2797
|
createdAt: Date.now()
|
|
2692
2798
|
};
|
|
2799
|
+
if (job.groupId !== void 0) record.groupId = job.groupId;
|
|
2693
2800
|
record.attempts = job.attempt;
|
|
2694
2801
|
transitionStatus(record, "running");
|
|
2695
2802
|
delete record.progress;
|
|
2696
2803
|
record.error = void 0;
|
|
2697
2804
|
records.set(job.id, record);
|
|
2805
|
+
const willRetry = job.attempt <= (meta.retries ?? 0);
|
|
2698
2806
|
try {
|
|
2699
2807
|
let result;
|
|
2700
2808
|
if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
|
|
@@ -2724,7 +2832,16 @@ function createTaskQueue(deps) {
|
|
|
2724
2832
|
onProgress: (value) => {
|
|
2725
2833
|
if (record.status === "running") record.progress = value;
|
|
2726
2834
|
},
|
|
2727
|
-
onLog: writeLogEntry
|
|
2835
|
+
onLog: writeLogEntry,
|
|
2836
|
+
// 隔离路径的 taskCtx.tasks 为 postMessage RPC 代理——宿主侧按方法名
|
|
2837
|
+
// 分派到队列本体(与 ctx.tasks / app.tasks 同一实例),结果/错误按 seq 回传
|
|
2838
|
+
onTasksCall: async (method, args) => {
|
|
2839
|
+
if (!TASK_CLIENT_METHODS.has(method)) {
|
|
2840
|
+
throw new Error(`[faapi] Unknown task client method "${method}"`);
|
|
2841
|
+
}
|
|
2842
|
+
const fn = queue[method];
|
|
2843
|
+
return fn.apply(queue, args);
|
|
2844
|
+
}
|
|
2728
2845
|
});
|
|
2729
2846
|
} else {
|
|
2730
2847
|
let mod = moduleCache2.get(job.name);
|
|
@@ -2737,6 +2854,8 @@ function createTaskQueue(deps) {
|
|
|
2737
2854
|
}
|
|
2738
2855
|
const taskCtx = {
|
|
2739
2856
|
signal: job.signal,
|
|
2857
|
+
// 活引用:与 ctx.tasks / app.tasks 同一 app 实例队列
|
|
2858
|
+
tasks: queue,
|
|
2740
2859
|
config: deps.config,
|
|
2741
2860
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
2742
2861
|
registries: registriesView,
|
|
@@ -2754,19 +2873,22 @@ function createTaskQueue(deps) {
|
|
|
2754
2873
|
transitionStatus(record, "done");
|
|
2755
2874
|
record.result = result;
|
|
2756
2875
|
evictFinishedRecords();
|
|
2876
|
+
await settleGroup(record, "done", willRetry);
|
|
2757
2877
|
return result;
|
|
2758
2878
|
} catch (err) {
|
|
2759
2879
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
2760
|
-
|
|
2880
|
+
const outcome = cancelled ? "cancelled" : "failed";
|
|
2881
|
+
transitionStatus(record, outcome);
|
|
2761
2882
|
record.error = err instanceof Error ? err.message : String(err);
|
|
2762
2883
|
evictFinishedRecords();
|
|
2884
|
+
await settleGroup(record, outcome, willRetry);
|
|
2763
2885
|
if (deps.onFailed) {
|
|
2764
2886
|
void Promise.resolve().then(
|
|
2765
2887
|
() => deps.onFailed({
|
|
2766
2888
|
task: job.name,
|
|
2767
2889
|
jobId: job.id,
|
|
2768
2890
|
attempt: job.attempt,
|
|
2769
|
-
willRetry
|
|
2891
|
+
willRetry,
|
|
2770
2892
|
cancelled,
|
|
2771
2893
|
error: record.error
|
|
2772
2894
|
})
|
|
@@ -2785,35 +2907,86 @@ function createTaskQueue(deps) {
|
|
|
2785
2907
|
});
|
|
2786
2908
|
}
|
|
2787
2909
|
}
|
|
2910
|
+
async function enqueueCore(name, payload, opts) {
|
|
2911
|
+
if (stopped) {
|
|
2912
|
+
throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
|
|
2913
|
+
}
|
|
2914
|
+
assertKnownTask(name);
|
|
2915
|
+
const data = await validatePayload(name, payload);
|
|
2916
|
+
const meta = registry.get(name);
|
|
2917
|
+
const id = await driver.enqueue(name, data, {
|
|
2918
|
+
delayMs: opts?.delayMs,
|
|
2919
|
+
retries: meta.retries ?? 0,
|
|
2920
|
+
dedupId: opts?.dedupId,
|
|
2921
|
+
// 执行硬限预算透传驱动(pgboss 映射 expireInSeconds):未声明 timeoutMs 时
|
|
2922
|
+
// 两字段为 undefined,驱动用自身兜底
|
|
2923
|
+
timeoutMs: meta.timeoutMs,
|
|
2924
|
+
graceMs: meta.graceMs,
|
|
2925
|
+
...opts?.groupId !== void 0 ? { groupId: opts.groupId } : {}
|
|
2926
|
+
});
|
|
2927
|
+
if (!records.has(id)) {
|
|
2928
|
+
records.set(id, {
|
|
2929
|
+
id,
|
|
2930
|
+
name,
|
|
2931
|
+
payload: data,
|
|
2932
|
+
status: "pending",
|
|
2933
|
+
attempts: 0,
|
|
2934
|
+
...opts?.groupId !== void 0 ? { groupId: opts.groupId } : {},
|
|
2935
|
+
createdAt: Date.now(),
|
|
2936
|
+
...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
return id;
|
|
2940
|
+
}
|
|
2788
2941
|
const queue = {
|
|
2789
2942
|
async enqueue(name, payload = {}, opts) {
|
|
2943
|
+
const id = await enqueueCore(name, payload, {
|
|
2944
|
+
delayMs: opts?.delayMs,
|
|
2945
|
+
dedupId: opts?.dedupId
|
|
2946
|
+
});
|
|
2947
|
+
return { id };
|
|
2948
|
+
},
|
|
2949
|
+
async enqueueGroup(name, payloads, opts) {
|
|
2790
2950
|
if (stopped) {
|
|
2791
2951
|
throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
|
|
2792
2952
|
}
|
|
2953
|
+
assertGroupSupport();
|
|
2793
2954
|
assertKnownTask(name);
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2955
|
+
if (!Array.isArray(payloads) || payloads.length === 0) {
|
|
2956
|
+
throw new Error(
|
|
2957
|
+
"[faapi] enqueueGroup requires a non-empty payloads array \u2014 a group without members can never settle."
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
2960
|
+
const onComplete = opts?.onComplete;
|
|
2961
|
+
if (onComplete !== void 0) assertKnownTask(onComplete);
|
|
2962
|
+
const onFailure = opts?.onFailure ?? "run-to-completion";
|
|
2963
|
+
const validated = [];
|
|
2964
|
+
for (const p of payloads) {
|
|
2965
|
+
validated.push(await validatePayload(name, p));
|
|
2966
|
+
}
|
|
2967
|
+
const groupId = opts?.groupId ?? crypto.randomUUID();
|
|
2968
|
+
await driver.groups.create({
|
|
2969
|
+
id: groupId,
|
|
2970
|
+
task: name,
|
|
2971
|
+
total: validated.length,
|
|
2972
|
+
...onComplete !== void 0 ? { onComplete } : {},
|
|
2973
|
+
onFailure
|
|
2804
2974
|
});
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
attempts: 0,
|
|
2812
|
-
createdAt: Date.now(),
|
|
2813
|
-
...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
|
|
2975
|
+
const jobs = [];
|
|
2976
|
+
for (let i = 0; i < validated.length; i++) {
|
|
2977
|
+
const id = await enqueueCore(name, validated[i], {
|
|
2978
|
+
delayMs: opts?.delayMs,
|
|
2979
|
+
dedupId: GROUP_MEMBER_DEDUP(groupId, i + 1),
|
|
2980
|
+
groupId
|
|
2814
2981
|
});
|
|
2982
|
+
jobs.push({ id });
|
|
2815
2983
|
}
|
|
2816
|
-
|
|
2984
|
+
await maybeFireCompletion(await driver.groups.get(groupId));
|
|
2985
|
+
return { groupId, jobs };
|
|
2986
|
+
},
|
|
2987
|
+
async getGroup(groupId) {
|
|
2988
|
+
assertGroupSupport();
|
|
2989
|
+
return driver.groups.get(groupId);
|
|
2817
2990
|
},
|
|
2818
2991
|
list(name) {
|
|
2819
2992
|
const snapshot = [];
|
|
@@ -2840,6 +3013,7 @@ function createTaskQueue(deps) {
|
|
|
2840
3013
|
status: record.status,
|
|
2841
3014
|
attempts: record.attempts,
|
|
2842
3015
|
createdAt: record.createdAt,
|
|
3016
|
+
...record.groupId !== void 0 ? { groupId: record.groupId } : {},
|
|
2843
3017
|
...record.result !== void 0 ? { result: record.result } : {},
|
|
2844
3018
|
...record.error !== void 0 ? { error: record.error } : {},
|
|
2845
3019
|
...record.runAt !== void 0 ? { runAt: record.runAt } : {}
|
|
@@ -2859,8 +3033,11 @@ function createTaskQueue(deps) {
|
|
|
2859
3033
|
}
|
|
2860
3034
|
await driver.cancel(name, id);
|
|
2861
3035
|
const record = records.get(id);
|
|
2862
|
-
if (record)
|
|
2863
|
-
|
|
3036
|
+
if (record) {
|
|
3037
|
+
transitionStatus(record, "cancelled");
|
|
3038
|
+
evictFinishedRecords();
|
|
3039
|
+
await settleGroup(record, "cancelled", false);
|
|
3040
|
+
}
|
|
2864
3041
|
},
|
|
2865
3042
|
async retry(name, id) {
|
|
2866
3043
|
if (!driver.retry) {
|
|
@@ -2870,7 +3047,18 @@ function createTaskQueue(deps) {
|
|
|
2870
3047
|
}
|
|
2871
3048
|
await driver.retry(name, id);
|
|
2872
3049
|
const record = records.get(id);
|
|
2873
|
-
if (record)
|
|
3050
|
+
if (record) {
|
|
3051
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
3052
|
+
transitionStatus(record, "pending");
|
|
3053
|
+
if (wasTerminal && record.groupId && driver.groups) {
|
|
3054
|
+
await driver.groups.unsettle(record.groupId, record.id).catch((err) => {
|
|
3055
|
+
console.error(
|
|
3056
|
+
`[faapi] task group unsettle failed for "${record.groupId}" (job ${record.id}):`,
|
|
3057
|
+
err
|
|
3058
|
+
);
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
2874
3062
|
},
|
|
2875
3063
|
async start() {
|
|
2876
3064
|
if (stopped) {
|