@faapi/faapi 6.6.0 → 6.7.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 +143 -41
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +140 -39
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-q9OXB--o.d.ts → routeTypes-CsL7xtqu.d.ts} +38 -5
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3376,6 +3376,9 @@ var init_generateAgentArtifacts = __esm({
|
|
|
3376
3376
|
import fg4 from "fast-glob";
|
|
3377
3377
|
import path16 from "path";
|
|
3378
3378
|
import fs12 from "fs";
|
|
3379
|
+
function parseNumericLiteral(raw) {
|
|
3380
|
+
return Number(raw.replace(/_/g, ""));
|
|
3381
|
+
}
|
|
3379
3382
|
function filePathToTaskName(filePath) {
|
|
3380
3383
|
const normalized = filePath.replace(/\\/g, "/");
|
|
3381
3384
|
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
@@ -3413,24 +3416,37 @@ async function scanTasks(rootDir, patterns) {
|
|
|
3413
3416
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
3414
3417
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
3415
3418
|
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
3419
|
+
const graceMs = GRACE_MS_RE.exec(source)?.[1];
|
|
3416
3420
|
if (cron !== void 0) manifest.cron = cron;
|
|
3417
|
-
if (concurrency !== void 0) manifest.concurrency =
|
|
3418
|
-
if (retries !== void 0) manifest.retries =
|
|
3419
|
-
if (timeoutMs !== void 0)
|
|
3421
|
+
if (concurrency !== void 0) manifest.concurrency = parseNumericLiteral(concurrency);
|
|
3422
|
+
if (retries !== void 0) manifest.retries = parseNumericLiteral(retries);
|
|
3423
|
+
if (timeoutMs !== void 0) {
|
|
3424
|
+
const value = parseNumericLiteral(timeoutMs);
|
|
3425
|
+
if (value < MIN_ISOLATED_TIMEOUT_MS) {
|
|
3426
|
+
throw new Error(
|
|
3427
|
+
`[faapi] Task "${name}" timeoutMs ${value} is below the minimum ${MIN_ISOLATED_TIMEOUT_MS}ms (1 minute). Declaring a timeout means this is a long-running task that needs real cancellation \u2014 tasks finishing within a minute do not need one: remove timeoutMs to run in-process (implement your own deadline with Promise.race if needed), or raise it.`
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
manifest.timeoutMs = value;
|
|
3431
|
+
}
|
|
3432
|
+
if (graceMs !== void 0) manifest.graceMs = parseNumericLiteral(graceMs);
|
|
3420
3433
|
tasks.push(manifest);
|
|
3421
3434
|
}
|
|
3422
3435
|
return tasks.sort((a, b) => a.name.localeCompare(b.name));
|
|
3423
3436
|
}
|
|
3424
|
-
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE;
|
|
3437
|
+
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, NUM_LITERAL, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE, GRACE_MS_RE, MIN_ISOLATED_TIMEOUT_MS;
|
|
3425
3438
|
var init_scanTasks = __esm({
|
|
3426
3439
|
"src/task/scanTasks.ts"() {
|
|
3427
3440
|
"use strict";
|
|
3428
3441
|
TASK_PATTERNS = ["src/tasks/**/task.ts"];
|
|
3429
3442
|
TASK_FILENAME = "task.ts";
|
|
3430
3443
|
CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3444
|
+
NUM_LITERAL = "(\\d+(?:_\\d+)*)";
|
|
3445
|
+
CONCURRENCY_RE = new RegExp(`(?:^|\\n)\\s*concurrency:\\s*${NUM_LITERAL}`);
|
|
3446
|
+
RETRIES_RE = new RegExp(`(?:^|\\n)\\s*retries:\\s*${NUM_LITERAL}`);
|
|
3447
|
+
TIMEOUT_MS_RE = new RegExp(`(?:^|\\n)\\s*timeoutMs:\\s*${NUM_LITERAL}`);
|
|
3448
|
+
GRACE_MS_RE = new RegExp(`(?:^|\\n)\\s*graceMs:\\s*${NUM_LITERAL}`);
|
|
3449
|
+
MIN_ISOLATED_TIMEOUT_MS = 6e4;
|
|
3434
3450
|
}
|
|
3435
3451
|
});
|
|
3436
3452
|
|
|
@@ -3443,7 +3459,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
3443
3459
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
3444
3460
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
3445
3461
|
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
3446
|
-
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
3462
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
3463
|
+
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
3447
3464
|
}));
|
|
3448
3465
|
}
|
|
3449
3466
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -3459,7 +3476,8 @@ function hydrateTasks(manifest) {
|
|
|
3459
3476
|
cron: t.cron ?? void 0,
|
|
3460
3477
|
concurrency: t.concurrency ?? void 0,
|
|
3461
3478
|
retries: t.retries ?? void 0,
|
|
3462
|
-
timeoutMs: t.timeoutMs ?? void 0
|
|
3479
|
+
timeoutMs: t.timeoutMs ?? void 0,
|
|
3480
|
+
graceMs: t.graceMs ?? void 0
|
|
3463
3481
|
}));
|
|
3464
3482
|
}
|
|
3465
3483
|
function collectTaskSchemaSources(tasks, rootDir) {
|
|
@@ -8366,9 +8384,14 @@ import * as mod from '${moduleUrl}';
|
|
|
8366
8384
|
|
|
8367
8385
|
${BUILD_VIEW_SOURCE}
|
|
8368
8386
|
|
|
8387
|
+
${SERIALIZE_ERROR_SOURCE}
|
|
8388
|
+
|
|
8369
8389
|
const run = mod.run;
|
|
8370
8390
|
if (typeof run !== 'function') {
|
|
8371
|
-
parentPort.postMessage({
|
|
8391
|
+
parentPort.postMessage({
|
|
8392
|
+
type: 'error',
|
|
8393
|
+
error: { name: 'Error', message: 'Task module has no run export', stack: undefined, props: {} },
|
|
8394
|
+
});
|
|
8372
8395
|
} else {
|
|
8373
8396
|
let controller = null;
|
|
8374
8397
|
parentPort.on('message', async (msg) => {
|
|
@@ -8381,21 +8404,44 @@ if (typeof run !== 'function') {
|
|
|
8381
8404
|
const { payload, taskCtx } = msg;
|
|
8382
8405
|
try {
|
|
8383
8406
|
const registries = buildRegistriesView(msg.registries);
|
|
8384
|
-
const
|
|
8407
|
+
const progress = (value) => parentPort.postMessage({ type: 'progress', value });
|
|
8408
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal, registries, progress });
|
|
8385
8409
|
parentPort.postMessage({ type: 'done', result });
|
|
8386
8410
|
} catch (err) {
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
})
|
|
8411
|
+
const serialized = serializeError(err);
|
|
8412
|
+
try {
|
|
8413
|
+
parentPort.postMessage({ type: 'error', error: serialized });
|
|
8414
|
+
} catch (_) {
|
|
8415
|
+
// props \u542B\u4E0D\u53EF\u514B\u9686\u503C\uFF08\u51FD\u6570\u7B49\uFF09\u65F6\u4E22\u5F03 props\uFF0C\u4FDD\u5E95 name/message/stack
|
|
8416
|
+
parentPort.postMessage({
|
|
8417
|
+
type: 'error',
|
|
8418
|
+
error: { name: serialized.name, message: serialized.message, stack: serialized.stack, props: {} },
|
|
8419
|
+
});
|
|
8420
|
+
}
|
|
8391
8421
|
}
|
|
8392
8422
|
});
|
|
8393
8423
|
}
|
|
8394
8424
|
`;
|
|
8395
8425
|
}
|
|
8426
|
+
function reviveError(payload) {
|
|
8427
|
+
const err = new Error(payload?.message ?? "task worker error");
|
|
8428
|
+
if (payload?.name !== void 0) err.name = payload.name;
|
|
8429
|
+
if (payload?.stack !== void 0) err.stack = payload.stack;
|
|
8430
|
+
if (payload?.props) {
|
|
8431
|
+
for (const [key, value] of Object.entries(payload.props)) {
|
|
8432
|
+
err[key] = value;
|
|
8433
|
+
}
|
|
8434
|
+
}
|
|
8435
|
+
return err;
|
|
8436
|
+
}
|
|
8396
8437
|
async function runTaskInWorker(options) {
|
|
8397
8438
|
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
8398
|
-
const killGraceMs = options.
|
|
8439
|
+
const killGraceMs = options.graceMs ?? KILL_GRACE_MS;
|
|
8440
|
+
if (externalSignal?.aborted) {
|
|
8441
|
+
throw new TaskCancelledError(
|
|
8442
|
+
`Task "${taskCtx.job.name}" cancelled before start (driver stop signal already aborted)`
|
|
8443
|
+
);
|
|
8444
|
+
}
|
|
8399
8445
|
const moduleUrl = pathToFileURL2(taskModulePath).href;
|
|
8400
8446
|
const wrapperUrl = new URL(
|
|
8401
8447
|
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
@@ -8443,27 +8489,37 @@ async function runTaskInWorker(options) {
|
|
|
8443
8489
|
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
8444
8490
|
timeoutMs
|
|
8445
8491
|
);
|
|
8446
|
-
worker.on(
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8492
|
+
worker.on(
|
|
8493
|
+
"message",
|
|
8494
|
+
(msg) => {
|
|
8495
|
+
if (phase === "grace") {
|
|
8496
|
+
if (msg?.type === "done") {
|
|
8497
|
+
finish(
|
|
8498
|
+
() => reject(
|
|
8499
|
+
new TaskCancelledError(
|
|
8500
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
8501
|
+
)
|
|
8453
8502
|
)
|
|
8454
|
-
)
|
|
8455
|
-
)
|
|
8503
|
+
);
|
|
8504
|
+
} else if (msg?.type === "error") {
|
|
8505
|
+
finish(() => reject(new TaskCancelledError(msg.error?.message ?? cancelReason)));
|
|
8506
|
+
}
|
|
8507
|
+
return;
|
|
8508
|
+
}
|
|
8509
|
+
if (phase !== "running") return;
|
|
8510
|
+
if (msg?.type === "done") {
|
|
8511
|
+
finish(() => resolve3(msg.result));
|
|
8456
8512
|
} else if (msg?.type === "error") {
|
|
8457
|
-
finish(() => reject(
|
|
8513
|
+
finish(() => reject(reviveError(msg.error)));
|
|
8514
|
+
} else if (msg?.type === "progress") {
|
|
8515
|
+
options.onProgress?.(msg.value);
|
|
8458
8516
|
}
|
|
8459
|
-
return;
|
|
8460
|
-
}
|
|
8461
|
-
if (phase !== "running") return;
|
|
8462
|
-
if (msg?.type === "done") {
|
|
8463
|
-
finish(() => resolve3(msg.result));
|
|
8464
|
-
} else if (msg?.type === "error") {
|
|
8465
|
-
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
8466
8517
|
}
|
|
8518
|
+
);
|
|
8519
|
+
worker.on("messageerror", () => {
|
|
8520
|
+
finish(
|
|
8521
|
+
() => reject(new Error(`Task "${taskCtx.job.name}" worker message could not be deserialized`))
|
|
8522
|
+
);
|
|
8467
8523
|
});
|
|
8468
8524
|
worker.on("error", (err) => {
|
|
8469
8525
|
finish(() => reject(err));
|
|
@@ -8491,7 +8547,7 @@ async function runTaskInWorker(options) {
|
|
|
8491
8547
|
}
|
|
8492
8548
|
});
|
|
8493
8549
|
}
|
|
8494
|
-
var KILL_GRACE_MS, TaskCancelledError, BUILD_VIEW_SOURCE;
|
|
8550
|
+
var KILL_GRACE_MS, TaskCancelledError, BUILD_VIEW_SOURCE, SERIALIZE_ERROR_SOURCE;
|
|
8495
8551
|
var init_taskWorker = __esm({
|
|
8496
8552
|
"src/task/taskWorker.ts"() {
|
|
8497
8553
|
"use strict";
|
|
@@ -8556,6 +8612,19 @@ function buildRegistriesView(snapshot) {
|
|
|
8556
8612
|
},
|
|
8557
8613
|
};
|
|
8558
8614
|
}
|
|
8615
|
+
`;
|
|
8616
|
+
SERIALIZE_ERROR_SOURCE = `
|
|
8617
|
+
function serializeError(err) {
|
|
8618
|
+
if (err instanceof Error) {
|
|
8619
|
+
var props = {};
|
|
8620
|
+
var keys = Object.keys(err);
|
|
8621
|
+
for (var i = 0; i < keys.length; i++) {
|
|
8622
|
+
props[keys[i]] = err[keys[i]];
|
|
8623
|
+
}
|
|
8624
|
+
return { name: err.name, message: err.message, stack: err.stack, props: props };
|
|
8625
|
+
}
|
|
8626
|
+
return { name: 'Error', message: String(err), stack: undefined, props: {} };
|
|
8627
|
+
}
|
|
8559
8628
|
`;
|
|
8560
8629
|
}
|
|
8561
8630
|
});
|
|
@@ -8570,6 +8639,25 @@ function createTaskQueue(deps) {
|
|
|
8570
8639
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
8571
8640
|
let started = false;
|
|
8572
8641
|
let stopped = false;
|
|
8642
|
+
let finishedCount = 0;
|
|
8643
|
+
function transitionStatus(record, status) {
|
|
8644
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
8645
|
+
const nowTerminal = TERMINAL_STATUSES.has(status);
|
|
8646
|
+
if (!wasTerminal && nowTerminal) finishedCount++;
|
|
8647
|
+
else if (wasTerminal && !nowTerminal) finishedCount--;
|
|
8648
|
+
record.status = status;
|
|
8649
|
+
}
|
|
8650
|
+
function evictFinishedRecords() {
|
|
8651
|
+
let toEvict = finishedCount - MAX_FINISHED_RECORDS;
|
|
8652
|
+
for (const [id, job] of records) {
|
|
8653
|
+
if (toEvict <= 0) break;
|
|
8654
|
+
if (TERMINAL_STATUSES.has(job.status)) {
|
|
8655
|
+
records.delete(id);
|
|
8656
|
+
finishedCount--;
|
|
8657
|
+
toEvict--;
|
|
8658
|
+
}
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8573
8661
|
const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
|
|
8574
8662
|
const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
|
|
8575
8663
|
const zodPath = path25.join(path25.dirname(filePath), "zod.js");
|
|
@@ -8628,7 +8716,8 @@ function createTaskQueue(deps) {
|
|
|
8628
8716
|
createdAt: Date.now()
|
|
8629
8717
|
};
|
|
8630
8718
|
record.attempts = job.attempt;
|
|
8631
|
-
record
|
|
8719
|
+
transitionStatus(record, "running");
|
|
8720
|
+
delete record.progress;
|
|
8632
8721
|
record.error = void 0;
|
|
8633
8722
|
records.set(job.id, record);
|
|
8634
8723
|
try {
|
|
@@ -8643,7 +8732,11 @@ function createTaskQueue(deps) {
|
|
|
8643
8732
|
},
|
|
8644
8733
|
registries: snapshotRegistries(),
|
|
8645
8734
|
timeoutMs: meta.timeoutMs,
|
|
8646
|
-
|
|
8735
|
+
graceMs: meta.graceMs,
|
|
8736
|
+
externalSignal: job.signal,
|
|
8737
|
+
onProgress: (value) => {
|
|
8738
|
+
if (record.status === "running") record.progress = value;
|
|
8739
|
+
}
|
|
8647
8740
|
});
|
|
8648
8741
|
} else {
|
|
8649
8742
|
let mod = moduleCache2.get(job.name);
|
|
@@ -8658,17 +8751,22 @@ function createTaskQueue(deps) {
|
|
|
8658
8751
|
signal: job.signal,
|
|
8659
8752
|
config: deps.config,
|
|
8660
8753
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
8661
|
-
registries: registriesView
|
|
8754
|
+
registries: registriesView,
|
|
8755
|
+
progress: (value) => {
|
|
8756
|
+
if (record.status === "running") record.progress = value;
|
|
8757
|
+
}
|
|
8662
8758
|
};
|
|
8663
8759
|
result = await mod.run(job.payload, taskCtx);
|
|
8664
8760
|
}
|
|
8665
|
-
record
|
|
8761
|
+
transitionStatus(record, "done");
|
|
8666
8762
|
record.result = result;
|
|
8763
|
+
evictFinishedRecords();
|
|
8667
8764
|
return result;
|
|
8668
8765
|
} catch (err) {
|
|
8669
8766
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
8670
|
-
record
|
|
8767
|
+
transitionStatus(record, cancelled ? "cancelled" : "failed");
|
|
8671
8768
|
record.error = err instanceof Error ? err.message : String(err);
|
|
8769
|
+
evictFinishedRecords();
|
|
8672
8770
|
if (deps.onFailed) {
|
|
8673
8771
|
void Promise.resolve().then(
|
|
8674
8772
|
() => deps.onFailed({
|
|
@@ -8763,7 +8861,8 @@ function createTaskQueue(deps) {
|
|
|
8763
8861
|
}
|
|
8764
8862
|
await driver.cancel(name, id);
|
|
8765
8863
|
const record = records.get(id);
|
|
8766
|
-
if (record) record
|
|
8864
|
+
if (record) transitionStatus(record, "cancelled");
|
|
8865
|
+
evictFinishedRecords();
|
|
8767
8866
|
},
|
|
8768
8867
|
async retry(name, id) {
|
|
8769
8868
|
if (!driver.retry) {
|
|
@@ -8773,7 +8872,7 @@ function createTaskQueue(deps) {
|
|
|
8773
8872
|
}
|
|
8774
8873
|
await driver.retry(name, id);
|
|
8775
8874
|
const record = records.get(id);
|
|
8776
|
-
if (record) record
|
|
8875
|
+
if (record) transitionStatus(record, "pending");
|
|
8777
8876
|
},
|
|
8778
8877
|
async start() {
|
|
8779
8878
|
if (stopped) {
|
|
@@ -8800,12 +8899,15 @@ function createTaskQueue(deps) {
|
|
|
8800
8899
|
};
|
|
8801
8900
|
return queue;
|
|
8802
8901
|
}
|
|
8902
|
+
var TERMINAL_STATUSES, MAX_FINISHED_RECORDS;
|
|
8803
8903
|
var init_taskQueue = __esm({
|
|
8804
8904
|
"src/task/taskQueue.ts"() {
|
|
8805
8905
|
"use strict";
|
|
8806
8906
|
init_httpErrors();
|
|
8807
8907
|
init_taskWorker();
|
|
8808
8908
|
init_registries();
|
|
8909
|
+
TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
8910
|
+
MAX_FINISHED_RECORDS = 1e3;
|
|
8809
8911
|
}
|
|
8810
8912
|
});
|
|
8811
8913
|
|