@faapi/faapi 6.3.0 → 6.4.1
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 +302 -156
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +68 -28
- package/dist/index.js +293 -153
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-CCveqSnY.d.ts → routeTypes-_17rXzal.d.ts} +152 -11
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3412,14 +3412,16 @@ async function scanTasks(rootDir, patterns) {
|
|
|
3412
3412
|
const cron = CRON_RE.exec(source)?.[1];
|
|
3413
3413
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
3414
3414
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
3415
|
+
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
3415
3416
|
if (cron !== void 0) manifest.cron = cron;
|
|
3416
3417
|
if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
|
|
3417
3418
|
if (retries !== void 0) manifest.retries = Number(retries);
|
|
3419
|
+
if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
|
|
3418
3420
|
tasks.push(manifest);
|
|
3419
3421
|
}
|
|
3420
3422
|
return tasks;
|
|
3421
3423
|
}
|
|
3422
|
-
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE;
|
|
3424
|
+
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE;
|
|
3423
3425
|
var init_scanTasks = __esm({
|
|
3424
3426
|
"src/task/scanTasks.ts"() {
|
|
3425
3427
|
"use strict";
|
|
@@ -3428,6 +3430,7 @@ var init_scanTasks = __esm({
|
|
|
3428
3430
|
CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
3429
3431
|
CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
|
|
3430
3432
|
RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
|
|
3433
|
+
TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
|
|
3431
3434
|
}
|
|
3432
3435
|
});
|
|
3433
3436
|
|
|
@@ -3439,7 +3442,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
3439
3442
|
filePath: toProdFilePath(t.filePath, dist),
|
|
3440
3443
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
3441
3444
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
3442
|
-
...t.retries !== void 0 ? { retries: t.retries } : {}
|
|
3445
|
+
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
3446
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
3443
3447
|
}));
|
|
3444
3448
|
}
|
|
3445
3449
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -3454,7 +3458,8 @@ function hydrateTasks(manifest) {
|
|
|
3454
3458
|
filePath: t.filePath,
|
|
3455
3459
|
cron: t.cron ?? void 0,
|
|
3456
3460
|
concurrency: t.concurrency ?? void 0,
|
|
3457
|
-
retries: t.retries ?? void 0
|
|
3461
|
+
retries: t.retries ?? void 0,
|
|
3462
|
+
timeoutMs: t.timeoutMs ?? void 0
|
|
3458
3463
|
}));
|
|
3459
3464
|
}
|
|
3460
3465
|
function collectTaskSchemaSources(tasks, rootDir) {
|
|
@@ -8304,138 +8309,168 @@ var init_startServer = __esm({
|
|
|
8304
8309
|
}
|
|
8305
8310
|
});
|
|
8306
8311
|
|
|
8307
|
-
// src/task/
|
|
8308
|
-
import {
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
if (stopped) return;
|
|
8317
|
-
for (const [name, worker] of workers) {
|
|
8318
|
-
const queue = pendingByTask.get(name);
|
|
8319
|
-
if (!queue || queue.length === 0) continue;
|
|
8320
|
-
let running = runningCount.get(name) ?? 0;
|
|
8321
|
-
while (running < worker.concurrency && queue.length > 0) {
|
|
8322
|
-
const head = jobs.get(queue[0]);
|
|
8323
|
-
if (!head) {
|
|
8324
|
-
queue.shift();
|
|
8325
|
-
continue;
|
|
8326
|
-
}
|
|
8327
|
-
if (head.runAt > Date.now()) break;
|
|
8328
|
-
queue.shift();
|
|
8329
|
-
running += 1;
|
|
8330
|
-
runningCount.set(name, running);
|
|
8331
|
-
void dispatch(name, worker, head);
|
|
8332
|
-
}
|
|
8333
|
-
if (running === 0) runningCount.delete(name);
|
|
8334
|
-
else runningCount.set(name, running);
|
|
8335
|
-
}
|
|
8312
|
+
// src/task/taskWorker.ts
|
|
8313
|
+
import { Worker } from "worker_threads";
|
|
8314
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
8315
|
+
function safeConfig(config) {
|
|
8316
|
+
if (config === void 0 || config === null) return config;
|
|
8317
|
+
try {
|
|
8318
|
+
structuredClone(config);
|
|
8319
|
+
return config;
|
|
8320
|
+
} catch {
|
|
8336
8321
|
}
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8322
|
+
try {
|
|
8323
|
+
return JSON.parse(JSON.stringify(config));
|
|
8324
|
+
} catch {
|
|
8325
|
+
return void 0;
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
function buildWrapperSource(moduleUrl) {
|
|
8329
|
+
return `
|
|
8330
|
+
import { parentPort } from 'node:worker_threads';
|
|
8331
|
+
import * as mod from '${moduleUrl}';
|
|
8332
|
+
|
|
8333
|
+
const run = mod.run;
|
|
8334
|
+
if (typeof run !== 'function') {
|
|
8335
|
+
parentPort.postMessage({ type: 'error', message: 'Task module has no run export' });
|
|
8336
|
+
} else {
|
|
8337
|
+
let controller = null;
|
|
8338
|
+
parentPort.on('message', async (msg) => {
|
|
8339
|
+
if (msg?.type === 'abort') {
|
|
8340
|
+
controller?.abort(new Error(msg.reason));
|
|
8341
|
+
return;
|
|
8342
|
+
}
|
|
8343
|
+
if (msg?.type !== 'run') return;
|
|
8344
|
+
controller = new AbortController();
|
|
8345
|
+
const { payload, taskCtx } = msg;
|
|
8346
8346
|
try {
|
|
8347
|
-
await
|
|
8347
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal });
|
|
8348
|
+
parentPort.postMessage({ type: 'done', result });
|
|
8348
8349
|
} catch (err) {
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
job.timer = void 0;
|
|
8354
|
-
pendingByTask.get(name)?.push(job.id);
|
|
8355
|
-
pump();
|
|
8356
|
-
}, delay);
|
|
8357
|
-
}
|
|
8358
|
-
void err;
|
|
8359
|
-
} finally {
|
|
8360
|
-
const running = (runningCount.get(name) ?? 1) - 1;
|
|
8361
|
-
if (running <= 0) runningCount.delete(name);
|
|
8362
|
-
else runningCount.set(name, running);
|
|
8363
|
-
pump();
|
|
8350
|
+
parentPort.postMessage({
|
|
8351
|
+
type: 'error',
|
|
8352
|
+
message: err instanceof Error ? err.message : String(err),
|
|
8353
|
+
});
|
|
8364
8354
|
}
|
|
8365
|
-
}
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8355
|
+
});
|
|
8356
|
+
}
|
|
8357
|
+
`;
|
|
8358
|
+
}
|
|
8359
|
+
async function runTaskInWorker(options) {
|
|
8360
|
+
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
8361
|
+
const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
|
|
8362
|
+
const moduleUrl = pathToFileURL2(taskModulePath).href;
|
|
8363
|
+
const wrapperUrl = new URL(
|
|
8364
|
+
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
8365
|
+
);
|
|
8366
|
+
return new Promise((resolve3, reject) => {
|
|
8367
|
+
let worker;
|
|
8368
|
+
try {
|
|
8369
|
+
worker = new Worker(wrapperUrl);
|
|
8370
|
+
} catch (err) {
|
|
8371
|
+
reject(err);
|
|
8372
|
+
return;
|
|
8373
|
+
}
|
|
8374
|
+
let phase = "running";
|
|
8375
|
+
let cancelReason = "";
|
|
8376
|
+
let graceTimer;
|
|
8377
|
+
const finish = (settle) => {
|
|
8378
|
+
if (phase === "settled") return;
|
|
8379
|
+
phase = "settled";
|
|
8380
|
+
clearTimeout(timeoutTimer);
|
|
8381
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
8382
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
8383
|
+
void worker.terminate();
|
|
8384
|
+
settle();
|
|
8385
|
+
};
|
|
8386
|
+
const startCancel = (reason) => {
|
|
8387
|
+
if (phase !== "running") return;
|
|
8388
|
+
phase = "grace";
|
|
8389
|
+
cancelReason = reason;
|
|
8390
|
+
worker.postMessage({ type: "abort", reason });
|
|
8391
|
+
graceTimer = setTimeout(() => {
|
|
8392
|
+
finish(
|
|
8393
|
+
() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${reason} and was terminated`))
|
|
8394
|
+
);
|
|
8395
|
+
}, killGraceMs);
|
|
8396
|
+
};
|
|
8397
|
+
const onExternalAbort = () => startCancel("cancelled (driver stop timeout)");
|
|
8398
|
+
if (externalSignal) {
|
|
8399
|
+
if (externalSignal.aborted) {
|
|
8400
|
+
onExternalAbort();
|
|
8387
8401
|
} else {
|
|
8388
|
-
|
|
8389
|
-
if (!q) {
|
|
8390
|
-
q = [];
|
|
8391
|
-
pendingByTask.set(name, q);
|
|
8392
|
-
}
|
|
8393
|
-
q.push(id);
|
|
8394
|
-
pump();
|
|
8402
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
8395
8403
|
}
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8404
|
+
}
|
|
8405
|
+
const timeoutTimer = setTimeout(
|
|
8406
|
+
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
8407
|
+
timeoutMs
|
|
8408
|
+
);
|
|
8409
|
+
worker.on("message", (msg) => {
|
|
8410
|
+
if (phase === "grace") {
|
|
8411
|
+
if (msg?.type === "done") {
|
|
8412
|
+
finish(
|
|
8413
|
+
() => reject(
|
|
8414
|
+
new TaskCancelledError(
|
|
8415
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
8416
|
+
)
|
|
8417
|
+
)
|
|
8418
|
+
);
|
|
8419
|
+
} else if (msg?.type === "error") {
|
|
8420
|
+
finish(() => reject(new TaskCancelledError(msg.message ?? cancelReason)));
|
|
8408
8421
|
}
|
|
8422
|
+
return;
|
|
8409
8423
|
}
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8424
|
+
if (phase !== "running") return;
|
|
8425
|
+
if (msg?.type === "done") {
|
|
8426
|
+
finish(() => resolve3(msg.result));
|
|
8427
|
+
} else if (msg?.type === "error") {
|
|
8428
|
+
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
8415
8429
|
}
|
|
8416
|
-
|
|
8417
|
-
|
|
8430
|
+
});
|
|
8431
|
+
worker.on("error", (err) => {
|
|
8432
|
+
finish(() => reject(err));
|
|
8433
|
+
});
|
|
8434
|
+
worker.on("exit", (code) => {
|
|
8435
|
+
if (phase === "grace") {
|
|
8436
|
+
finish(() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${cancelReason}`)));
|
|
8437
|
+
} else if (phase === "running") {
|
|
8438
|
+
finish(() => reject(new Error(`Task worker exited unexpectedly (code ${code})`)));
|
|
8418
8439
|
}
|
|
8419
|
-
}
|
|
8420
|
-
|
|
8421
|
-
|
|
8440
|
+
});
|
|
8441
|
+
try {
|
|
8442
|
+
worker.postMessage({
|
|
8443
|
+
type: "run",
|
|
8444
|
+
payload,
|
|
8445
|
+
taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job }
|
|
8446
|
+
});
|
|
8447
|
+
} catch (err) {
|
|
8448
|
+
finish(
|
|
8449
|
+
() => reject(
|
|
8450
|
+
new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
|
|
8451
|
+
)
|
|
8452
|
+
);
|
|
8422
8453
|
}
|
|
8423
|
-
};
|
|
8454
|
+
});
|
|
8424
8455
|
}
|
|
8425
|
-
var
|
|
8426
|
-
var
|
|
8427
|
-
"src/task/
|
|
8456
|
+
var KILL_GRACE_MS, TaskCancelledError;
|
|
8457
|
+
var init_taskWorker = __esm({
|
|
8458
|
+
"src/task/taskWorker.ts"() {
|
|
8428
8459
|
"use strict";
|
|
8429
|
-
|
|
8430
|
-
|
|
8460
|
+
KILL_GRACE_MS = 5e3;
|
|
8461
|
+
TaskCancelledError = class extends Error {
|
|
8462
|
+
constructor(message) {
|
|
8463
|
+
super(message);
|
|
8464
|
+
this.name = "TaskCancelledError";
|
|
8465
|
+
}
|
|
8466
|
+
};
|
|
8431
8467
|
}
|
|
8432
8468
|
});
|
|
8433
8469
|
|
|
8434
8470
|
// src/task/taskQueue.ts
|
|
8435
8471
|
import path25 from "path";
|
|
8436
8472
|
function createTaskQueue(deps) {
|
|
8437
|
-
const { registry, rootDir } = deps;
|
|
8438
|
-
const driver = deps.driver ?? createMemoryDriver();
|
|
8473
|
+
const { registry, rootDir, driver } = deps;
|
|
8439
8474
|
const records = /* @__PURE__ */ new Map();
|
|
8440
8475
|
const moduleCache2 = /* @__PURE__ */ new Map();
|
|
8441
8476
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
@@ -8496,26 +8531,54 @@ function createTaskQueue(deps) {
|
|
|
8496
8531
|
record.error = void 0;
|
|
8497
8532
|
records.set(job.id, record);
|
|
8498
8533
|
try {
|
|
8499
|
-
let
|
|
8500
|
-
if (
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
}
|
|
8512
|
-
|
|
8534
|
+
let result;
|
|
8535
|
+
if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
|
|
8536
|
+
result = await (deps.runIsolated ?? runTaskInWorker)({
|
|
8537
|
+
taskModulePath: path25.resolve(rootDir, meta.filePath),
|
|
8538
|
+
payload: job.payload,
|
|
8539
|
+
taskCtx: {
|
|
8540
|
+
config: deps.config,
|
|
8541
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
8542
|
+
},
|
|
8543
|
+
timeoutMs: meta.timeoutMs,
|
|
8544
|
+
externalSignal: job.signal
|
|
8545
|
+
});
|
|
8546
|
+
} else {
|
|
8547
|
+
let mod = moduleCache2.get(job.name);
|
|
8548
|
+
if (!mod) {
|
|
8549
|
+
mod = await loadTaskModule(path25.resolve(rootDir, meta.filePath));
|
|
8550
|
+
moduleCache2.set(job.name, mod);
|
|
8551
|
+
}
|
|
8552
|
+
if (typeof mod.run !== "function") {
|
|
8553
|
+
throw new Error(`Task "${job.name}" module has no run export`);
|
|
8554
|
+
}
|
|
8555
|
+
const taskCtx = {
|
|
8556
|
+
signal: job.signal,
|
|
8557
|
+
config: deps.config,
|
|
8558
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
8559
|
+
};
|
|
8560
|
+
result = await mod.run(job.payload, taskCtx);
|
|
8561
|
+
}
|
|
8513
8562
|
record.status = "done";
|
|
8514
8563
|
record.result = result;
|
|
8515
8564
|
return result;
|
|
8516
8565
|
} catch (err) {
|
|
8517
|
-
|
|
8566
|
+
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
8567
|
+
record.status = cancelled ? "cancelled" : "failed";
|
|
8518
8568
|
record.error = err instanceof Error ? err.message : String(err);
|
|
8569
|
+
if (deps.onFailed) {
|
|
8570
|
+
void Promise.resolve().then(
|
|
8571
|
+
() => deps.onFailed({
|
|
8572
|
+
task: job.name,
|
|
8573
|
+
jobId: job.id,
|
|
8574
|
+
attempt: job.attempt,
|
|
8575
|
+
willRetry: job.attempt <= (meta.retries ?? 0),
|
|
8576
|
+
cancelled,
|
|
8577
|
+
error: record.error
|
|
8578
|
+
})
|
|
8579
|
+
).catch(() => {
|
|
8580
|
+
});
|
|
8581
|
+
}
|
|
8519
8582
|
throw err;
|
|
8520
8583
|
}
|
|
8521
8584
|
}
|
|
@@ -8537,7 +8600,8 @@ function createTaskQueue(deps) {
|
|
|
8537
8600
|
const meta = registry.get(name);
|
|
8538
8601
|
const id = await driver.enqueue(name, data, {
|
|
8539
8602
|
delayMs: opts?.delayMs,
|
|
8540
|
-
retries: meta.retries ?? 0
|
|
8603
|
+
retries: meta.retries ?? 0,
|
|
8604
|
+
dedupId: opts?.dedupId
|
|
8541
8605
|
});
|
|
8542
8606
|
if (!records.has(id)) {
|
|
8543
8607
|
records.set(id, {
|
|
@@ -8560,6 +8624,54 @@ function createTaskQueue(deps) {
|
|
|
8560
8624
|
}
|
|
8561
8625
|
return snapshot;
|
|
8562
8626
|
},
|
|
8627
|
+
async listQueued(name) {
|
|
8628
|
+
if (!driver.list) {
|
|
8629
|
+
throw new Error(
|
|
8630
|
+
"[faapi] Task driver does not support listing queued tasks (TaskDriver.list is not implemented). Use the queue system's own management tools, or see driverTypes.md for per-driver capability."
|
|
8631
|
+
);
|
|
8632
|
+
}
|
|
8633
|
+
const queued = await driver.list({ name, limit: 50 });
|
|
8634
|
+
const merged = /* @__PURE__ */ new Map();
|
|
8635
|
+
for (const record of queued) {
|
|
8636
|
+
if (name !== void 0 && record.name !== name) continue;
|
|
8637
|
+
merged.set(record.id, {
|
|
8638
|
+
id: record.id,
|
|
8639
|
+
name: record.name,
|
|
8640
|
+
payload: record.payload,
|
|
8641
|
+
status: record.status,
|
|
8642
|
+
attempts: record.attempts,
|
|
8643
|
+
createdAt: record.createdAt,
|
|
8644
|
+
...record.result !== void 0 ? { result: record.result } : {},
|
|
8645
|
+
...record.error !== void 0 ? { error: record.error } : {},
|
|
8646
|
+
...record.runAt !== void 0 ? { runAt: record.runAt } : {}
|
|
8647
|
+
});
|
|
8648
|
+
}
|
|
8649
|
+
for (const local of records.values()) {
|
|
8650
|
+
if (name !== void 0 && local.name !== name) continue;
|
|
8651
|
+
merged.set(local.id, { ...local });
|
|
8652
|
+
}
|
|
8653
|
+
return [...merged.values()];
|
|
8654
|
+
},
|
|
8655
|
+
async cancel(name, id) {
|
|
8656
|
+
if (!driver.cancel) {
|
|
8657
|
+
throw new Error(
|
|
8658
|
+
"[faapi] Task driver does not support cancelling tasks (TaskDriver.cancel is not implemented)."
|
|
8659
|
+
);
|
|
8660
|
+
}
|
|
8661
|
+
await driver.cancel(name, id);
|
|
8662
|
+
const record = records.get(id);
|
|
8663
|
+
if (record) record.status = "cancelled";
|
|
8664
|
+
},
|
|
8665
|
+
async retry(name, id) {
|
|
8666
|
+
if (!driver.retry) {
|
|
8667
|
+
throw new Error(
|
|
8668
|
+
"[faapi] Task driver does not support retrying tasks (TaskDriver.retry is not implemented)."
|
|
8669
|
+
);
|
|
8670
|
+
}
|
|
8671
|
+
await driver.retry(name, id);
|
|
8672
|
+
const record = records.get(id);
|
|
8673
|
+
if (record) record.status = "pending";
|
|
8674
|
+
},
|
|
8563
8675
|
async start() {
|
|
8564
8676
|
if (stopped) {
|
|
8565
8677
|
throw new Error("[faapi] Task queue is stopped and cannot be restarted");
|
|
@@ -8589,21 +8701,28 @@ var init_taskQueue = __esm({
|
|
|
8589
8701
|
"src/task/taskQueue.ts"() {
|
|
8590
8702
|
"use strict";
|
|
8591
8703
|
init_httpErrors();
|
|
8592
|
-
|
|
8704
|
+
init_taskWorker();
|
|
8593
8705
|
}
|
|
8594
8706
|
});
|
|
8595
8707
|
|
|
8596
8708
|
// src/task/loadTaskDriver.ts
|
|
8597
8709
|
async function loadTaskDriver(driver, driverOptions) {
|
|
8598
|
-
if (driver === void 0
|
|
8599
|
-
|
|
8710
|
+
if (driver === void 0) {
|
|
8711
|
+
throw new Error(
|
|
8712
|
+
"[faapi] config.task.driver is required when tasks are defined. Install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq' (or pass a TaskDriver instance)."
|
|
8713
|
+
);
|
|
8714
|
+
}
|
|
8715
|
+
if (driver === "memory") {
|
|
8716
|
+
throw new Error(
|
|
8717
|
+
"[faapi] The built-in memory task driver has been removed. Migrate to a persistent driver: install @faapi/task-pgboss or @faapi/task-bullmq and set `task.driver` to 'pgboss' or 'bullmq'."
|
|
8718
|
+
);
|
|
8600
8719
|
}
|
|
8601
8720
|
if (typeof driver === "object") {
|
|
8602
8721
|
return driver;
|
|
8603
8722
|
}
|
|
8604
8723
|
if (driver !== "pgboss" && driver !== "bullmq") {
|
|
8605
8724
|
throw new Error(
|
|
8606
|
-
`[faapi] Unknown task driver "${driver}". Supported: '
|
|
8725
|
+
`[faapi] Unknown task driver "${driver}". Supported: 'pgboss', 'bullmq', or a TaskDriver instance.`
|
|
8607
8726
|
);
|
|
8608
8727
|
}
|
|
8609
8728
|
const specifier = `@faapi/task-${driver}`;
|
|
@@ -8630,7 +8749,30 @@ async function loadTaskDriver(driver, driverOptions) {
|
|
|
8630
8749
|
var init_loadTaskDriver = __esm({
|
|
8631
8750
|
"src/task/loadTaskDriver.ts"() {
|
|
8632
8751
|
"use strict";
|
|
8633
|
-
|
|
8752
|
+
}
|
|
8753
|
+
});
|
|
8754
|
+
|
|
8755
|
+
// src/task/idleTaskDriver.ts
|
|
8756
|
+
function createIdleTaskDriver() {
|
|
8757
|
+
return {
|
|
8758
|
+
enqueue() {
|
|
8759
|
+
return Promise.reject(
|
|
8760
|
+
new Error(
|
|
8761
|
+
"[faapi] New task(s) were registered without a queue driver. Set config.task.driver to 'pgboss' or 'bullmq' (install the matching @faapi/task-* package) and restart the server."
|
|
8762
|
+
)
|
|
8763
|
+
);
|
|
8764
|
+
},
|
|
8765
|
+
startWorker() {
|
|
8766
|
+
},
|
|
8767
|
+
async stop() {
|
|
8768
|
+
},
|
|
8769
|
+
async stopWorkers() {
|
|
8770
|
+
}
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
var init_idleTaskDriver = __esm({
|
|
8774
|
+
"src/task/idleTaskDriver.ts"() {
|
|
8775
|
+
"use strict";
|
|
8634
8776
|
}
|
|
8635
8777
|
});
|
|
8636
8778
|
|
|
@@ -8643,13 +8785,17 @@ function createCronScheduler(registry, enqueue) {
|
|
|
8643
8785
|
this.stop();
|
|
8644
8786
|
for (const task of registry.list()) {
|
|
8645
8787
|
if (!task.cron) continue;
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8788
|
+
const schedule = new Cron(task.cron, () => {
|
|
8789
|
+
const runAt = schedule.currentRun();
|
|
8790
|
+
void Promise.resolve().then(
|
|
8791
|
+
() => enqueue(task.name, {
|
|
8792
|
+
dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
|
|
8793
|
+
})
|
|
8794
|
+
).catch((err) => {
|
|
8795
|
+
console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
|
|
8796
|
+
});
|
|
8797
|
+
});
|
|
8798
|
+
schedules.push(schedule);
|
|
8653
8799
|
}
|
|
8654
8800
|
},
|
|
8655
8801
|
stop() {
|
|
@@ -8669,7 +8815,7 @@ var init_cronScheduler = __esm({
|
|
|
8669
8815
|
// src/cli/loadPlugins.ts
|
|
8670
8816
|
import path26 from "path";
|
|
8671
8817
|
import fs18 from "fs";
|
|
8672
|
-
import { pathToFileURL as
|
|
8818
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
8673
8819
|
async function loadPlugins(declarations, ctx, rootDir, dist) {
|
|
8674
8820
|
const handlerWrappers = [];
|
|
8675
8821
|
const upgradeWrappers = [];
|
|
@@ -8762,7 +8908,7 @@ async function importPluginModule(specifier, baseDir, dist) {
|
|
|
8762
8908
|
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
8763
8909
|
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
8764
8910
|
if (productPath !== null && fs18.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
|
|
8765
|
-
return import(
|
|
8911
|
+
return import(pathToFileURL3(productPath).href);
|
|
8766
8912
|
}
|
|
8767
8913
|
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
8768
8914
|
if (!isDevOnDemandEnabled() || !distDir) {
|
|
@@ -8777,11 +8923,11 @@ async function importPluginModule(specifier, baseDir, dist) {
|
|
|
8777
8923
|
await compileProjectModules([sourcePath], baseDir, dist);
|
|
8778
8924
|
}
|
|
8779
8925
|
if (productPath && fs18.existsSync(productPath)) {
|
|
8780
|
-
return import(
|
|
8926
|
+
return import(pathToFileURL3(productPath).href);
|
|
8781
8927
|
}
|
|
8782
8928
|
}
|
|
8783
8929
|
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
8784
|
-
return import(
|
|
8930
|
+
return import(pathToFileURL3(sourcePath).href);
|
|
8785
8931
|
}
|
|
8786
8932
|
const candidates = [
|
|
8787
8933
|
`${specifier}.ts`,
|
|
@@ -8848,10 +8994,8 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
|
|
|
8848
8994
|
}
|
|
8849
8995
|
function getTaskDriverOptions(config) {
|
|
8850
8996
|
if (!config?.task) return void 0;
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
if (driver === "pgboss") return taskConfig.pgboss;
|
|
8854
|
-
if (driver === "bullmq") return taskConfig.bullmq;
|
|
8997
|
+
if (config.task.driver === "pgboss") return config.task.pgboss;
|
|
8998
|
+
if (config.task.driver === "bullmq") return config.task.bullmq;
|
|
8855
8999
|
return void 0;
|
|
8856
9000
|
}
|
|
8857
9001
|
async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
|
|
@@ -8920,16 +9064,17 @@ async function createAppBase(options) {
|
|
|
8920
9064
|
const tools = await loadAndHydrateTools(rootDir, dist, registries);
|
|
8921
9065
|
const agents = await loadAndHydrateAgents(rootDir, dist, registries);
|
|
8922
9066
|
const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
|
|
8923
|
-
const taskDriver = await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config));
|
|
9067
|
+
const taskDriver = taskMetas.length ? await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config)) : createIdleTaskDriver();
|
|
8924
9068
|
const taskQueue = createTaskQueue({
|
|
8925
9069
|
registry: registries.task,
|
|
8926
9070
|
rootDir,
|
|
8927
9071
|
config,
|
|
8928
|
-
driver: taskDriver
|
|
9072
|
+
driver: taskDriver,
|
|
9073
|
+
onFailed: config?.task?.onFailed
|
|
8929
9074
|
});
|
|
8930
9075
|
const cronScheduler = createCronScheduler(
|
|
8931
9076
|
registries.task,
|
|
8932
|
-
(name) => taskQueue.enqueue(name)
|
|
9077
|
+
(name, opts) => taskQueue.enqueue(name, void 0, opts)
|
|
8933
9078
|
);
|
|
8934
9079
|
const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
|
|
8935
9080
|
const stopTaskRuntime = async () => {
|
|
@@ -8937,7 +9082,7 @@ async function createAppBase(options) {
|
|
|
8937
9082
|
await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
|
|
8938
9083
|
};
|
|
8939
9084
|
if (taskEnabled) {
|
|
8940
|
-
taskQueue.start();
|
|
9085
|
+
await taskQueue.start();
|
|
8941
9086
|
cronScheduler.start();
|
|
8942
9087
|
}
|
|
8943
9088
|
registries.taskHandle.register(() => taskQueue);
|
|
@@ -9235,6 +9380,7 @@ var init_createAppCore = __esm({
|
|
|
9235
9380
|
init_generateTaskArtifacts();
|
|
9236
9381
|
init_taskQueue();
|
|
9237
9382
|
init_loadTaskDriver();
|
|
9383
|
+
init_idleTaskDriver();
|
|
9238
9384
|
init_cronScheduler();
|
|
9239
9385
|
init_loadPlugins();
|
|
9240
9386
|
init_importWithCacheBust();
|