@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
package/dist/cli/index.js
CHANGED
|
@@ -9548,6 +9548,53 @@ async function runTaskInWorker(options) {
|
|
|
9548
9548
|
worker.on(
|
|
9549
9549
|
"message",
|
|
9550
9550
|
(msg) => {
|
|
9551
|
+
if (msg?.type === "tasks-call") {
|
|
9552
|
+
const { seq, method, args } = msg;
|
|
9553
|
+
const reply = (body) => {
|
|
9554
|
+
try {
|
|
9555
|
+
worker.postMessage({ type: "tasks-result", seq, ...body });
|
|
9556
|
+
} catch (err) {
|
|
9557
|
+
try {
|
|
9558
|
+
worker.postMessage({
|
|
9559
|
+
type: "tasks-result",
|
|
9560
|
+
seq,
|
|
9561
|
+
ok: false,
|
|
9562
|
+
error: {
|
|
9563
|
+
name: "Error",
|
|
9564
|
+
message: `taskCtx.tasks.${method} result is not cloneable: ${String(err)}`,
|
|
9565
|
+
stack: void 0,
|
|
9566
|
+
props: {}
|
|
9567
|
+
}
|
|
9568
|
+
});
|
|
9569
|
+
} catch {
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
};
|
|
9573
|
+
if (!options.onTasksCall) {
|
|
9574
|
+
reply({
|
|
9575
|
+
ok: false,
|
|
9576
|
+
error: {
|
|
9577
|
+
name: "Error",
|
|
9578
|
+
message: `taskCtx.tasks.${method} is unavailable: no host handler was provided for this execution`,
|
|
9579
|
+
stack: void 0,
|
|
9580
|
+
props: {}
|
|
9581
|
+
}
|
|
9582
|
+
});
|
|
9583
|
+
return;
|
|
9584
|
+
}
|
|
9585
|
+
options.onTasksCall(method, args ?? []).then((value) => reply({ ok: true, value })).catch((err) => {
|
|
9586
|
+
reply({
|
|
9587
|
+
ok: false,
|
|
9588
|
+
error: {
|
|
9589
|
+
name: err instanceof Error ? err.name : "Error",
|
|
9590
|
+
message: err instanceof Error ? err.message : String(err),
|
|
9591
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
9592
|
+
props: {}
|
|
9593
|
+
}
|
|
9594
|
+
});
|
|
9595
|
+
});
|
|
9596
|
+
return;
|
|
9597
|
+
}
|
|
9551
9598
|
if (phase === "grace") {
|
|
9552
9599
|
if (msg?.type === "done") {
|
|
9553
9600
|
finish(
|
|
@@ -9669,6 +9716,54 @@ function createTaskQueue(deps) {
|
|
|
9669
9716
|
throw new Error(`[faapi] Unknown task "${name}". Registered tasks: ${known}`);
|
|
9670
9717
|
}
|
|
9671
9718
|
}
|
|
9719
|
+
function assertGroupSupport() {
|
|
9720
|
+
if (!driver.groups) {
|
|
9721
|
+
throw new Error(
|
|
9722
|
+
"[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."
|
|
9723
|
+
);
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
async function maybeFireCompletion(snap) {
|
|
9727
|
+
if (!driver.groups) return;
|
|
9728
|
+
if (!snap.onComplete || snap.completionEnqueued || snap.settled < snap.total) return;
|
|
9729
|
+
const summary = {
|
|
9730
|
+
groupId: snap.groupId,
|
|
9731
|
+
task: snap.task,
|
|
9732
|
+
total: snap.total,
|
|
9733
|
+
done: snap.done,
|
|
9734
|
+
failed: snap.failed,
|
|
9735
|
+
cancelled: snap.cancelled,
|
|
9736
|
+
settled: snap.settled
|
|
9737
|
+
};
|
|
9738
|
+
try {
|
|
9739
|
+
await enqueueCore(snap.onComplete, summary, {
|
|
9740
|
+
dedupId: GROUP_COMPLETION_DEDUP(snap.groupId)
|
|
9741
|
+
});
|
|
9742
|
+
await driver.groups.markCompletionEnqueued(snap.groupId);
|
|
9743
|
+
} catch (err) {
|
|
9744
|
+
console.error(
|
|
9745
|
+
`[faapi] task group "${snap.groupId}" completion enqueue failed (group is settled; re-invoke enqueueGroup with the same groupId to re-arm the callback):`,
|
|
9746
|
+
err
|
|
9747
|
+
);
|
|
9748
|
+
}
|
|
9749
|
+
}
|
|
9750
|
+
async function settleGroup(record, outcome, willRetry) {
|
|
9751
|
+
const groupId = record.groupId;
|
|
9752
|
+
if (!groupId || !driver.groups) return;
|
|
9753
|
+
if (outcome !== "done" && willRetry) return;
|
|
9754
|
+
try {
|
|
9755
|
+
const snap = await driver.groups.settle(groupId, record.id, outcome);
|
|
9756
|
+
if (outcome === "failed" && snap.onFailure === "fail-fast") {
|
|
9757
|
+
await maybeFireCompletion(await driver.groups.cancelRemaining(groupId));
|
|
9758
|
+
}
|
|
9759
|
+
await maybeFireCompletion(snap);
|
|
9760
|
+
} catch (err) {
|
|
9761
|
+
console.error(
|
|
9762
|
+
`[faapi] task group accounting failed for "${groupId}" (job ${record.id}, outcome ${outcome}):`,
|
|
9763
|
+
err
|
|
9764
|
+
);
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9672
9767
|
function snapshotRegistries() {
|
|
9673
9768
|
return {
|
|
9674
9769
|
agents: registriesView.agent.listAgents().map((core) => registriesView.agent.getAgentEntry(core.name)).filter((entry) => entry !== void 0),
|
|
@@ -9707,11 +9802,13 @@ function createTaskQueue(deps) {
|
|
|
9707
9802
|
attempts: 0,
|
|
9708
9803
|
createdAt: Date.now()
|
|
9709
9804
|
};
|
|
9805
|
+
if (job.groupId !== void 0) record.groupId = job.groupId;
|
|
9710
9806
|
record.attempts = job.attempt;
|
|
9711
9807
|
transitionStatus(record, "running");
|
|
9712
9808
|
delete record.progress;
|
|
9713
9809
|
record.error = void 0;
|
|
9714
9810
|
records.set(job.id, record);
|
|
9811
|
+
const willRetry = job.attempt <= (meta.retries ?? 0);
|
|
9715
9812
|
try {
|
|
9716
9813
|
let result;
|
|
9717
9814
|
if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
|
|
@@ -9741,7 +9838,16 @@ function createTaskQueue(deps) {
|
|
|
9741
9838
|
onProgress: (value) => {
|
|
9742
9839
|
if (record.status === "running") record.progress = value;
|
|
9743
9840
|
},
|
|
9744
|
-
onLog: writeLogEntry
|
|
9841
|
+
onLog: writeLogEntry,
|
|
9842
|
+
// 隔离路径的 taskCtx.tasks 为 postMessage RPC 代理——宿主侧按方法名
|
|
9843
|
+
// 分派到队列本体(与 ctx.tasks / app.tasks 同一实例),结果/错误按 seq 回传
|
|
9844
|
+
onTasksCall: async (method, args) => {
|
|
9845
|
+
if (!TASK_CLIENT_METHODS.has(method)) {
|
|
9846
|
+
throw new Error(`[faapi] Unknown task client method "${method}"`);
|
|
9847
|
+
}
|
|
9848
|
+
const fn = queue[method];
|
|
9849
|
+
return fn.apply(queue, args);
|
|
9850
|
+
}
|
|
9745
9851
|
});
|
|
9746
9852
|
} else {
|
|
9747
9853
|
let mod = moduleCache2.get(job.name);
|
|
@@ -9754,6 +9860,8 @@ function createTaskQueue(deps) {
|
|
|
9754
9860
|
}
|
|
9755
9861
|
const taskCtx = {
|
|
9756
9862
|
signal: job.signal,
|
|
9863
|
+
// 活引用:与 ctx.tasks / app.tasks 同一 app 实例队列
|
|
9864
|
+
tasks: queue,
|
|
9757
9865
|
config: deps.config,
|
|
9758
9866
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
9759
9867
|
registries: registriesView,
|
|
@@ -9771,19 +9879,22 @@ function createTaskQueue(deps) {
|
|
|
9771
9879
|
transitionStatus(record, "done");
|
|
9772
9880
|
record.result = result;
|
|
9773
9881
|
evictFinishedRecords();
|
|
9882
|
+
await settleGroup(record, "done", willRetry);
|
|
9774
9883
|
return result;
|
|
9775
9884
|
} catch (err) {
|
|
9776
9885
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
9777
|
-
|
|
9886
|
+
const outcome = cancelled ? "cancelled" : "failed";
|
|
9887
|
+
transitionStatus(record, outcome);
|
|
9778
9888
|
record.error = err instanceof Error ? err.message : String(err);
|
|
9779
9889
|
evictFinishedRecords();
|
|
9890
|
+
await settleGroup(record, outcome, willRetry);
|
|
9780
9891
|
if (deps.onFailed) {
|
|
9781
9892
|
void Promise.resolve().then(
|
|
9782
9893
|
() => deps.onFailed({
|
|
9783
9894
|
task: job.name,
|
|
9784
9895
|
jobId: job.id,
|
|
9785
9896
|
attempt: job.attempt,
|
|
9786
|
-
willRetry
|
|
9897
|
+
willRetry,
|
|
9787
9898
|
cancelled,
|
|
9788
9899
|
error: record.error
|
|
9789
9900
|
})
|
|
@@ -9802,35 +9913,86 @@ function createTaskQueue(deps) {
|
|
|
9802
9913
|
});
|
|
9803
9914
|
}
|
|
9804
9915
|
}
|
|
9916
|
+
async function enqueueCore(name, payload, opts) {
|
|
9917
|
+
if (stopped) {
|
|
9918
|
+
throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
|
|
9919
|
+
}
|
|
9920
|
+
assertKnownTask(name);
|
|
9921
|
+
const data = await validatePayload(name, payload);
|
|
9922
|
+
const meta = registry.get(name);
|
|
9923
|
+
const id = await driver.enqueue(name, data, {
|
|
9924
|
+
delayMs: opts?.delayMs,
|
|
9925
|
+
retries: meta.retries ?? 0,
|
|
9926
|
+
dedupId: opts?.dedupId,
|
|
9927
|
+
// 执行硬限预算透传驱动(pgboss 映射 expireInSeconds):未声明 timeoutMs 时
|
|
9928
|
+
// 两字段为 undefined,驱动用自身兜底
|
|
9929
|
+
timeoutMs: meta.timeoutMs,
|
|
9930
|
+
graceMs: meta.graceMs,
|
|
9931
|
+
...opts?.groupId !== void 0 ? { groupId: opts.groupId } : {}
|
|
9932
|
+
});
|
|
9933
|
+
if (!records.has(id)) {
|
|
9934
|
+
records.set(id, {
|
|
9935
|
+
id,
|
|
9936
|
+
name,
|
|
9937
|
+
payload: data,
|
|
9938
|
+
status: "pending",
|
|
9939
|
+
attempts: 0,
|
|
9940
|
+
...opts?.groupId !== void 0 ? { groupId: opts.groupId } : {},
|
|
9941
|
+
createdAt: Date.now(),
|
|
9942
|
+
...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
|
|
9943
|
+
});
|
|
9944
|
+
}
|
|
9945
|
+
return id;
|
|
9946
|
+
}
|
|
9805
9947
|
const queue = {
|
|
9806
9948
|
async enqueue(name, payload = {}, opts) {
|
|
9949
|
+
const id = await enqueueCore(name, payload, {
|
|
9950
|
+
delayMs: opts?.delayMs,
|
|
9951
|
+
dedupId: opts?.dedupId
|
|
9952
|
+
});
|
|
9953
|
+
return { id };
|
|
9954
|
+
},
|
|
9955
|
+
async enqueueGroup(name, payloads, opts) {
|
|
9807
9956
|
if (stopped) {
|
|
9808
9957
|
throw new Error("[faapi] Task queue is stopped and no longer accepts jobs");
|
|
9809
9958
|
}
|
|
9959
|
+
assertGroupSupport();
|
|
9810
9960
|
assertKnownTask(name);
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9961
|
+
if (!Array.isArray(payloads) || payloads.length === 0) {
|
|
9962
|
+
throw new Error(
|
|
9963
|
+
"[faapi] enqueueGroup requires a non-empty payloads array \u2014 a group without members can never settle."
|
|
9964
|
+
);
|
|
9965
|
+
}
|
|
9966
|
+
const onComplete = opts?.onComplete;
|
|
9967
|
+
if (onComplete !== void 0) assertKnownTask(onComplete);
|
|
9968
|
+
const onFailure = opts?.onFailure ?? "run-to-completion";
|
|
9969
|
+
const validated = [];
|
|
9970
|
+
for (const p of payloads) {
|
|
9971
|
+
validated.push(await validatePayload(name, p));
|
|
9972
|
+
}
|
|
9973
|
+
const groupId = opts?.groupId ?? crypto.randomUUID();
|
|
9974
|
+
await driver.groups.create({
|
|
9975
|
+
id: groupId,
|
|
9976
|
+
task: name,
|
|
9977
|
+
total: validated.length,
|
|
9978
|
+
...onComplete !== void 0 ? { onComplete } : {},
|
|
9979
|
+
onFailure
|
|
9821
9980
|
});
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
attempts: 0,
|
|
9829
|
-
createdAt: Date.now(),
|
|
9830
|
-
...opts?.delayMs ? { runAt: Date.now() + opts.delayMs } : {}
|
|
9981
|
+
const jobs = [];
|
|
9982
|
+
for (let i = 0; i < validated.length; i++) {
|
|
9983
|
+
const id = await enqueueCore(name, validated[i], {
|
|
9984
|
+
delayMs: opts?.delayMs,
|
|
9985
|
+
dedupId: GROUP_MEMBER_DEDUP(groupId, i + 1),
|
|
9986
|
+
groupId
|
|
9831
9987
|
});
|
|
9988
|
+
jobs.push({ id });
|
|
9832
9989
|
}
|
|
9833
|
-
|
|
9990
|
+
await maybeFireCompletion(await driver.groups.get(groupId));
|
|
9991
|
+
return { groupId, jobs };
|
|
9992
|
+
},
|
|
9993
|
+
async getGroup(groupId) {
|
|
9994
|
+
assertGroupSupport();
|
|
9995
|
+
return driver.groups.get(groupId);
|
|
9834
9996
|
},
|
|
9835
9997
|
list(name) {
|
|
9836
9998
|
const snapshot = [];
|
|
@@ -9857,6 +10019,7 @@ function createTaskQueue(deps) {
|
|
|
9857
10019
|
status: record.status,
|
|
9858
10020
|
attempts: record.attempts,
|
|
9859
10021
|
createdAt: record.createdAt,
|
|
10022
|
+
...record.groupId !== void 0 ? { groupId: record.groupId } : {},
|
|
9860
10023
|
...record.result !== void 0 ? { result: record.result } : {},
|
|
9861
10024
|
...record.error !== void 0 ? { error: record.error } : {},
|
|
9862
10025
|
...record.runAt !== void 0 ? { runAt: record.runAt } : {}
|
|
@@ -9876,8 +10039,11 @@ function createTaskQueue(deps) {
|
|
|
9876
10039
|
}
|
|
9877
10040
|
await driver.cancel(name, id);
|
|
9878
10041
|
const record = records.get(id);
|
|
9879
|
-
if (record)
|
|
9880
|
-
|
|
10042
|
+
if (record) {
|
|
10043
|
+
transitionStatus(record, "cancelled");
|
|
10044
|
+
evictFinishedRecords();
|
|
10045
|
+
await settleGroup(record, "cancelled", false);
|
|
10046
|
+
}
|
|
9881
10047
|
},
|
|
9882
10048
|
async retry(name, id) {
|
|
9883
10049
|
if (!driver.retry) {
|
|
@@ -9887,7 +10053,18 @@ function createTaskQueue(deps) {
|
|
|
9887
10053
|
}
|
|
9888
10054
|
await driver.retry(name, id);
|
|
9889
10055
|
const record = records.get(id);
|
|
9890
|
-
if (record)
|
|
10056
|
+
if (record) {
|
|
10057
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
10058
|
+
transitionStatus(record, "pending");
|
|
10059
|
+
if (wasTerminal && record.groupId && driver.groups) {
|
|
10060
|
+
await driver.groups.unsettle(record.groupId, record.id).catch((err) => {
|
|
10061
|
+
console.error(
|
|
10062
|
+
`[faapi] task group unsettle failed for "${record.groupId}" (job ${record.id}):`,
|
|
10063
|
+
err
|
|
10064
|
+
);
|
|
10065
|
+
});
|
|
10066
|
+
}
|
|
10067
|
+
}
|
|
9891
10068
|
},
|
|
9892
10069
|
async start() {
|
|
9893
10070
|
if (stopped) {
|
|
@@ -9914,7 +10091,7 @@ function createTaskQueue(deps) {
|
|
|
9914
10091
|
};
|
|
9915
10092
|
return queue;
|
|
9916
10093
|
}
|
|
9917
|
-
var TERMINAL_STATUSES, MAX_FINISHED_RECORDS;
|
|
10094
|
+
var TERMINAL_STATUSES, GROUP_MEMBER_DEDUP, GROUP_COMPLETION_DEDUP, TASK_CLIENT_METHODS, MAX_FINISHED_RECORDS;
|
|
9918
10095
|
var init_taskQueue = __esm({
|
|
9919
10096
|
"src/task/taskQueue.ts"() {
|
|
9920
10097
|
"use strict";
|
|
@@ -9923,6 +10100,17 @@ var init_taskQueue = __esm({
|
|
|
9923
10100
|
init_registries();
|
|
9924
10101
|
init_logger();
|
|
9925
10102
|
TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
10103
|
+
GROUP_MEMBER_DEDUP = (groupId, index) => `faapi-group:${groupId}:${index}`;
|
|
10104
|
+
GROUP_COMPLETION_DEDUP = (groupId) => `faapi-group:${groupId}:complete`;
|
|
10105
|
+
TASK_CLIENT_METHODS = /* @__PURE__ */ new Set([
|
|
10106
|
+
"enqueue",
|
|
10107
|
+
"enqueueGroup",
|
|
10108
|
+
"list",
|
|
10109
|
+
"listQueued",
|
|
10110
|
+
"cancel",
|
|
10111
|
+
"retry",
|
|
10112
|
+
"getGroup"
|
|
10113
|
+
]);
|
|
9926
10114
|
MAX_FINISHED_RECORDS = 1e3;
|
|
9927
10115
|
}
|
|
9928
10116
|
});
|