@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/index.js
CHANGED
|
@@ -1941,131 +1941,161 @@ var PayloadTooLargeError = class extends FaapiError {
|
|
|
1941
1941
|
}
|
|
1942
1942
|
};
|
|
1943
1943
|
|
|
1944
|
-
// src/task/
|
|
1945
|
-
import {
|
|
1946
|
-
|
|
1947
|
-
var
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
const workers = /* @__PURE__ */ new Map();
|
|
1953
|
-
let stopped = false;
|
|
1954
|
-
function pump() {
|
|
1955
|
-
if (stopped) return;
|
|
1956
|
-
for (const [name, worker] of workers) {
|
|
1957
|
-
const queue = pendingByTask.get(name);
|
|
1958
|
-
if (!queue || queue.length === 0) continue;
|
|
1959
|
-
let running = runningCount.get(name) ?? 0;
|
|
1960
|
-
while (running < worker.concurrency && queue.length > 0) {
|
|
1961
|
-
const head = jobs.get(queue[0]);
|
|
1962
|
-
if (!head) {
|
|
1963
|
-
queue.shift();
|
|
1964
|
-
continue;
|
|
1965
|
-
}
|
|
1966
|
-
if (head.runAt > Date.now()) break;
|
|
1967
|
-
queue.shift();
|
|
1968
|
-
running += 1;
|
|
1969
|
-
runningCount.set(name, running);
|
|
1970
|
-
void dispatch(name, worker, head);
|
|
1971
|
-
}
|
|
1972
|
-
if (running === 0) runningCount.delete(name);
|
|
1973
|
-
else runningCount.set(name, running);
|
|
1974
|
-
}
|
|
1944
|
+
// src/task/taskWorker.ts
|
|
1945
|
+
import { Worker } from "worker_threads";
|
|
1946
|
+
import { pathToFileURL } from "url";
|
|
1947
|
+
var KILL_GRACE_MS = 5e3;
|
|
1948
|
+
var TaskCancelledError = class extends Error {
|
|
1949
|
+
constructor(message) {
|
|
1950
|
+
super(message);
|
|
1951
|
+
this.name = "TaskCancelledError";
|
|
1975
1952
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1953
|
+
};
|
|
1954
|
+
function safeConfig(config) {
|
|
1955
|
+
if (config === void 0 || config === null) return config;
|
|
1956
|
+
try {
|
|
1957
|
+
structuredClone(config);
|
|
1958
|
+
return config;
|
|
1959
|
+
} catch {
|
|
1960
|
+
}
|
|
1961
|
+
try {
|
|
1962
|
+
return JSON.parse(JSON.stringify(config));
|
|
1963
|
+
} catch {
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
function buildWrapperSource(moduleUrl) {
|
|
1968
|
+
return `
|
|
1969
|
+
import { parentPort } from 'node:worker_threads';
|
|
1970
|
+
import * as mod from '${moduleUrl}';
|
|
1971
|
+
|
|
1972
|
+
const run = mod.run;
|
|
1973
|
+
if (typeof run !== 'function') {
|
|
1974
|
+
parentPort.postMessage({ type: 'error', message: 'Task module has no run export' });
|
|
1975
|
+
} else {
|
|
1976
|
+
let controller = null;
|
|
1977
|
+
parentPort.on('message', async (msg) => {
|
|
1978
|
+
if (msg?.type === 'abort') {
|
|
1979
|
+
controller?.abort(new Error(msg.reason));
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (msg?.type !== 'run') return;
|
|
1983
|
+
controller = new AbortController();
|
|
1984
|
+
const { payload, taskCtx } = msg;
|
|
1985
1985
|
try {
|
|
1986
|
-
await
|
|
1986
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal });
|
|
1987
|
+
parentPort.postMessage({ type: 'done', result });
|
|
1987
1988
|
} catch (err) {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
job.timer = void 0;
|
|
1993
|
-
pendingByTask.get(name)?.push(job.id);
|
|
1994
|
-
pump();
|
|
1995
|
-
}, delay);
|
|
1996
|
-
}
|
|
1997
|
-
void err;
|
|
1998
|
-
} finally {
|
|
1999
|
-
const running = (runningCount.get(name) ?? 1) - 1;
|
|
2000
|
-
if (running <= 0) runningCount.delete(name);
|
|
2001
|
-
else runningCount.set(name, running);
|
|
2002
|
-
pump();
|
|
1989
|
+
parentPort.postMessage({
|
|
1990
|
+
type: 'error',
|
|
1991
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1992
|
+
});
|
|
2003
1993
|
}
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
`;
|
|
1997
|
+
}
|
|
1998
|
+
async function runTaskInWorker(options) {
|
|
1999
|
+
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
2000
|
+
const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
|
|
2001
|
+
const moduleUrl = pathToFileURL(taskModulePath).href;
|
|
2002
|
+
const wrapperUrl = new URL(
|
|
2003
|
+
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
2004
|
+
);
|
|
2005
|
+
return new Promise((resolve, reject) => {
|
|
2006
|
+
let worker;
|
|
2007
|
+
try {
|
|
2008
|
+
worker = new Worker(wrapperUrl);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
reject(err);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
let phase = "running";
|
|
2014
|
+
let cancelReason = "";
|
|
2015
|
+
let graceTimer;
|
|
2016
|
+
const finish = (settle) => {
|
|
2017
|
+
if (phase === "settled") return;
|
|
2018
|
+
phase = "settled";
|
|
2019
|
+
clearTimeout(timeoutTimer);
|
|
2020
|
+
if (graceTimer) clearTimeout(graceTimer);
|
|
2021
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
2022
|
+
void worker.terminate();
|
|
2023
|
+
settle();
|
|
2024
|
+
};
|
|
2025
|
+
const startCancel = (reason) => {
|
|
2026
|
+
if (phase !== "running") return;
|
|
2027
|
+
phase = "grace";
|
|
2028
|
+
cancelReason = reason;
|
|
2029
|
+
worker.postMessage({ type: "abort", reason });
|
|
2030
|
+
graceTimer = setTimeout(() => {
|
|
2031
|
+
finish(
|
|
2032
|
+
() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${reason} and was terminated`))
|
|
2033
|
+
);
|
|
2034
|
+
}, killGraceMs);
|
|
2035
|
+
};
|
|
2036
|
+
const onExternalAbort = () => startCancel("cancelled (driver stop timeout)");
|
|
2037
|
+
if (externalSignal) {
|
|
2038
|
+
if (externalSignal.aborted) {
|
|
2039
|
+
onExternalAbort();
|
|
2026
2040
|
} else {
|
|
2027
|
-
|
|
2028
|
-
if (!q) {
|
|
2029
|
-
q = [];
|
|
2030
|
-
pendingByTask.set(name, q);
|
|
2031
|
-
}
|
|
2032
|
-
q.push(id);
|
|
2033
|
-
pump();
|
|
2041
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
2034
2042
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2043
|
+
}
|
|
2044
|
+
const timeoutTimer = setTimeout(
|
|
2045
|
+
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
2046
|
+
timeoutMs
|
|
2047
|
+
);
|
|
2048
|
+
worker.on("message", (msg) => {
|
|
2049
|
+
if (phase === "grace") {
|
|
2050
|
+
if (msg?.type === "done") {
|
|
2051
|
+
finish(
|
|
2052
|
+
() => reject(
|
|
2053
|
+
new TaskCancelledError(
|
|
2054
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
2055
|
+
)
|
|
2056
|
+
)
|
|
2057
|
+
);
|
|
2058
|
+
} else if (msg?.type === "error") {
|
|
2059
|
+
finish(() => reject(new TaskCancelledError(msg.message ?? cancelReason)));
|
|
2047
2060
|
}
|
|
2061
|
+
return;
|
|
2048
2062
|
}
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2063
|
+
if (phase !== "running") return;
|
|
2064
|
+
if (msg?.type === "done") {
|
|
2065
|
+
finish(() => resolve(msg.result));
|
|
2066
|
+
} else if (msg?.type === "error") {
|
|
2067
|
+
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
2054
2068
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2069
|
+
});
|
|
2070
|
+
worker.on("error", (err) => {
|
|
2071
|
+
finish(() => reject(err));
|
|
2072
|
+
});
|
|
2073
|
+
worker.on("exit", (code) => {
|
|
2074
|
+
if (phase === "grace") {
|
|
2075
|
+
finish(() => reject(new TaskCancelledError(`Task "${taskCtx.job.name}" ${cancelReason}`)));
|
|
2076
|
+
} else if (phase === "running") {
|
|
2077
|
+
finish(() => reject(new Error(`Task worker exited unexpectedly (code ${code})`)));
|
|
2057
2078
|
}
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2079
|
+
});
|
|
2080
|
+
try {
|
|
2081
|
+
worker.postMessage({
|
|
2082
|
+
type: "run",
|
|
2083
|
+
payload,
|
|
2084
|
+
taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job }
|
|
2085
|
+
});
|
|
2086
|
+
} catch (err) {
|
|
2087
|
+
finish(
|
|
2088
|
+
() => reject(
|
|
2089
|
+
new Error(`Task "${taskCtx.job.name}" payload/config is not cloneable: ${String(err)}`)
|
|
2090
|
+
)
|
|
2091
|
+
);
|
|
2061
2092
|
}
|
|
2062
|
-
};
|
|
2093
|
+
});
|
|
2063
2094
|
}
|
|
2064
2095
|
|
|
2065
2096
|
// src/task/taskQueue.ts
|
|
2066
2097
|
function createTaskQueue(deps) {
|
|
2067
|
-
const { registry, rootDir } = deps;
|
|
2068
|
-
const driver = deps.driver ?? createMemoryDriver();
|
|
2098
|
+
const { registry, rootDir, driver } = deps;
|
|
2069
2099
|
const records = /* @__PURE__ */ new Map();
|
|
2070
2100
|
const moduleCache2 = /* @__PURE__ */ new Map();
|
|
2071
2101
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
@@ -2126,26 +2156,54 @@ function createTaskQueue(deps) {
|
|
|
2126
2156
|
record.error = void 0;
|
|
2127
2157
|
records.set(job.id, record);
|
|
2128
2158
|
try {
|
|
2129
|
-
let
|
|
2130
|
-
if (
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2159
|
+
let result;
|
|
2160
|
+
if (meta.timeoutMs !== void 0 && meta.timeoutMs > 0) {
|
|
2161
|
+
result = await (deps.runIsolated ?? runTaskInWorker)({
|
|
2162
|
+
taskModulePath: path3.resolve(rootDir, meta.filePath),
|
|
2163
|
+
payload: job.payload,
|
|
2164
|
+
taskCtx: {
|
|
2165
|
+
config: deps.config,
|
|
2166
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2167
|
+
},
|
|
2168
|
+
timeoutMs: meta.timeoutMs,
|
|
2169
|
+
externalSignal: job.signal
|
|
2170
|
+
});
|
|
2171
|
+
} else {
|
|
2172
|
+
let mod = moduleCache2.get(job.name);
|
|
2173
|
+
if (!mod) {
|
|
2174
|
+
mod = await loadTaskModule(path3.resolve(rootDir, meta.filePath));
|
|
2175
|
+
moduleCache2.set(job.name, mod);
|
|
2176
|
+
}
|
|
2177
|
+
if (typeof mod.run !== "function") {
|
|
2178
|
+
throw new Error(`Task "${job.name}" module has no run export`);
|
|
2179
|
+
}
|
|
2180
|
+
const taskCtx = {
|
|
2181
|
+
signal: job.signal,
|
|
2182
|
+
config: deps.config,
|
|
2183
|
+
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2184
|
+
};
|
|
2185
|
+
result = await mod.run(job.payload, taskCtx);
|
|
2136
2186
|
}
|
|
2137
|
-
const taskCtx = {
|
|
2138
|
-
signal: job.signal,
|
|
2139
|
-
config: deps.config,
|
|
2140
|
-
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2141
|
-
};
|
|
2142
|
-
const result = await mod.run(job.payload, taskCtx);
|
|
2143
2187
|
record.status = "done";
|
|
2144
2188
|
record.result = result;
|
|
2145
2189
|
return result;
|
|
2146
2190
|
} catch (err) {
|
|
2147
|
-
|
|
2191
|
+
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
2192
|
+
record.status = cancelled ? "cancelled" : "failed";
|
|
2148
2193
|
record.error = err instanceof Error ? err.message : String(err);
|
|
2194
|
+
if (deps.onFailed) {
|
|
2195
|
+
void Promise.resolve().then(
|
|
2196
|
+
() => deps.onFailed({
|
|
2197
|
+
task: job.name,
|
|
2198
|
+
jobId: job.id,
|
|
2199
|
+
attempt: job.attempt,
|
|
2200
|
+
willRetry: job.attempt <= (meta.retries ?? 0),
|
|
2201
|
+
cancelled,
|
|
2202
|
+
error: record.error
|
|
2203
|
+
})
|
|
2204
|
+
).catch(() => {
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2149
2207
|
throw err;
|
|
2150
2208
|
}
|
|
2151
2209
|
}
|
|
@@ -2167,7 +2225,8 @@ function createTaskQueue(deps) {
|
|
|
2167
2225
|
const meta = registry.get(name);
|
|
2168
2226
|
const id = await driver.enqueue(name, data, {
|
|
2169
2227
|
delayMs: opts?.delayMs,
|
|
2170
|
-
retries: meta.retries ?? 0
|
|
2228
|
+
retries: meta.retries ?? 0,
|
|
2229
|
+
dedupId: opts?.dedupId
|
|
2171
2230
|
});
|
|
2172
2231
|
if (!records.has(id)) {
|
|
2173
2232
|
records.set(id, {
|
|
@@ -2190,6 +2249,54 @@ function createTaskQueue(deps) {
|
|
|
2190
2249
|
}
|
|
2191
2250
|
return snapshot;
|
|
2192
2251
|
},
|
|
2252
|
+
async listQueued(name) {
|
|
2253
|
+
if (!driver.list) {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
"[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."
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
const queued = await driver.list({ name, limit: 50 });
|
|
2259
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2260
|
+
for (const record of queued) {
|
|
2261
|
+
if (name !== void 0 && record.name !== name) continue;
|
|
2262
|
+
merged.set(record.id, {
|
|
2263
|
+
id: record.id,
|
|
2264
|
+
name: record.name,
|
|
2265
|
+
payload: record.payload,
|
|
2266
|
+
status: record.status,
|
|
2267
|
+
attempts: record.attempts,
|
|
2268
|
+
createdAt: record.createdAt,
|
|
2269
|
+
...record.result !== void 0 ? { result: record.result } : {},
|
|
2270
|
+
...record.error !== void 0 ? { error: record.error } : {},
|
|
2271
|
+
...record.runAt !== void 0 ? { runAt: record.runAt } : {}
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
for (const local of records.values()) {
|
|
2275
|
+
if (name !== void 0 && local.name !== name) continue;
|
|
2276
|
+
merged.set(local.id, { ...local });
|
|
2277
|
+
}
|
|
2278
|
+
return [...merged.values()];
|
|
2279
|
+
},
|
|
2280
|
+
async cancel(name, id) {
|
|
2281
|
+
if (!driver.cancel) {
|
|
2282
|
+
throw new Error(
|
|
2283
|
+
"[faapi] Task driver does not support cancelling tasks (TaskDriver.cancel is not implemented)."
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
await driver.cancel(name, id);
|
|
2287
|
+
const record = records.get(id);
|
|
2288
|
+
if (record) record.status = "cancelled";
|
|
2289
|
+
},
|
|
2290
|
+
async retry(name, id) {
|
|
2291
|
+
if (!driver.retry) {
|
|
2292
|
+
throw new Error(
|
|
2293
|
+
"[faapi] Task driver does not support retrying tasks (TaskDriver.retry is not implemented)."
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
await driver.retry(name, id);
|
|
2297
|
+
const record = records.get(id);
|
|
2298
|
+
if (record) record.status = "pending";
|
|
2299
|
+
},
|
|
2193
2300
|
async start() {
|
|
2194
2301
|
if (stopped) {
|
|
2195
2302
|
throw new Error("[faapi] Task queue is stopped and cannot be restarted");
|
|
@@ -2218,15 +2325,22 @@ function createTaskQueue(deps) {
|
|
|
2218
2325
|
|
|
2219
2326
|
// src/task/loadTaskDriver.ts
|
|
2220
2327
|
async function loadTaskDriver(driver, driverOptions) {
|
|
2221
|
-
if (driver === void 0
|
|
2222
|
-
|
|
2328
|
+
if (driver === void 0) {
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
"[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)."
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
if (driver === "memory") {
|
|
2334
|
+
throw new Error(
|
|
2335
|
+
"[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'."
|
|
2336
|
+
);
|
|
2223
2337
|
}
|
|
2224
2338
|
if (typeof driver === "object") {
|
|
2225
2339
|
return driver;
|
|
2226
2340
|
}
|
|
2227
2341
|
if (driver !== "pgboss" && driver !== "bullmq") {
|
|
2228
2342
|
throw new Error(
|
|
2229
|
-
`[faapi] Unknown task driver "${driver}". Supported: '
|
|
2343
|
+
`[faapi] Unknown task driver "${driver}". Supported: 'pgboss', 'bullmq', or a TaskDriver instance.`
|
|
2230
2344
|
);
|
|
2231
2345
|
}
|
|
2232
2346
|
const specifier = `@faapi/task-${driver}`;
|
|
@@ -2260,13 +2374,17 @@ function createCronScheduler(registry, enqueue) {
|
|
|
2260
2374
|
this.stop();
|
|
2261
2375
|
for (const task of registry.list()) {
|
|
2262
2376
|
if (!task.cron) continue;
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2377
|
+
const schedule = new Cron(task.cron, () => {
|
|
2378
|
+
const runAt = schedule.currentRun();
|
|
2379
|
+
void Promise.resolve().then(
|
|
2380
|
+
() => enqueue(task.name, {
|
|
2381
|
+
dedupId: runAt ? `cron:${task.name}:${runAt.toISOString()}` : void 0
|
|
2382
|
+
})
|
|
2383
|
+
).catch((err) => {
|
|
2384
|
+
console.error(`[faapi] Cron enqueue failed for task "${task.name}":`, err);
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
schedules.push(schedule);
|
|
2270
2388
|
}
|
|
2271
2389
|
},
|
|
2272
2390
|
stop() {
|
|
@@ -2287,6 +2405,7 @@ var TASK_FILENAME = "task.ts";
|
|
|
2287
2405
|
var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
2288
2406
|
var CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
|
|
2289
2407
|
var RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
|
|
2408
|
+
var TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
|
|
2290
2409
|
function filePathToTaskName(filePath) {
|
|
2291
2410
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2292
2411
|
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
@@ -2323,9 +2442,11 @@ async function scanTasks(rootDir, patterns) {
|
|
|
2323
2442
|
const cron = CRON_RE.exec(source)?.[1];
|
|
2324
2443
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
2325
2444
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
2445
|
+
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
2326
2446
|
if (cron !== void 0) manifest.cron = cron;
|
|
2327
2447
|
if (concurrency !== void 0) manifest.concurrency = Number(concurrency);
|
|
2328
2448
|
if (retries !== void 0) manifest.retries = Number(retries);
|
|
2449
|
+
if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
|
|
2329
2450
|
tasks.push(manifest);
|
|
2330
2451
|
}
|
|
2331
2452
|
return tasks;
|
|
@@ -2389,7 +2510,7 @@ function resolveExport(module, exportName) {
|
|
|
2389
2510
|
}
|
|
2390
2511
|
|
|
2391
2512
|
// src/utils/importWithCacheBust.ts
|
|
2392
|
-
import { pathToFileURL } from "url";
|
|
2513
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2393
2514
|
var loadTs;
|
|
2394
2515
|
function setLoadTimestamp(ts11) {
|
|
2395
2516
|
loadTs = ts11;
|
|
@@ -2403,13 +2524,13 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
2403
2524
|
const importActual = getVitestImportActual();
|
|
2404
2525
|
if (importActual) {
|
|
2405
2526
|
if (bustViteCache) {
|
|
2406
|
-
let url2 =
|
|
2527
|
+
let url2 = pathToFileURL2(filePath).href;
|
|
2407
2528
|
url2 += `?t=${Date.now()}`;
|
|
2408
2529
|
return await import(url2);
|
|
2409
2530
|
}
|
|
2410
2531
|
return await importActual(filePath);
|
|
2411
2532
|
}
|
|
2412
|
-
let url =
|
|
2533
|
+
let url = pathToFileURL2(filePath).href;
|
|
2413
2534
|
if (loadTs !== void 0) {
|
|
2414
2535
|
url += `?t=${loadTs}`;
|
|
2415
2536
|
}
|
|
@@ -5891,7 +6012,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
5891
6012
|
filePath: toProdFilePath(t.filePath, dist),
|
|
5892
6013
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
5893
6014
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
5894
|
-
...t.retries !== void 0 ? { retries: t.retries } : {}
|
|
6015
|
+
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6016
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
5895
6017
|
}));
|
|
5896
6018
|
}
|
|
5897
6019
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -5906,7 +6028,8 @@ function hydrateTasks(manifest) {
|
|
|
5906
6028
|
filePath: t.filePath,
|
|
5907
6029
|
cron: t.cron ?? void 0,
|
|
5908
6030
|
concurrency: t.concurrency ?? void 0,
|
|
5909
|
-
retries: t.retries ?? void 0
|
|
6031
|
+
retries: t.retries ?? void 0,
|
|
6032
|
+
timeoutMs: t.timeoutMs ?? void 0
|
|
5910
6033
|
}));
|
|
5911
6034
|
}
|
|
5912
6035
|
function collectTaskSchemaSources(tasks, rootDir) {
|
|
@@ -6034,10 +6157,29 @@ async function generateTaskArtifacts(manifests, rootDir, dist) {
|
|
|
6034
6157
|
return hydrateTasks(serialized);
|
|
6035
6158
|
}
|
|
6036
6159
|
|
|
6160
|
+
// src/task/idleTaskDriver.ts
|
|
6161
|
+
function createIdleTaskDriver() {
|
|
6162
|
+
return {
|
|
6163
|
+
enqueue() {
|
|
6164
|
+
return Promise.reject(
|
|
6165
|
+
new Error(
|
|
6166
|
+
"[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."
|
|
6167
|
+
)
|
|
6168
|
+
);
|
|
6169
|
+
},
|
|
6170
|
+
startWorker() {
|
|
6171
|
+
},
|
|
6172
|
+
async stop() {
|
|
6173
|
+
},
|
|
6174
|
+
async stopWorkers() {
|
|
6175
|
+
}
|
|
6176
|
+
};
|
|
6177
|
+
}
|
|
6178
|
+
|
|
6037
6179
|
// src/cli/loadPlugins.ts
|
|
6038
6180
|
import path22 from "path";
|
|
6039
6181
|
import fs17 from "fs";
|
|
6040
|
-
import { pathToFileURL as
|
|
6182
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
6041
6183
|
|
|
6042
6184
|
// src/cli/compileConfig.ts
|
|
6043
6185
|
import path21 from "path";
|
|
@@ -6172,7 +6314,7 @@ async function importPluginModule(specifier, baseDir, dist) {
|
|
|
6172
6314
|
const sourcePath = resolveLocalPluginSource(specifier, baseDir);
|
|
6173
6315
|
const productPath = sourcePath && distDir ? mirrorProductPath(sourcePath, baseDir, distDir) : null;
|
|
6174
6316
|
if (productPath !== null && fs17.existsSync(productPath) && (sourcePath === null || !isSourceNewer(sourcePath, productPath))) {
|
|
6175
|
-
return import(
|
|
6317
|
+
return import(pathToFileURL3(productPath).href);
|
|
6176
6318
|
}
|
|
6177
6319
|
if (sourcePath && TS_EXTS.test(sourcePath)) {
|
|
6178
6320
|
if (!isDevOnDemandEnabled() || !distDir) {
|
|
@@ -6187,11 +6329,11 @@ async function importPluginModule(specifier, baseDir, dist) {
|
|
|
6187
6329
|
await compileProjectModules([sourcePath], baseDir, dist);
|
|
6188
6330
|
}
|
|
6189
6331
|
if (productPath && fs17.existsSync(productPath)) {
|
|
6190
|
-
return import(
|
|
6332
|
+
return import(pathToFileURL3(productPath).href);
|
|
6191
6333
|
}
|
|
6192
6334
|
}
|
|
6193
6335
|
if (sourcePath && JS_EXTS.test(sourcePath)) {
|
|
6194
|
-
return import(
|
|
6336
|
+
return import(pathToFileURL3(sourcePath).href);
|
|
6195
6337
|
}
|
|
6196
6338
|
const candidates = [
|
|
6197
6339
|
`${specifier}.ts`,
|
|
@@ -6250,10 +6392,8 @@ async function loadAndHydrateAgents(rootDir, dist, registries = defaultRegistrie
|
|
|
6250
6392
|
}
|
|
6251
6393
|
function getTaskDriverOptions(config) {
|
|
6252
6394
|
if (!config?.task) return void 0;
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
if (driver === "pgboss") return taskConfig.pgboss;
|
|
6256
|
-
if (driver === "bullmq") return taskConfig.bullmq;
|
|
6395
|
+
if (config.task.driver === "pgboss") return config.task.pgboss;
|
|
6396
|
+
if (config.task.driver === "bullmq") return config.task.bullmq;
|
|
6257
6397
|
return void 0;
|
|
6258
6398
|
}
|
|
6259
6399
|
async function loadAndHydrateTasks(rootDir, dist, registries = defaultRegistries) {
|
|
@@ -6350,16 +6490,17 @@ async function createAppBase(options) {
|
|
|
6350
6490
|
const tools = await loadAndHydrateTools(rootDir, dist, registries);
|
|
6351
6491
|
const agents = await loadAndHydrateAgents(rootDir, dist, registries);
|
|
6352
6492
|
const taskMetas = await loadAndHydrateTasks(rootDir, dist, registries);
|
|
6353
|
-
const taskDriver = await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config));
|
|
6493
|
+
const taskDriver = taskMetas.length ? await loadTaskDriver(config?.task?.driver, getTaskDriverOptions(config)) : createIdleTaskDriver();
|
|
6354
6494
|
const taskQueue = createTaskQueue({
|
|
6355
6495
|
registry: registries.task,
|
|
6356
6496
|
rootDir,
|
|
6357
6497
|
config,
|
|
6358
|
-
driver: taskDriver
|
|
6498
|
+
driver: taskDriver,
|
|
6499
|
+
onFailed: config?.task?.onFailed
|
|
6359
6500
|
});
|
|
6360
6501
|
const cronScheduler = createCronScheduler(
|
|
6361
6502
|
registries.task,
|
|
6362
|
-
(name) => taskQueue.enqueue(name)
|
|
6503
|
+
(name, opts) => taskQueue.enqueue(name, void 0, opts)
|
|
6363
6504
|
);
|
|
6364
6505
|
const taskEnabled = process.env.FAAPI_TASKS_DISABLED === "1" ? false : config?.task?.enabled ?? true;
|
|
6365
6506
|
const stopTaskRuntime = async () => {
|
|
@@ -6367,7 +6508,7 @@ async function createAppBase(options) {
|
|
|
6367
6508
|
await taskQueue.stop(config?.task?.shutdownTimeoutMs ?? 1e4);
|
|
6368
6509
|
};
|
|
6369
6510
|
if (taskEnabled) {
|
|
6370
|
-
taskQueue.start();
|
|
6511
|
+
await taskQueue.start();
|
|
6371
6512
|
cronScheduler.start();
|
|
6372
6513
|
}
|
|
6373
6514
|
registries.taskHandle.register(() => taskQueue);
|
|
@@ -7034,7 +7175,6 @@ export {
|
|
|
7034
7175
|
createAppRegistries,
|
|
7035
7176
|
createCronScheduler,
|
|
7036
7177
|
createDevApp,
|
|
7037
|
-
createMemoryDriver,
|
|
7038
7178
|
createProdApp,
|
|
7039
7179
|
createProgram,
|
|
7040
7180
|
createPrograms,
|