@faapi/task-pgboss 6.16.0 → 6.18.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/README.md CHANGED
@@ -37,13 +37,13 @@ export function POST(body, tasks) {
37
37
 
38
38
  | faapi 驱动接口 | pg-boss |
39
39
  | --- | --- |
40
- | `enqueue(name, payload, { retries, delayMs })` | 幂等 `createQueue(name)`(每任务名每进程一次,缓存短路)→ `boss.send(name, payload, { retryLimit, retryDelay: 1, retryBackoff: true, startAfter })` |
41
- | `startWorker(name, { concurrency, process })` | 幂等 `createQueue(name)` → `boss.work(name, { batchSize: concurrency }, handler)` |
42
- | `stop(timeoutMs)` | 停 work + `boss.stop({ close: true, timeout })` |
40
+ | `enqueue(name, payload, { retries, delayMs, dedupId, timeoutMs, graceMs })` | 幂等 `createQueue(name)`(每任务名每进程一次,缓存短路)→ `boss.send(name, payload, { retryLimit, retryDelay: 1, retryBackoff: true, expireInSeconds, startAfter, id })`。`expireInSeconds` = `timeoutMs + graceMs + 60s` 缓冲(未声明 `timeoutMs` 的任务用 `defaultExpireSeconds` 兜底,默认 24h)——pg-boss 以 expire_in 硬限执行,不映射会让 DDL 默认 15 分钟强杀仍在运行的长任务并重试 |
41
+ | `startWorker(name, { concurrency, process })` | 幂等 `createQueue(name)` → `boss.work(name, { batchSize: concurrency }, handler)`。批内任务并发执行、逐任务 `complete`/`fail` 结算——单个任务失败只消耗自己的重试额度,不毒化同批 |
42
+ | `stop(timeoutMs)` | `offWork`(与 deadline 竞速,卡死任务不悬挂停机)+ `boss.stop({ close: true, graceful: true, timeout })`(timeout 单位毫秒);deadline 到点 abort 在跑任务的 signal |
43
43
  | `stopWorkers()` | `offWork()`(不断开连接,dev 热替换重注册用) |
44
44
  | 失败重试 | pg-boss 侧执行(retryLimit + retryBackoff 指数退避) |
45
45
 
46
- 注意:pg-boss v10 不再隐式建队列(`send()` 对未创建队列静默返回 null)——驱动在投递/注册 worker 前自动幂等建队列,业务方无需预建;pg-boss 也不提供执行中任务的取消信号——`run` 的 `taskCtx.signal` 永不 abort,长任务请自行做超时控制。
46
+ 注意:pg-boss v10 不再隐式建队列(`send()` 对未创建队列静默返回 null)——驱动在投递/注册 worker 前自动幂等建队列,业务方无需预建。pg-boss 自身不提供执行中任务的取消信号,但 `stop` 超时路径会 abort `taskCtx.signal`(任务监听 signal 可尽快退出;仍不退出的由 expire_in 兜底结算)。
47
47
 
48
48
  ## License
49
49
 
package/dist/index.d.ts CHANGED
@@ -2,11 +2,21 @@ import { TaskDriver } from '@faapi/faapi';
2
2
  import PgBoss from 'pg-boss';
3
3
 
4
4
  /**
5
- * pg-boss 驱动选项(透传给 PgBoss 构造函数)
5
+ * pg-boss 驱动选项
6
6
  *
7
- * 常用:`connectionString`;其余见 pg-boss 文档(ConstructorOptions)。
7
+ * `defaultExpireSeconds` 之外全部透传给 PgBoss 构造函数(常用:`connectionString`,
8
+ * 其余见 pg-boss 文档 ConstructorOptions)。
8
9
  */
9
- type PgBossDriverOptions = PgBoss.ConstructorOptions;
10
- declare function createPgBossDriver(options?: PgBossDriverOptions): TaskDriver;
10
+ type PgBossDriverOptions = PgBoss.ConstructorOptions & {
11
+ /**
12
+ * 未声明 `timeoutMs` 的任务的 expire_in 兜底秒数(默认 24 小时)。
13
+ *
14
+ * pg-boss 以 job 的 expire_in 硬限 handler 执行(DDL 默认 15 分钟):超时判失败
15
+ * 重试,而任务还在后台跑 → 同一任务两份并发执行。声明了 `timeoutMs` 的任务由
16
+ * 驱动按 `timeoutMs + graceMs + 60s` 缓冲给足;未声明的任务用本兜底。
17
+ */
18
+ defaultExpireSeconds?: number;
19
+ };
20
+ declare function createPgBossDriver(driverOptions?: PgBossDriverOptions): TaskDriver;
11
21
 
12
22
  export { type PgBossDriverOptions, createPgBossDriver };
package/dist/index.js CHANGED
@@ -13,7 +13,11 @@ function dedupIdToUuid(dedupId) {
13
13
  h.slice(20, 32)
14
14
  ].join("-");
15
15
  }
16
- function createPgBossDriver(options = {}) {
16
+ var DEFAULT_EXPIRE_SECONDS = 24 * 60 * 60;
17
+ var EXPIRE_BUFFER_SECONDS = 60;
18
+ var DEFAULT_GRACE_MS = 5e3;
19
+ function createPgBossDriver(driverOptions = {}) {
20
+ const { defaultExpireSeconds, ...options } = driverOptions;
17
21
  let boss = null;
18
22
  let stopped = false;
19
23
  const workerIds = /* @__PURE__ */ new Map();
@@ -24,14 +28,26 @@ function createPgBossDriver(options = {}) {
24
28
  async function ensureBoss() {
25
29
  if (boss) return boss;
26
30
  if (!bossStarting) {
27
- bossStarting = (async () => {
31
+ const starting = (async () => {
28
32
  const b = new PgBoss(options);
33
+ b.on("error", (err) => {
34
+ console.error(
35
+ "[faapi] pg-boss error:",
36
+ err instanceof Error ? err.stack ?? err.message : err
37
+ );
38
+ });
29
39
  await b.start();
40
+ if (stopped) {
41
+ await b.stop().catch(() => {
42
+ });
43
+ throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
44
+ }
30
45
  boss = b;
31
46
  return b;
32
47
  })();
33
- void bossStarting.catch(() => {
34
- bossStarting = null;
48
+ bossStarting = starting;
49
+ void starting.catch(() => {
50
+ if (bossStarting === starting) bossStarting = null;
35
51
  });
36
52
  }
37
53
  return bossStarting;
@@ -56,11 +72,13 @@ function createPgBossDriver(options = {}) {
56
72
  }
57
73
  const b = await ensureBoss();
58
74
  await ensureQueue(b, name);
75
+ const expireInSeconds = opts?.timeoutMs ? Math.ceil((opts.timeoutMs + (opts.graceMs ?? DEFAULT_GRACE_MS)) / 1e3) + EXPIRE_BUFFER_SECONDS : defaultExpireSeconds ?? DEFAULT_EXPIRE_SECONDS;
59
76
  const sendOptions = {
60
77
  retryLimit: opts?.retries ?? 0,
61
78
  retryDelay: 1,
62
79
  // 秒;配合 retryBackoff 指数退避
63
80
  retryBackoff: true,
81
+ expireInSeconds,
64
82
  ...opts?.dedupId ? { id: dedupIdToUuid(opts.dedupId) } : {},
65
83
  ...opts?.delayMs ? { startAfter: new Date(Date.now() + opts.delayMs) } : {}
66
84
  };
@@ -85,46 +103,81 @@ function createPgBossDriver(options = {}) {
85
103
  name,
86
104
  { batchSize: workerOpts.concurrency, includeMetadata: true },
87
105
  async (jobs) => {
88
- for (const job of jobs) {
89
- const attempt = job.retryCount + 1;
90
- const controller = new AbortController();
91
- inflight.set(job.id, controller);
92
- try {
93
- await process({
94
- id: job.id,
95
- name,
96
- payload: job.data,
97
- attempt,
98
- signal: controller.signal
99
- });
100
- } finally {
101
- inflight.delete(job.id);
102
- }
103
- }
106
+ await Promise.all(
107
+ jobs.map(async (job) => {
108
+ const attempt = job.retryCount + 1;
109
+ const controller = new AbortController();
110
+ inflight.set(job.id, controller);
111
+ try {
112
+ try {
113
+ await process({
114
+ id: job.id,
115
+ name,
116
+ payload: job.data,
117
+ attempt,
118
+ signal: controller.signal
119
+ });
120
+ } catch (err) {
121
+ const reason = err instanceof Error ? { name: err.name, message: err.message } : { value: String(err) };
122
+ try {
123
+ await b.fail(name, job.id, reason);
124
+ } catch (failErr) {
125
+ console.error(
126
+ `[faapi] pg-boss fail() failed for job ${job.id} of "${name}":`,
127
+ failErr
128
+ );
129
+ }
130
+ return;
131
+ }
132
+ try {
133
+ await b.complete(name, job.id);
134
+ } catch (completeErr) {
135
+ console.error(
136
+ `[faapi] pg-boss complete() failed for job ${job.id} of "${name}":`,
137
+ completeErr
138
+ );
139
+ }
140
+ } finally {
141
+ inflight.delete(job.id);
142
+ }
143
+ })
144
+ );
104
145
  }
105
146
  );
106
147
  workerIds.set(name, workerId);
107
148
  },
108
149
  async stop(timeoutMs = 1e4) {
109
150
  stopped = true;
110
- const abortTimer = setTimeout(() => {
111
- for (const controller of inflight.values()) controller.abort();
112
- }, timeoutMs);
151
+ const b = boss;
152
+ bossStarting = null;
153
+ if (!b) return;
154
+ let deadlineTimer;
155
+ let aborted = false;
156
+ const deadline = new Promise((resolve) => {
157
+ deadlineTimer = setTimeout(() => {
158
+ aborted = true;
159
+ for (const controller of inflight.values()) controller.abort();
160
+ resolve();
161
+ }, timeoutMs);
162
+ });
113
163
  try {
114
- const b = boss;
115
- bossStarting = null;
116
- if (b) {
117
- for (const workerId of workerIds.values()) {
118
- await b.offWork(workerId).catch(() => {
119
- });
120
- }
121
- workerIds.clear();
122
- await b.stop({ close: true, graceful: true, timeout: Math.floor(timeoutMs / 1e3) });
123
- boss = null;
124
- }
164
+ await Promise.race([
165
+ (async () => {
166
+ for (const workerId of workerIds.values()) {
167
+ await b.offWork(workerId).catch(() => {
168
+ });
169
+ }
170
+ })(),
171
+ deadline
172
+ ]);
173
+ workerIds.clear();
174
+ await Promise.race([b.stop({ close: true, graceful: true, timeout: timeoutMs }), deadline]);
175
+ boss = null;
125
176
  } finally {
126
- clearTimeout(abortTimer);
127
- for (const controller of inflight.values()) controller.abort();
177
+ clearTimeout(deadlineTimer);
178
+ if (!aborted) {
179
+ for (const controller of inflight.values()) controller.abort();
180
+ }
128
181
  inflight.clear();
129
182
  }
130
183
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { TaskDriver, TaskDriverProcess } from '@faapi/faapi';\nimport PgBoss from 'pg-boss';\n\n/**\n * pg-boss 驱动选项(透传给 PgBoss 构造函数)\n *\n * 常用:`connectionString`;其余见 pg-boss 文档(ConstructorOptions)。\n */\nexport type PgBossDriverOptions = PgBoss.ConstructorOptions;\n\n/**\n * faapi 任务队列 pg-boss 驱动(PostgreSQL 持久化队列)\n *\n * 与 faapi 主包 `config.task.driver: 'pgboss'` 配合使用:\n *\n * ```ts\n * // faapi.config.ts\n * export default {\n * task: {\n * driver: 'pgboss',\n * pgboss: { connectionString: 'postgres://localhost:5432/app' },\n * },\n * } satisfies FaapiConfig;\n * ```\n *\n * 语义映射(详见包根 README):\n * - `enqueue` → 幂等 ensureQueue + `boss.send(name, payload, { retryLimit, retryDelay, retryBackoff, startAfter })`\n * - `startWorker` → 幂等 ensureQueue + `boss.work(name, { batchSize: concurrency, includeMetadata: true }, handler)`\n * - `stop` → `offWork` + `boss.stop({ close: true, graceful: true, timeout })`;超时后 abort 在跑任务的 signal\n * - 重试 → pg-boss 侧执行(retryLimit + retryBackoff 指数退避)\n */\n/**\n * dedupId → 确定性 UUID:pg-boss 的 send 自定义 id 要求 UUID 格式(SQL 侧 cast),\n * 任意字符串键经 SHA-1 映射为合法 UUID——同键必同 UUID(幂等),不同键碰撞可忽略\n */\nfunction dedupIdToUuid(dedupId: string): string {\n const h = createHash('sha1').update(dedupId).digest('hex');\n return [\n h.slice(0, 8),\n h.slice(8, 12),\n `5${h.slice(13, 16)}`, // version 5\n `${((parseInt(h.slice(16, 17), 16) & 0x3) | 0x8).toString(16)}${h.slice(17, 20)}`, // variant\n h.slice(20, 32),\n ].join('-');\n}\n\nexport function createPgBossDriver(options: PgBossDriverOptions = {}): TaskDriver {\n let boss: PgBoss | null = null;\n let stopped = false;\n /** 已创建的 pg-boss worker id(stopWorkers 时 offWork) */\n const workerIds = new Map<string, string>();\n /** 在跑任务的取消控制器(stop 超时 abort——run 监听 signal 可尽快退出) */\n const inflight = new Map<string, AbortController>();\n /** 已建队列名缓存(每任务名每进程一次真实 createQueue,之后短路) */\n const ensuredQueues = new Set<string>();\n /** 并发首次投递同名任务的 in-flight 去重 */\n const ensuring = new Map<string, Promise<void>>();\n /** boss.start() in-flight 共享 promise(并发首调等待同一次建连,不出现半启动实例) */\n let bossStarting: Promise<PgBoss> | null = null;\n\n async function ensureBoss(): Promise<PgBoss> {\n if (boss) return boss;\n if (!bossStarting) {\n bossStarting = (async () => {\n const b = new PgBoss(options);\n // pg-boss 惰性连接:start 后才连库;驱动在首次 enqueue/startWorker 前建立。\n // start 完成前实例不外借——pre-open 的 executeSql 静默 no-op(返回 undefined),\n // 建连中并发调用会在半启动实例上跑 SQL\n await b.start();\n boss = b;\n return b;\n })();\n // start 失败清空 in-flight:下次调用重新建连(坏实例不入缓存)\n void bossStarting.catch(() => {\n bossStarting = null;\n });\n }\n return bossStarting;\n }\n\n /**\n * pg-boss v10 不再隐式建队列(v9 行为):send() 的 INSERT JOIN queue 对未创建队列\n * 静默返回 null,work() 只注册进程内轮询器同样不建队列——投递/注册 worker 前先建队列。\n * create_queue plpgsql 幂等(INSERT ON CONFLICT DO NOTHING,已存在直接返回),\n * 重复/并发调用安全;失败不入缓存,下次投递重试。\n */\n async function ensureQueue(b: PgBoss, name: string): Promise<void> {\n if (ensuredQueues.has(name)) return;\n const pending = ensuring.get(name);\n if (pending) return pending;\n const promise = (async () => {\n await b.createQueue(name);\n ensuredQueues.add(name);\n })().finally(() => {\n ensuring.delete(name);\n });\n ensuring.set(name, promise);\n return promise;\n }\n\n return {\n async enqueue(name, payload, opts) {\n if (stopped) {\n throw new Error('[faapi] Task queue is stopped and no longer accepts jobs');\n }\n const b = await ensureBoss();\n await ensureQueue(b, name);\n const sendOptions: PgBoss.SendOptions = {\n retryLimit: opts?.retries ?? 0,\n retryDelay: 1, // 秒;配合 retryBackoff 指数退避\n retryBackoff: true,\n ...(opts?.dedupId ? { id: dedupIdToUuid(opts.dedupId) } : {}),\n ...(opts?.delayMs ? { startAfter: new Date(Date.now() + opts.delayMs) } : {}),\n };\n // pg-boss data 形参为 object——基础类型 payload(cron 空任务等)按 JSON 语义透传\n const id = await b.send(name, payload as object, sendOptions);\n if (!id) {\n // dedupId 幂等投递:同键已存在(主键冲突 DO NOTHING → send 返回 null),\n // 返回已存在任务的确定性 id;无 dedupId 的失败投递才是异常\n if (opts?.dedupId) {\n return dedupIdToUuid(opts.dedupId);\n }\n throw new Error(`[faapi] pg-boss send failed for task \"${name}\"`);\n }\n return id;\n },\n\n async startWorker(name, workerOpts) {\n const b = await ensureBoss();\n await ensureQueue(b, name);\n const process: TaskDriverProcess = workerOpts.process;\n // 二次注册(reload 场景)先 offWork 旧 worker\n const existing = workerIds.get(name);\n if (existing) {\n await b.offWork(existing);\n }\n const workerId = await b.work<unknown>(\n name,\n { batchSize: workerOpts.concurrency, includeMetadata: true },\n async (jobs) => {\n for (const job of jobs) {\n // retryCount 从 0 起(首次执行为 0)→ faapi attempt 从 1 起\n const attempt = job.retryCount + 1;\n // 信号由驱动自管:stop 超时 abort(pg-boss 自身不提供执行中任务的取消能力)\n const controller = new AbortController();\n inflight.set(job.id, controller);\n try {\n await process({\n id: job.id,\n name,\n payload: job.data,\n attempt,\n signal: controller.signal,\n });\n } finally {\n inflight.delete(job.id);\n }\n }\n // handler 正常返回 = 本批全部完成;抛错 = 整批失败由 pg-boss 按 retryLimit 重试\n },\n );\n workerIds.set(name, workerId);\n },\n\n async stop(timeoutMs = 10_000) {\n stopped = true;\n // 等待超时到点 abort 在跑任务(run 监听 signal 尽快退出);等待结束后兜底 abort 残留\n const abortTimer = setTimeout(() => {\n for (const controller of inflight.values()) controller.abort();\n }, timeoutMs);\n try {\n const b = boss;\n bossStarting = null; // 停机后 ensureBoss 不再复用旧实例/旧建连 promise\n if (b) {\n for (const workerId of workerIds.values()) {\n await b.offWork(workerId).catch(() => {});\n }\n workerIds.clear();\n // graceful: 等 in-flight 任务完成;timeout 秒后强制处置\n await b.stop({ close: true, graceful: true, timeout: Math.floor(timeoutMs / 1000) });\n boss = null;\n }\n } finally {\n clearTimeout(abortTimer);\n for (const controller of inflight.values()) controller.abort();\n inflight.clear();\n }\n },\n\n async stopWorkers() {\n const b = boss;\n if (b) {\n for (const workerId of workerIds.values()) {\n await b.offWork(workerId).catch(() => {});\n }\n }\n workerIds.clear();\n },\n\n async cancel(name, id) {\n if (stopped) {\n throw new Error(\n '[faapi] Task queue is stopped and no longer accepts management operations',\n );\n }\n const b = await ensureBoss();\n await b.cancel(name, id);\n },\n\n async retry(name, id) {\n if (stopped) {\n throw new Error(\n '[faapi] Task queue is stopped and no longer accepts management operations',\n );\n }\n const b = await ensureBoss();\n // pg-boss v10 语义:resume 恢复 cancelled 任务;failed 任务无原生重试 API\n await b.resume(name, id);\n },\n // list 未实现:pg-boss v10 无批量列出 jobs 的公开 API(getJobById/getQueueSize 只能单查/计数),\n // 不硬造内部 SQL 依赖——语义层 listQueued 显式抛错,管理走 pg-boss 自身 API/SQL\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { TaskDriver, TaskDriverProcess } from '@faapi/faapi';\nimport PgBoss from 'pg-boss';\n\n/**\n * pg-boss 驱动选项\n *\n * `defaultExpireSeconds` 之外全部透传给 PgBoss 构造函数(常用:`connectionString`,\n * 其余见 pg-boss 文档 ConstructorOptions)。\n */\nexport type PgBossDriverOptions = PgBoss.ConstructorOptions & {\n /**\n * 未声明 `timeoutMs` 的任务的 expire_in 兜底秒数(默认 24 小时)。\n *\n * pg-boss 以 job 的 expire_in 硬限 handler 执行(DDL 默认 15 分钟):超时判失败\n * 重试,而任务还在后台跑 → 同一任务两份并发执行。声明了 `timeoutMs` 的任务由\n * 驱动按 `timeoutMs + graceMs + 60s` 缓冲给足;未声明的任务用本兜底。\n */\n defaultExpireSeconds?: number;\n};\n\n/**\n * faapi 任务队列 pg-boss 驱动(PostgreSQL 持久化队列)\n *\n * 与 faapi 主包 `config.task.driver: 'pgboss'` 配合使用:\n *\n * ```ts\n * // faapi.config.ts\n * export default {\n * task: {\n * driver: 'pgboss',\n * pgboss: { connectionString: 'postgres://localhost:5432/app' },\n * },\n * } satisfies FaapiConfig;\n * ```\n *\n * 语义映射(详见包根 README):\n * - `enqueue` → 幂等 ensureQueue + `boss.send(name, payload, { retryLimit, retryDelay, retryBackoff, expireInSeconds, startAfter })`\n * - `startWorker` → 幂等 ensureQueue + `boss.work(name, { batchSize: concurrency, includeMetadata: true }, handler)`;\n * 批内任务并发执行、逐任务 complete/fail 结算(失败不毒化同批)\n * - `stop` → `offWork` + `boss.stop({ close: true, graceful: true, timeout })` 整体与\n * deadline 竞速;deadline 到点 abort 在跑任务的 signal\n * - 重试 → pg-boss 侧执行(retryLimit + retryBackoff 指数退避)\n */\n/**\n * dedupId → 确定性 UUID:pg-boss 的 send 自定义 id 要求 UUID 格式(SQL 侧 cast),\n * 任意字符串键经 SHA-1 映射为合法 UUID——同键必同 UUID(幂等),不同键碰撞可忽略\n */\nfunction dedupIdToUuid(dedupId: string): string {\n const h = createHash('sha1').update(dedupId).digest('hex');\n return [\n h.slice(0, 8),\n h.slice(8, 12),\n `5${h.slice(13, 16)}`, // version 5\n `${((parseInt(h.slice(16, 17), 16) & 0x3) | 0x8).toString(16)}${h.slice(17, 20)}`, // variant\n h.slice(20, 32),\n ].join('-');\n}\n\n/** 未声明 timeoutMs 的任务 expire_in 兜底(秒) */\nconst DEFAULT_EXPIRE_SECONDS = 24 * 60 * 60;\n/** expire_in 在任务执行预算之外的缓冲(秒)——留出调度/网络抖动余量 */\nconst EXPIRE_BUFFER_SECONDS = 60;\n/** graceMs 未声明时的默认值(与主包 taskWorker 的取消宽限期默认一致) */\nconst DEFAULT_GRACE_MS = 5000;\n\nexport function createPgBossDriver(driverOptions: PgBossDriverOptions = {}): TaskDriver {\n const { defaultExpireSeconds, ...options } = driverOptions;\n let boss: PgBoss | null = null;\n let stopped = false;\n /** 已创建的 pg-boss worker id(stopWorkers 时 offWork) */\n const workerIds = new Map<string, string>();\n /** 在跑任务的取消控制器(stop 超时 abort——run 监听 signal 可尽快退出) */\n const inflight = new Map<string, AbortController>();\n /** 已建队列名缓存(每任务名每进程一次真实 createQueue,之后短路) */\n const ensuredQueues = new Set<string>();\n /** 并发首次投递同名任务的 in-flight 去重 */\n const ensuring = new Map<string, Promise<void>>();\n /** boss.start() in-flight 共享 promise(并发首调等待同一次建连,不出现半启动实例) */\n let bossStarting: Promise<PgBoss> | null = null;\n\n async function ensureBoss(): Promise<PgBoss> {\n if (boss) return boss;\n if (!bossStarting) {\n const starting = (async () => {\n const b = new PgBoss(options);\n // pg-boss 把连接池错误(PG 重启/网络闪断)与 worker 异常 emit 为 'error' 事件,\n // EventEmitter 无监听器时 emit 即抛 uncaughtException 崩进程——必须挂监听\n b.on('error', (err: unknown) => {\n console.error(\n '[faapi] pg-boss error:',\n err instanceof Error ? (err.stack ?? err.message) : err,\n );\n });\n // pg-boss 惰性连接:start 后才连库;驱动在首次 enqueue/startWorker 前建立。\n // start 完成前实例不外借——pre-open 的 executeSql 静默 no-op(返回 undefined),\n // 建连中并发调用会在半启动实例上跑 SQL\n await b.start();\n if (stopped) {\n // 停机竞态:start 期间 stop() 已执行——不回写实例(否则泄漏一个已 start、\n // 永不 stop 的 PgBoss),关闭后让调用方拿到失败\n await b.stop().catch(() => {});\n throw new Error('[faapi] Task queue is stopped and no longer accepts jobs');\n }\n boss = b;\n return b;\n })();\n bossStarting = starting;\n // start 失败清空 in-flight:下次调用重新建连(坏实例不入缓存);\n // 身份校验防止误清 stop() 之后新建的 promise\n void starting.catch(() => {\n if (bossStarting === starting) bossStarting = null;\n });\n }\n return bossStarting;\n }\n\n /**\n * pg-boss v10 不再隐式建队列(v9 行为):send() 的 INSERT JOIN queue 对未创建队列\n * 静默返回 null,work() 只注册进程内轮询器同样不建队列——投递/注册 worker 前先建队列。\n * create_queue plpgsql 幂等(INSERT ON CONFLICT DO NOTHING,已存在直接返回),\n * 重复/并发调用安全;失败不入缓存,下次投递重试。\n */\n async function ensureQueue(b: PgBoss, name: string): Promise<void> {\n if (ensuredQueues.has(name)) return;\n const pending = ensuring.get(name);\n if (pending) return pending;\n const promise = (async () => {\n await b.createQueue(name);\n ensuredQueues.add(name);\n })().finally(() => {\n ensuring.delete(name);\n });\n ensuring.set(name, promise);\n return promise;\n }\n\n return {\n async enqueue(name, payload, opts) {\n if (stopped) {\n throw new Error('[faapi] Task queue is stopped and no longer accepts jobs');\n }\n const b = await ensureBoss();\n await ensureQueue(b, name);\n // pg-boss 以 job 的 expire_in 硬限 handler 执行(DDL 默认 15 分钟):超时判失败\n // 重试,而任务还在后台跑 → 同一任务两份并发执行。按任务元信息给足执行预算:\n // timeoutMs + graceMs + 缓冲;未声明 timeoutMs 的任务用 defaultExpireSeconds 兜底\n const expireInSeconds = opts?.timeoutMs\n ? Math.ceil((opts.timeoutMs + (opts.graceMs ?? DEFAULT_GRACE_MS)) / 1000) +\n EXPIRE_BUFFER_SECONDS\n : (defaultExpireSeconds ?? DEFAULT_EXPIRE_SECONDS);\n const sendOptions: PgBoss.SendOptions = {\n retryLimit: opts?.retries ?? 0,\n retryDelay: 1, // 秒;配合 retryBackoff 指数退避\n retryBackoff: true,\n expireInSeconds,\n ...(opts?.dedupId ? { id: dedupIdToUuid(opts.dedupId) } : {}),\n ...(opts?.delayMs ? { startAfter: new Date(Date.now() + opts.delayMs) } : {}),\n };\n // pg-boss data 形参为 object——基础类型 payload(cron 空任务等)按 JSON 语义透传\n const id = await b.send(name, payload as object, sendOptions);\n if (!id) {\n // dedupId 幂等投递:同键已存在(主键冲突 DO NOTHING → send 返回 null),\n // 返回已存在任务的确定性 id;无 dedupId 的失败投递才是异常\n if (opts?.dedupId) {\n return dedupIdToUuid(opts.dedupId);\n }\n throw new Error(`[faapi] pg-boss send failed for task \"${name}\"`);\n }\n return id;\n },\n\n async startWorker(name, workerOpts) {\n const b = await ensureBoss();\n await ensureQueue(b, name);\n const process: TaskDriverProcess = workerOpts.process;\n // 二次注册(reload 场景)先 offWork 旧 worker\n const existing = workerIds.get(name);\n if (existing) {\n await b.offWork(existing);\n }\n const workerId = await b.work<unknown>(\n name,\n { batchSize: workerOpts.concurrency, includeMetadata: true },\n async (jobs) => {\n // 批内并发执行 + 逐任务结算:complete/fail 按单 job id 调用(pg-boss 的结算\n // SQL 带 state 守卫——complete 仅 active、fail 仅 state<completed,handler\n // 正常返回后 onFetch 的批量 complete 对已结算任务是 no-op)。单个任务失败只\n // 消耗自己的重试额度,不毒化同批其他任务;concurrency 语义与 BullMQ 驱动一致\n await Promise.all(\n jobs.map(async (job) => {\n // retryCount 从 0 起(首次执行为 0)→ faapi attempt 从 1 起\n const attempt = job.retryCount + 1;\n // 信号由驱动自管:stop 超时 abort(pg-boss 自身不提供执行中任务的取消能力)\n const controller = new AbortController();\n inflight.set(job.id, controller);\n try {\n try {\n await process({\n id: job.id,\n name,\n payload: job.data,\n attempt,\n signal: controller.signal,\n });\n } catch (err) {\n // fail 的 data 存 jsonb:传可序列化的错误摘要(原始 Error 序列化为 {} 丢信息)\n const reason =\n err instanceof Error\n ? { name: err.name, message: err.message }\n : { value: String(err) };\n try {\n await b.fail(name, job.id, reason);\n } catch (failErr) {\n // fail 结算失败:任务留在 active,由 pg-boss 的 expire_in 兜底结算\n console.error(\n `[faapi] pg-boss fail() failed for job ${job.id} of \"${name}\":`,\n failErr,\n );\n }\n return;\n }\n try {\n await b.complete(name, job.id);\n } catch (completeErr) {\n // complete 结算失败:任务留在 active,由 expire_in 兜底(不标失败——\n // 任务已成功执行,标失败会触发重试导致重复执行)\n console.error(\n `[faapi] pg-boss complete() failed for job ${job.id} of \"${name}\":`,\n completeErr,\n );\n }\n } finally {\n inflight.delete(job.id);\n }\n }),\n );\n },\n );\n workerIds.set(name, workerId);\n },\n\n async stop(timeoutMs = 10_000) {\n stopped = true;\n const b = boss;\n bossStarting = null; // 停机后 ensureBoss 不再复用旧实例/旧建连 promise\n if (!b) return;\n\n let deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n let aborted = false;\n const deadline = new Promise<void>((resolve) => {\n deadlineTimer = setTimeout(() => {\n aborted = true;\n // deadline 到点 abort 在跑任务:监听 signal 的任务尽快退出;仍不退出的由\n // pg-boss 的 expire_in 兜底结算(resolveWithinSeconds 判超时失败并重试)\n for (const controller of inflight.values()) controller.abort();\n resolve();\n }, timeoutMs);\n });\n try {\n // offWork 等待在跑 handler 结束——卡死的任务会让它永久悬挂,必须与 deadline\n // 竞速保证 stop() 有界返回\n await Promise.race([\n (async () => {\n for (const workerId of workerIds.values()) {\n await b.offWork(workerId).catch(() => {});\n }\n })(),\n deadline,\n ]);\n workerIds.clear();\n // graceful 收尾:pg-boss stop 的 timeout 单位是毫秒(其实现以 Date.now() 差值\n // 比较,超时 failWip 处置在跑任务)。整体与 deadline 竞速,超时即放弃等待\n await Promise.race([b.stop({ close: true, graceful: true, timeout: timeoutMs }), deadline]);\n boss = null;\n } finally {\n clearTimeout(deadlineTimer);\n if (!aborted) {\n for (const controller of inflight.values()) controller.abort();\n }\n inflight.clear();\n }\n },\n\n async stopWorkers() {\n const b = boss;\n if (b) {\n for (const workerId of workerIds.values()) {\n await b.offWork(workerId).catch(() => {});\n }\n }\n workerIds.clear();\n },\n\n async cancel(name, id) {\n if (stopped) {\n throw new Error(\n '[faapi] Task queue is stopped and no longer accepts management operations',\n );\n }\n const b = await ensureBoss();\n await b.cancel(name, id);\n },\n\n async retry(name, id) {\n if (stopped) {\n throw new Error(\n '[faapi] Task queue is stopped and no longer accepts management operations',\n );\n }\n const b = await ensureBoss();\n // pg-boss v10 语义:resume 恢复 cancelled 任务;failed 任务无原生重试 API\n await b.resume(name, id);\n },\n // list 未实现:pg-boss v10 无批量列出 jobs 的公开 API(getJobById/getQueueSize 只能单查/计数),\n // 不硬造内部 SQL 依赖——语义层 listQueued 显式抛错,管理走 pg-boss 自身 API/SQL\n };\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAE3B,OAAO,YAAY;AA8CnB,SAAS,cAAc,SAAyB;AAC9C,QAAM,IAAI,WAAW,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AACzD,SAAO;AAAA,IACL,EAAE,MAAM,GAAG,CAAC;AAAA,IACZ,EAAE,MAAM,GAAG,EAAE;AAAA,IACb,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA;AAAA,IACnB,IAAK,SAAS,EAAE,MAAM,IAAI,EAAE,GAAG,EAAE,IAAI,IAAO,GAAK,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC;AAAA;AAAA,IAC/E,EAAE,MAAM,IAAI,EAAE;AAAA,EAChB,EAAE,KAAK,GAAG;AACZ;AAGA,IAAM,yBAAyB,KAAK,KAAK;AAEzC,IAAM,wBAAwB;AAE9B,IAAM,mBAAmB;AAElB,SAAS,mBAAmB,gBAAqC,CAAC,GAAe;AACtF,QAAM,EAAE,sBAAsB,GAAG,QAAQ,IAAI;AAC7C,MAAI,OAAsB;AAC1B,MAAI,UAAU;AAEd,QAAM,YAAY,oBAAI,IAAoB;AAE1C,QAAM,WAAW,oBAAI,IAA6B;AAElD,QAAM,gBAAgB,oBAAI,IAAY;AAEtC,QAAM,WAAW,oBAAI,IAA2B;AAEhD,MAAI,eAAuC;AAE3C,iBAAe,aAA8B;AAC3C,QAAI,KAAM,QAAO;AACjB,QAAI,CAAC,cAAc;AACjB,YAAM,YAAY,YAAY;AAC5B,cAAM,IAAI,IAAI,OAAO,OAAO;AAG5B,UAAE,GAAG,SAAS,CAAC,QAAiB;AAC9B,kBAAQ;AAAA,YACN;AAAA,YACA,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW;AAAA,UACtD;AAAA,QACF,CAAC;AAID,cAAM,EAAE,MAAM;AACd,YAAI,SAAS;AAGX,gBAAM,EAAE,KAAK,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAC7B,gBAAM,IAAI,MAAM,0DAA0D;AAAA,QAC5E;AACA,eAAO;AACP,eAAO;AAAA,MACT,GAAG;AACH,qBAAe;AAGf,WAAK,SAAS,MAAM,MAAM;AACxB,YAAI,iBAAiB,SAAU,gBAAe;AAAA,MAChD,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAQA,iBAAe,YAAY,GAAW,MAA6B;AACjE,QAAI,cAAc,IAAI,IAAI,EAAG;AAC7B,UAAM,UAAU,SAAS,IAAI,IAAI;AACjC,QAAI,QAAS,QAAO;AACpB,UAAM,WAAW,YAAY;AAC3B,YAAM,EAAE,YAAY,IAAI;AACxB,oBAAc,IAAI,IAAI;AAAA,IACxB,GAAG,EAAE,QAAQ,MAAM;AACjB,eAAS,OAAO,IAAI;AAAA,IACtB,CAAC;AACD,aAAS,IAAI,MAAM,OAAO;AAC1B,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,SAAS,MAAM;AACjC,UAAI,SAAS;AACX,cAAM,IAAI,MAAM,0DAA0D;AAAA,MAC5E;AACA,YAAM,IAAI,MAAM,WAAW;AAC3B,YAAM,YAAY,GAAG,IAAI;AAIzB,YAAM,kBAAkB,MAAM,YAC1B,KAAK,MAAM,KAAK,aAAa,KAAK,WAAW,qBAAqB,GAAI,IACtE,wBACC,wBAAwB;AAC7B,YAAM,cAAkC;AAAA,QACtC,YAAY,MAAM,WAAW;AAAA,QAC7B,YAAY;AAAA;AAAA,QACZ,cAAc;AAAA,QACd;AAAA,QACA,GAAI,MAAM,UAAU,EAAE,IAAI,cAAc,KAAK,OAAO,EAAE,IAAI,CAAC;AAAA,QAC3D,GAAI,MAAM,UAAU,EAAE,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,OAAO,EAAE,IAAI,CAAC;AAAA,MAC7E;AAEA,YAAM,KAAK,MAAM,EAAE,KAAK,MAAM,SAAmB,WAAW;AAC5D,UAAI,CAAC,IAAI;AAGP,YAAI,MAAM,SAAS;AACjB,iBAAO,cAAc,KAAK,OAAO;AAAA,QACnC;AACA,cAAM,IAAI,MAAM,yCAAyC,IAAI,GAAG;AAAA,MAClE;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,YAAY,MAAM,YAAY;AAClC,YAAM,IAAI,MAAM,WAAW;AAC3B,YAAM,YAAY,GAAG,IAAI;AACzB,YAAM,UAA6B,WAAW;AAE9C,YAAM,WAAW,UAAU,IAAI,IAAI;AACnC,UAAI,UAAU;AACZ,cAAM,EAAE,QAAQ,QAAQ;AAAA,MAC1B;AACA,YAAM,WAAW,MAAM,EAAE;AAAA,QACvB;AAAA,QACA,EAAE,WAAW,WAAW,aAAa,iBAAiB,KAAK;AAAA,QAC3D,OAAO,SAAS;AAKd,gBAAM,QAAQ;AAAA,YACZ,KAAK,IAAI,OAAO,QAAQ;AAEtB,oBAAM,UAAU,IAAI,aAAa;AAEjC,oBAAM,aAAa,IAAI,gBAAgB;AACvC,uBAAS,IAAI,IAAI,IAAI,UAAU;AAC/B,kBAAI;AACF,oBAAI;AACF,wBAAM,QAAQ;AAAA,oBACZ,IAAI,IAAI;AAAA,oBACR;AAAA,oBACA,SAAS,IAAI;AAAA,oBACb;AAAA,oBACA,QAAQ,WAAW;AAAA,kBACrB,CAAC;AAAA,gBACH,SAAS,KAAK;AAEZ,wBAAM,SACJ,eAAe,QACX,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,QAAQ,IACvC,EAAE,OAAO,OAAO,GAAG,EAAE;AAC3B,sBAAI;AACF,0BAAM,EAAE,KAAK,MAAM,IAAI,IAAI,MAAM;AAAA,kBACnC,SAAS,SAAS;AAEhB,4BAAQ;AAAA,sBACN,yCAAyC,IAAI,EAAE,QAAQ,IAAI;AAAA,sBAC3D;AAAA,oBACF;AAAA,kBACF;AACA;AAAA,gBACF;AACA,oBAAI;AACF,wBAAM,EAAE,SAAS,MAAM,IAAI,EAAE;AAAA,gBAC/B,SAAS,aAAa;AAGpB,0BAAQ;AAAA,oBACN,6CAA6C,IAAI,EAAE,QAAQ,IAAI;AAAA,oBAC/D;AAAA,kBACF;AAAA,gBACF;AAAA,cACF,UAAE;AACA,yBAAS,OAAO,IAAI,EAAE;AAAA,cACxB;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AACA,gBAAU,IAAI,MAAM,QAAQ;AAAA,IAC9B;AAAA,IAEA,MAAM,KAAK,YAAY,KAAQ;AAC7B,gBAAU;AACV,YAAM,IAAI;AACV,qBAAe;AACf,UAAI,CAAC,EAAG;AAER,UAAI;AACJ,UAAI,UAAU;AACd,YAAM,WAAW,IAAI,QAAc,CAAC,YAAY;AAC9C,wBAAgB,WAAW,MAAM;AAC/B,oBAAU;AAGV,qBAAW,cAAc,SAAS,OAAO,EAAG,YAAW,MAAM;AAC7D,kBAAQ;AAAA,QACV,GAAG,SAAS;AAAA,MACd,CAAC;AACD,UAAI;AAGF,cAAM,QAAQ,KAAK;AAAA,WAChB,YAAY;AACX,uBAAW,YAAY,UAAU,OAAO,GAAG;AACzC,oBAAM,EAAE,QAAQ,QAAQ,EAAE,MAAM,MAAM;AAAA,cAAC,CAAC;AAAA,YAC1C;AAAA,UACF,GAAG;AAAA,UACH;AAAA,QACF,CAAC;AACD,kBAAU,MAAM;AAGhB,cAAM,QAAQ,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,MAAM,UAAU,MAAM,SAAS,UAAU,CAAC,GAAG,QAAQ,CAAC;AAC1F,eAAO;AAAA,MACT,UAAE;AACA,qBAAa,aAAa;AAC1B,YAAI,CAAC,SAAS;AACZ,qBAAW,cAAc,SAAS,OAAO,EAAG,YAAW,MAAM;AAAA,QAC/D;AACA,iBAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,IAEA,MAAM,cAAc;AAClB,YAAM,IAAI;AACV,UAAI,GAAG;AACL,mBAAW,YAAY,UAAU,OAAO,GAAG;AACzC,gBAAM,EAAE,QAAQ,QAAQ,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1C;AAAA,MACF;AACA,gBAAU,MAAM;AAAA,IAClB;AAAA,IAEA,MAAM,OAAO,MAAM,IAAI;AACrB,UAAI,SAAS;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,IAAI,MAAM,WAAW;AAC3B,YAAM,EAAE,OAAO,MAAM,EAAE;AAAA,IACzB;AAAA,IAEA,MAAM,MAAM,MAAM,IAAI;AACpB,UAAI,SAAS;AACX,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,YAAM,IAAI,MAAM,WAAW;AAE3B,YAAM,EAAE,OAAO,MAAM,EAAE;AAAA,IACzB;AAAA;AAAA;AAAA,EAGF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faapi/task-pgboss",
3
- "version": "6.16.0",
3
+ "version": "6.18.0",
4
4
  "description": "pg-boss task queue driver for faapi — persistent queue on PostgreSQL",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,11 +24,11 @@
24
24
  "tsup": "^8.4.0",
25
25
  "typescript": "^5.7.0",
26
26
  "vitest": "^4.1.11",
27
- "@faapi/faapi": "6.16.0"
27
+ "@faapi/faapi": "6.18.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "pg-boss": "^10.0.0",
31
- "@faapi/faapi": "^6.16.0"
31
+ "@faapi/faapi": "^6.18.0"
32
32
  },
33
33
  "license": "MIT",
34
34
  "repository": {