@faapi/faapi 6.6.0 → 6.8.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 +336 -45
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +43 -3
- package/dist/index.js +324 -41
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-q9OXB--o.d.ts → routeTypes-J5pPbjwR.d.ts} +142 -6
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +97 -1
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2260,8 +2260,10 @@ var init_resolveInjection = __esm({
|
|
|
2260
2260
|
// Phase 2.3
|
|
2261
2261
|
agents: "agents",
|
|
2262
2262
|
// Phase 2.3
|
|
2263
|
-
tasks: "tasks"
|
|
2263
|
+
tasks: "tasks",
|
|
2264
2264
|
// 任务子系统:TaskClient(入队/查询)
|
|
2265
|
+
log: "log"
|
|
2266
|
+
// 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
|
|
2265
2267
|
};
|
|
2266
2268
|
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
2267
2269
|
}
|
|
@@ -3376,6 +3378,9 @@ var init_generateAgentArtifacts = __esm({
|
|
|
3376
3378
|
import fg4 from "fast-glob";
|
|
3377
3379
|
import path16 from "path";
|
|
3378
3380
|
import fs12 from "fs";
|
|
3381
|
+
function parseNumericLiteral(raw) {
|
|
3382
|
+
return Number(raw.replace(/_/g, ""));
|
|
3383
|
+
}
|
|
3379
3384
|
function filePathToTaskName(filePath) {
|
|
3380
3385
|
const normalized = filePath.replace(/\\/g, "/");
|
|
3381
3386
|
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
@@ -3413,24 +3418,37 @@ async function scanTasks(rootDir, patterns) {
|
|
|
3413
3418
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
3414
3419
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
3415
3420
|
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
3421
|
+
const graceMs = GRACE_MS_RE.exec(source)?.[1];
|
|
3416
3422
|
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)
|
|
3423
|
+
if (concurrency !== void 0) manifest.concurrency = parseNumericLiteral(concurrency);
|
|
3424
|
+
if (retries !== void 0) manifest.retries = parseNumericLiteral(retries);
|
|
3425
|
+
if (timeoutMs !== void 0) {
|
|
3426
|
+
const value = parseNumericLiteral(timeoutMs);
|
|
3427
|
+
if (value < MIN_ISOLATED_TIMEOUT_MS) {
|
|
3428
|
+
throw new Error(
|
|
3429
|
+
`[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.`
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3432
|
+
manifest.timeoutMs = value;
|
|
3433
|
+
}
|
|
3434
|
+
if (graceMs !== void 0) manifest.graceMs = parseNumericLiteral(graceMs);
|
|
3420
3435
|
tasks.push(manifest);
|
|
3421
3436
|
}
|
|
3422
3437
|
return tasks.sort((a, b) => a.name.localeCompare(b.name));
|
|
3423
3438
|
}
|
|
3424
|
-
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE;
|
|
3439
|
+
var TASK_PATTERNS, TASK_FILENAME, CRON_RE, NUM_LITERAL, CONCURRENCY_RE, RETRIES_RE, TIMEOUT_MS_RE, GRACE_MS_RE, MIN_ISOLATED_TIMEOUT_MS;
|
|
3425
3440
|
var init_scanTasks = __esm({
|
|
3426
3441
|
"src/task/scanTasks.ts"() {
|
|
3427
3442
|
"use strict";
|
|
3428
3443
|
TASK_PATTERNS = ["src/tasks/**/task.ts"];
|
|
3429
3444
|
TASK_FILENAME = "task.ts";
|
|
3430
3445
|
CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3446
|
+
NUM_LITERAL = "(\\d+(?:_\\d+)*)";
|
|
3447
|
+
CONCURRENCY_RE = new RegExp(`(?:^|\\n)\\s*concurrency:\\s*${NUM_LITERAL}`);
|
|
3448
|
+
RETRIES_RE = new RegExp(`(?:^|\\n)\\s*retries:\\s*${NUM_LITERAL}`);
|
|
3449
|
+
TIMEOUT_MS_RE = new RegExp(`(?:^|\\n)\\s*timeoutMs:\\s*${NUM_LITERAL}`);
|
|
3450
|
+
GRACE_MS_RE = new RegExp(`(?:^|\\n)\\s*graceMs:\\s*${NUM_LITERAL}`);
|
|
3451
|
+
MIN_ISOLATED_TIMEOUT_MS = 6e4;
|
|
3434
3452
|
}
|
|
3435
3453
|
});
|
|
3436
3454
|
|
|
@@ -3443,7 +3461,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
3443
3461
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
3444
3462
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
3445
3463
|
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
3446
|
-
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
3464
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
3465
|
+
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
3447
3466
|
}));
|
|
3448
3467
|
}
|
|
3449
3468
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -3459,7 +3478,8 @@ function hydrateTasks(manifest) {
|
|
|
3459
3478
|
cron: t.cron ?? void 0,
|
|
3460
3479
|
concurrency: t.concurrency ?? void 0,
|
|
3461
3480
|
retries: t.retries ?? void 0,
|
|
3462
|
-
timeoutMs: t.timeoutMs ?? void 0
|
|
3481
|
+
timeoutMs: t.timeoutMs ?? void 0,
|
|
3482
|
+
graceMs: t.graceMs ?? void 0
|
|
3463
3483
|
}));
|
|
3464
3484
|
}
|
|
3465
3485
|
function collectTaskSchemaSources(tasks, rootDir) {
|
|
@@ -6368,7 +6388,131 @@ var init_responseFormatter = __esm({
|
|
|
6368
6388
|
}
|
|
6369
6389
|
});
|
|
6370
6390
|
|
|
6391
|
+
// src/logger/logger.ts
|
|
6392
|
+
function assertLogLevel(value, source) {
|
|
6393
|
+
if (typeof value !== "string" || !VALID_LEVELS.includes(value)) {
|
|
6394
|
+
throw new Error(
|
|
6395
|
+
`[faapi] Invalid log level from ${source}: ${String(value)}. Expected one of: ${VALID_LEVELS.join(", ")}.`
|
|
6396
|
+
);
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
function configureLogging(config) {
|
|
6400
|
+
if (config === void 0) {
|
|
6401
|
+
globalState.level = null;
|
|
6402
|
+
globalState.sink = null;
|
|
6403
|
+
globalState.disabled = false;
|
|
6404
|
+
return;
|
|
6405
|
+
}
|
|
6406
|
+
const objectConfig = typeof config === "object" ? config : {};
|
|
6407
|
+
globalState.disabled = config === false;
|
|
6408
|
+
globalState.sink = objectConfig.sink ?? null;
|
|
6409
|
+
if (objectConfig.level !== void 0) {
|
|
6410
|
+
assertLogLevel(objectConfig.level, "config.log.level");
|
|
6411
|
+
globalState.level = objectConfig.level;
|
|
6412
|
+
} else if (process.env.LOG_LEVEL !== void 0) {
|
|
6413
|
+
assertLogLevel(process.env.LOG_LEVEL, "LOG_LEVEL env");
|
|
6414
|
+
globalState.level = process.env.LOG_LEVEL;
|
|
6415
|
+
} else {
|
|
6416
|
+
globalState.level = null;
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
function globalThreshold() {
|
|
6420
|
+
return globalState.level ?? "info";
|
|
6421
|
+
}
|
|
6422
|
+
function getEffectiveLogLevel() {
|
|
6423
|
+
return globalThreshold();
|
|
6424
|
+
}
|
|
6425
|
+
function serializeFieldValue(value) {
|
|
6426
|
+
if (value instanceof Error) {
|
|
6427
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
6428
|
+
}
|
|
6429
|
+
return value;
|
|
6430
|
+
}
|
|
6431
|
+
function formatEntry(entry) {
|
|
6432
|
+
let line = `[${entry.time}] ${entry.level.toUpperCase()} `;
|
|
6433
|
+
if (entry.scope) line += `[${entry.scope}] `;
|
|
6434
|
+
line += entry.message;
|
|
6435
|
+
if (entry.fields !== void 0) {
|
|
6436
|
+
const plain = Object.fromEntries(
|
|
6437
|
+
Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)])
|
|
6438
|
+
);
|
|
6439
|
+
try {
|
|
6440
|
+
line += ` ${JSON.stringify(plain)}`;
|
|
6441
|
+
} catch (err) {
|
|
6442
|
+
line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
return line;
|
|
6446
|
+
}
|
|
6447
|
+
function globalDispatch(entry) {
|
|
6448
|
+
if (globalState.disabled) return;
|
|
6449
|
+
(globalState.sink ?? consoleSink)(entry);
|
|
6450
|
+
}
|
|
6451
|
+
function writeLogEntry(entry) {
|
|
6452
|
+
if (LEVEL_RANK[entry.level] < LEVEL_RANK[globalThreshold()]) return;
|
|
6453
|
+
globalDispatch(entry);
|
|
6454
|
+
}
|
|
6455
|
+
function createLogger(scope, options) {
|
|
6456
|
+
const baseScope = scope;
|
|
6457
|
+
const baseFields = options?.fields;
|
|
6458
|
+
const write = (level, message, callFields, scopeSuffix) => {
|
|
6459
|
+
const threshold = options?.level ?? globalThreshold();
|
|
6460
|
+
if (LEVEL_RANK[level] < LEVEL_RANK[threshold]) return;
|
|
6461
|
+
const entry = {
|
|
6462
|
+
level,
|
|
6463
|
+
message,
|
|
6464
|
+
time: (/* @__PURE__ */ new Date()).toISOString()
|
|
6465
|
+
};
|
|
6466
|
+
const fullScope = scopeSuffix === void 0 ? baseScope : baseScope === void 0 ? scopeSuffix : `${baseScope}:${scopeSuffix}`;
|
|
6467
|
+
if (fullScope !== void 0) entry.scope = fullScope;
|
|
6468
|
+
if (baseFields !== void 0 || callFields !== void 0) {
|
|
6469
|
+
entry.fields = { ...baseFields, ...callFields };
|
|
6470
|
+
}
|
|
6471
|
+
if (options?.sink) {
|
|
6472
|
+
options.sink(entry);
|
|
6473
|
+
} else {
|
|
6474
|
+
globalDispatch(entry);
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
const makeLogger = (scopeSuffix) => ({
|
|
6478
|
+
debug: (message, fields) => write("debug", message, fields, scopeSuffix),
|
|
6479
|
+
info: (message, fields) => write("info", message, fields, scopeSuffix),
|
|
6480
|
+
warn: (message, fields) => write("warn", message, fields, scopeSuffix),
|
|
6481
|
+
error: (message, fields) => write("error", message, fields, scopeSuffix),
|
|
6482
|
+
child: (childScope) => makeLogger(scopeSuffix === void 0 ? childScope : `${scopeSuffix}:${childScope}`)
|
|
6483
|
+
});
|
|
6484
|
+
return makeLogger(void 0);
|
|
6485
|
+
}
|
|
6486
|
+
var LEVEL_RANK, VALID_LEVELS, globalState, CONSOLE_METHOD, consoleSink;
|
|
6487
|
+
var init_logger = __esm({
|
|
6488
|
+
"src/logger/logger.ts"() {
|
|
6489
|
+
"use strict";
|
|
6490
|
+
LEVEL_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
6491
|
+
VALID_LEVELS = ["debug", "info", "warn", "error"];
|
|
6492
|
+
globalState = {
|
|
6493
|
+
level: null,
|
|
6494
|
+
sink: null,
|
|
6495
|
+
disabled: false
|
|
6496
|
+
};
|
|
6497
|
+
CONSOLE_METHOD = {
|
|
6498
|
+
debug: "debug",
|
|
6499
|
+
info: "info",
|
|
6500
|
+
warn: "warn",
|
|
6501
|
+
error: "error"
|
|
6502
|
+
};
|
|
6503
|
+
consoleSink = (entry) => {
|
|
6504
|
+
console[CONSOLE_METHOD[entry.level]](formatEntry(entry));
|
|
6505
|
+
};
|
|
6506
|
+
}
|
|
6507
|
+
});
|
|
6508
|
+
|
|
6371
6509
|
// src/runtime/createContext.ts
|
|
6510
|
+
function resolveRequestId(request) {
|
|
6511
|
+
const header = request.headers.get("x-request-id");
|
|
6512
|
+
const first = header?.split(",")[0]?.trim();
|
|
6513
|
+
if (first) return first;
|
|
6514
|
+
return crypto.randomUUID();
|
|
6515
|
+
}
|
|
6372
6516
|
function parseCookies(cookieHeader) {
|
|
6373
6517
|
const cookies = /* @__PURE__ */ new Map();
|
|
6374
6518
|
if (!cookieHeader) return cookies;
|
|
@@ -6402,7 +6546,13 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
6402
6546
|
for (const [key, val] of parsedCookies) {
|
|
6403
6547
|
cookiesObj[key] = val;
|
|
6404
6548
|
}
|
|
6549
|
+
const requestId = resolveRequestId(request);
|
|
6550
|
+
const log = createLogger("http", {
|
|
6551
|
+
fields: { requestId, method: request.method, path: url.pathname }
|
|
6552
|
+
});
|
|
6405
6553
|
const ctx = {
|
|
6554
|
+
requestId,
|
|
6555
|
+
log,
|
|
6406
6556
|
request,
|
|
6407
6557
|
params,
|
|
6408
6558
|
query: url.searchParams,
|
|
@@ -6507,6 +6657,7 @@ var init_createContext = __esm({
|
|
|
6507
6657
|
"use strict";
|
|
6508
6658
|
init_sse();
|
|
6509
6659
|
init_responseFormatter();
|
|
6660
|
+
init_logger();
|
|
6510
6661
|
}
|
|
6511
6662
|
});
|
|
6512
6663
|
|
|
@@ -7074,6 +7225,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
7074
7225
|
// 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
|
|
7075
7226
|
case "tasks":
|
|
7076
7227
|
return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
|
|
7228
|
+
// 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)
|
|
7229
|
+
case "log":
|
|
7230
|
+
return ctx.log;
|
|
7077
7231
|
default:
|
|
7078
7232
|
return void 0;
|
|
7079
7233
|
}
|
|
@@ -7698,6 +7852,7 @@ function logger(options = {}) {
|
|
|
7698
7852
|
const response = await next();
|
|
7699
7853
|
const duration = Date.now() - start;
|
|
7700
7854
|
const entry = {
|
|
7855
|
+
requestId: ctx.requestId,
|
|
7701
7856
|
method: ctx.method,
|
|
7702
7857
|
path: ctx.path,
|
|
7703
7858
|
status: response.status,
|
|
@@ -7710,6 +7865,7 @@ function logger(options = {}) {
|
|
|
7710
7865
|
const message = err instanceof Error ? err.message : String(err);
|
|
7711
7866
|
const status = err?.statusCode ?? 500;
|
|
7712
7867
|
const entry = {
|
|
7868
|
+
requestId: ctx.requestId,
|
|
7713
7869
|
method: ctx.method,
|
|
7714
7870
|
path: ctx.path,
|
|
7715
7871
|
status,
|
|
@@ -7721,7 +7877,7 @@ function logger(options = {}) {
|
|
|
7721
7877
|
}
|
|
7722
7878
|
};
|
|
7723
7879
|
}
|
|
7724
|
-
var
|
|
7880
|
+
var init_logger2 = __esm({
|
|
7725
7881
|
"src/middleware/logger.ts"() {
|
|
7726
7882
|
"use strict";
|
|
7727
7883
|
}
|
|
@@ -8299,7 +8455,7 @@ var init_createServer = __esm({
|
|
|
8299
8455
|
init_helmet();
|
|
8300
8456
|
init_compression();
|
|
8301
8457
|
init_etag();
|
|
8302
|
-
|
|
8458
|
+
init_logger2();
|
|
8303
8459
|
init_handleWsUpgrade();
|
|
8304
8460
|
init_serverUtils();
|
|
8305
8461
|
init_generateSchemaFiles();
|
|
@@ -8366,9 +8522,16 @@ import * as mod from '${moduleUrl}';
|
|
|
8366
8522
|
|
|
8367
8523
|
${BUILD_VIEW_SOURCE}
|
|
8368
8524
|
|
|
8525
|
+
${SERIALIZE_ERROR_SOURCE}
|
|
8526
|
+
|
|
8527
|
+
${BUILD_LOGGER_SOURCE}
|
|
8528
|
+
|
|
8369
8529
|
const run = mod.run;
|
|
8370
8530
|
if (typeof run !== 'function') {
|
|
8371
|
-
parentPort.postMessage({
|
|
8531
|
+
parentPort.postMessage({
|
|
8532
|
+
type: 'error',
|
|
8533
|
+
error: { name: 'Error', message: 'Task module has no run export', stack: undefined, props: {} },
|
|
8534
|
+
});
|
|
8372
8535
|
} else {
|
|
8373
8536
|
let controller = null;
|
|
8374
8537
|
parentPort.on('message', async (msg) => {
|
|
@@ -8381,21 +8544,45 @@ if (typeof run !== 'function') {
|
|
|
8381
8544
|
const { payload, taskCtx } = msg;
|
|
8382
8545
|
try {
|
|
8383
8546
|
const registries = buildRegistriesView(msg.registries);
|
|
8384
|
-
const
|
|
8547
|
+
const progress = (value) => parentPort.postMessage({ type: 'progress', value });
|
|
8548
|
+
const taskLog = msg.log ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields) : undefined;
|
|
8549
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal, registries, progress, log: taskLog });
|
|
8385
8550
|
parentPort.postMessage({ type: 'done', result });
|
|
8386
8551
|
} catch (err) {
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
})
|
|
8552
|
+
const serialized = serializeError(err);
|
|
8553
|
+
try {
|
|
8554
|
+
parentPort.postMessage({ type: 'error', error: serialized });
|
|
8555
|
+
} catch (_) {
|
|
8556
|
+
// props \u542B\u4E0D\u53EF\u514B\u9686\u503C\uFF08\u51FD\u6570\u7B49\uFF09\u65F6\u4E22\u5F03 props\uFF0C\u4FDD\u5E95 name/message/stack
|
|
8557
|
+
parentPort.postMessage({
|
|
8558
|
+
type: 'error',
|
|
8559
|
+
error: { name: serialized.name, message: serialized.message, stack: serialized.stack, props: {} },
|
|
8560
|
+
});
|
|
8561
|
+
}
|
|
8391
8562
|
}
|
|
8392
8563
|
});
|
|
8393
8564
|
}
|
|
8394
8565
|
`;
|
|
8395
8566
|
}
|
|
8567
|
+
function reviveError(payload) {
|
|
8568
|
+
const err = new Error(payload?.message ?? "task worker error");
|
|
8569
|
+
if (payload?.name !== void 0) err.name = payload.name;
|
|
8570
|
+
if (payload?.stack !== void 0) err.stack = payload.stack;
|
|
8571
|
+
if (payload?.props) {
|
|
8572
|
+
for (const [key, value] of Object.entries(payload.props)) {
|
|
8573
|
+
err[key] = value;
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
return err;
|
|
8577
|
+
}
|
|
8396
8578
|
async function runTaskInWorker(options) {
|
|
8397
8579
|
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
8398
|
-
const killGraceMs = options.
|
|
8580
|
+
const killGraceMs = options.graceMs ?? KILL_GRACE_MS;
|
|
8581
|
+
if (externalSignal?.aborted) {
|
|
8582
|
+
throw new TaskCancelledError(
|
|
8583
|
+
`Task "${taskCtx.job.name}" cancelled before start (driver stop signal already aborted)`
|
|
8584
|
+
);
|
|
8585
|
+
}
|
|
8399
8586
|
const moduleUrl = pathToFileURL2(taskModulePath).href;
|
|
8400
8587
|
const wrapperUrl = new URL(
|
|
8401
8588
|
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
@@ -8443,27 +8630,39 @@ async function runTaskInWorker(options) {
|
|
|
8443
8630
|
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
8444
8631
|
timeoutMs
|
|
8445
8632
|
);
|
|
8446
|
-
worker.on(
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8633
|
+
worker.on(
|
|
8634
|
+
"message",
|
|
8635
|
+
(msg) => {
|
|
8636
|
+
if (phase === "grace") {
|
|
8637
|
+
if (msg?.type === "done") {
|
|
8638
|
+
finish(
|
|
8639
|
+
() => reject(
|
|
8640
|
+
new TaskCancelledError(
|
|
8641
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
8642
|
+
)
|
|
8453
8643
|
)
|
|
8454
|
-
)
|
|
8455
|
-
)
|
|
8644
|
+
);
|
|
8645
|
+
} else if (msg?.type === "error") {
|
|
8646
|
+
finish(() => reject(new TaskCancelledError(msg.error?.message ?? cancelReason)));
|
|
8647
|
+
}
|
|
8648
|
+
return;
|
|
8649
|
+
}
|
|
8650
|
+
if (phase !== "running") return;
|
|
8651
|
+
if (msg?.type === "done") {
|
|
8652
|
+
finish(() => resolve3(msg.result));
|
|
8456
8653
|
} else if (msg?.type === "error") {
|
|
8457
|
-
finish(() => reject(
|
|
8654
|
+
finish(() => reject(reviveError(msg.error)));
|
|
8655
|
+
} else if (msg?.type === "progress") {
|
|
8656
|
+
options.onProgress?.(msg.value);
|
|
8657
|
+
} else if (msg?.type === "log") {
|
|
8658
|
+
options.onLog?.(msg.entry);
|
|
8458
8659
|
}
|
|
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
8660
|
}
|
|
8661
|
+
);
|
|
8662
|
+
worker.on("messageerror", () => {
|
|
8663
|
+
finish(
|
|
8664
|
+
() => reject(new Error(`Task "${taskCtx.job.name}" worker message could not be deserialized`))
|
|
8665
|
+
);
|
|
8467
8666
|
});
|
|
8468
8667
|
worker.on("error", (err) => {
|
|
8469
8668
|
finish(() => reject(err));
|
|
@@ -8480,7 +8679,8 @@ async function runTaskInWorker(options) {
|
|
|
8480
8679
|
type: "run",
|
|
8481
8680
|
payload,
|
|
8482
8681
|
taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job },
|
|
8483
|
-
registries: options.registries
|
|
8682
|
+
registries: options.registries,
|
|
8683
|
+
log: options.log
|
|
8484
8684
|
});
|
|
8485
8685
|
} catch (err) {
|
|
8486
8686
|
finish(
|
|
@@ -8491,7 +8691,7 @@ async function runTaskInWorker(options) {
|
|
|
8491
8691
|
}
|
|
8492
8692
|
});
|
|
8493
8693
|
}
|
|
8494
|
-
var KILL_GRACE_MS, TaskCancelledError, BUILD_VIEW_SOURCE;
|
|
8694
|
+
var KILL_GRACE_MS, TaskCancelledError, BUILD_VIEW_SOURCE, SERIALIZE_ERROR_SOURCE, BUILD_LOGGER_SOURCE;
|
|
8495
8695
|
var init_taskWorker = __esm({
|
|
8496
8696
|
"src/task/taskWorker.ts"() {
|
|
8497
8697
|
"use strict";
|
|
@@ -8556,6 +8756,49 @@ function buildRegistriesView(snapshot) {
|
|
|
8556
8756
|
},
|
|
8557
8757
|
};
|
|
8558
8758
|
}
|
|
8759
|
+
`;
|
|
8760
|
+
SERIALIZE_ERROR_SOURCE = `
|
|
8761
|
+
function serializeError(err) {
|
|
8762
|
+
if (err instanceof Error) {
|
|
8763
|
+
var props = {};
|
|
8764
|
+
var keys = Object.keys(err);
|
|
8765
|
+
for (var i = 0; i < keys.length; i++) {
|
|
8766
|
+
props[keys[i]] = err[keys[i]];
|
|
8767
|
+
}
|
|
8768
|
+
return { name: err.name, message: err.message, stack: err.stack, props: props };
|
|
8769
|
+
}
|
|
8770
|
+
return { name: 'Error', message: String(err), stack: undefined, props: {} };
|
|
8771
|
+
}
|
|
8772
|
+
`;
|
|
8773
|
+
BUILD_LOGGER_SOURCE = `
|
|
8774
|
+
var LOG_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
8775
|
+
function createTaskLogger(level, scope, fields) {
|
|
8776
|
+
function make(suffix) {
|
|
8777
|
+
function write(lvl, message, callFields) {
|
|
8778
|
+
if (LOG_RANK[lvl] < LOG_RANK[level]) return;
|
|
8779
|
+
var entry = { level: lvl, message: message, time: new Date().toISOString() };
|
|
8780
|
+
var fullScope = suffix === undefined ? scope : scope === undefined ? suffix : scope + ':' + suffix;
|
|
8781
|
+
if (fullScope !== undefined) entry.scope = fullScope;
|
|
8782
|
+
if (fields !== undefined || callFields !== undefined) {
|
|
8783
|
+
entry.fields = Object.assign({}, fields, callFields);
|
|
8784
|
+
}
|
|
8785
|
+
try {
|
|
8786
|
+
parentPort.postMessage({ type: 'log', entry: entry });
|
|
8787
|
+
} catch (_) {
|
|
8788
|
+
entry.fields = { warning: 'log fields not cloneable across worker boundary, dropped' };
|
|
8789
|
+
parentPort.postMessage({ type: 'log', entry: entry });
|
|
8790
|
+
}
|
|
8791
|
+
}
|
|
8792
|
+
return {
|
|
8793
|
+
debug: function (m, f) { write('debug', m, f); },
|
|
8794
|
+
info: function (m, f) { write('info', m, f); },
|
|
8795
|
+
warn: function (m, f) { write('warn', m, f); },
|
|
8796
|
+
error: function (m, f) { write('error', m, f); },
|
|
8797
|
+
child: function (cs) { return make(suffix === undefined ? cs : suffix + ':' + cs); },
|
|
8798
|
+
};
|
|
8799
|
+
}
|
|
8800
|
+
return make(undefined);
|
|
8801
|
+
}
|
|
8559
8802
|
`;
|
|
8560
8803
|
}
|
|
8561
8804
|
});
|
|
@@ -8570,6 +8813,25 @@ function createTaskQueue(deps) {
|
|
|
8570
8813
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
8571
8814
|
let started = false;
|
|
8572
8815
|
let stopped = false;
|
|
8816
|
+
let finishedCount = 0;
|
|
8817
|
+
function transitionStatus(record, status) {
|
|
8818
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
8819
|
+
const nowTerminal = TERMINAL_STATUSES.has(status);
|
|
8820
|
+
if (!wasTerminal && nowTerminal) finishedCount++;
|
|
8821
|
+
else if (wasTerminal && !nowTerminal) finishedCount--;
|
|
8822
|
+
record.status = status;
|
|
8823
|
+
}
|
|
8824
|
+
function evictFinishedRecords() {
|
|
8825
|
+
let toEvict = finishedCount - MAX_FINISHED_RECORDS;
|
|
8826
|
+
for (const [id, job] of records) {
|
|
8827
|
+
if (toEvict <= 0) break;
|
|
8828
|
+
if (TERMINAL_STATUSES.has(job.status)) {
|
|
8829
|
+
records.delete(id);
|
|
8830
|
+
finishedCount--;
|
|
8831
|
+
toEvict--;
|
|
8832
|
+
}
|
|
8833
|
+
}
|
|
8834
|
+
}
|
|
8573
8835
|
const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
|
|
8574
8836
|
const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
|
|
8575
8837
|
const zodPath = path25.join(path25.dirname(filePath), "zod.js");
|
|
@@ -8628,7 +8890,8 @@ function createTaskQueue(deps) {
|
|
|
8628
8890
|
createdAt: Date.now()
|
|
8629
8891
|
};
|
|
8630
8892
|
record.attempts = job.attempt;
|
|
8631
|
-
record
|
|
8893
|
+
transitionStatus(record, "running");
|
|
8894
|
+
delete record.progress;
|
|
8632
8895
|
record.error = void 0;
|
|
8633
8896
|
records.set(job.id, record);
|
|
8634
8897
|
try {
|
|
@@ -8642,8 +8905,20 @@ function createTaskQueue(deps) {
|
|
|
8642
8905
|
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
8643
8906
|
},
|
|
8644
8907
|
registries: snapshotRegistries(),
|
|
8908
|
+
// 日志配置随 workerData 下发(纯数据),worker 内联重建 taskCtx.log,
|
|
8909
|
+
// 条目回传宿主走统一管道(scope/fields 与进程内路径一致)
|
|
8910
|
+
log: {
|
|
8911
|
+
level: getEffectiveLogLevel(),
|
|
8912
|
+
scope: `task:${job.name}`,
|
|
8913
|
+
fields: { jobId: job.id, task: job.name, attempt: job.attempt }
|
|
8914
|
+
},
|
|
8645
8915
|
timeoutMs: meta.timeoutMs,
|
|
8646
|
-
|
|
8916
|
+
graceMs: meta.graceMs,
|
|
8917
|
+
externalSignal: job.signal,
|
|
8918
|
+
onProgress: (value) => {
|
|
8919
|
+
if (record.status === "running") record.progress = value;
|
|
8920
|
+
},
|
|
8921
|
+
onLog: writeLogEntry
|
|
8647
8922
|
});
|
|
8648
8923
|
} else {
|
|
8649
8924
|
let mod = moduleCache2.get(job.name);
|
|
@@ -8658,17 +8933,25 @@ function createTaskQueue(deps) {
|
|
|
8658
8933
|
signal: job.signal,
|
|
8659
8934
|
config: deps.config,
|
|
8660
8935
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
8661
|
-
registries: registriesView
|
|
8936
|
+
registries: registriesView,
|
|
8937
|
+
log: createLogger(`task:${job.name}`, {
|
|
8938
|
+
fields: { jobId: job.id, task: job.name, attempt: job.attempt }
|
|
8939
|
+
}),
|
|
8940
|
+
progress: (value) => {
|
|
8941
|
+
if (record.status === "running") record.progress = value;
|
|
8942
|
+
}
|
|
8662
8943
|
};
|
|
8663
8944
|
result = await mod.run(job.payload, taskCtx);
|
|
8664
8945
|
}
|
|
8665
|
-
record
|
|
8946
|
+
transitionStatus(record, "done");
|
|
8666
8947
|
record.result = result;
|
|
8948
|
+
evictFinishedRecords();
|
|
8667
8949
|
return result;
|
|
8668
8950
|
} catch (err) {
|
|
8669
8951
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
8670
|
-
record
|
|
8952
|
+
transitionStatus(record, cancelled ? "cancelled" : "failed");
|
|
8671
8953
|
record.error = err instanceof Error ? err.message : String(err);
|
|
8954
|
+
evictFinishedRecords();
|
|
8672
8955
|
if (deps.onFailed) {
|
|
8673
8956
|
void Promise.resolve().then(
|
|
8674
8957
|
() => deps.onFailed({
|
|
@@ -8763,7 +9046,8 @@ function createTaskQueue(deps) {
|
|
|
8763
9046
|
}
|
|
8764
9047
|
await driver.cancel(name, id);
|
|
8765
9048
|
const record = records.get(id);
|
|
8766
|
-
if (record) record
|
|
9049
|
+
if (record) transitionStatus(record, "cancelled");
|
|
9050
|
+
evictFinishedRecords();
|
|
8767
9051
|
},
|
|
8768
9052
|
async retry(name, id) {
|
|
8769
9053
|
if (!driver.retry) {
|
|
@@ -8773,7 +9057,7 @@ function createTaskQueue(deps) {
|
|
|
8773
9057
|
}
|
|
8774
9058
|
await driver.retry(name, id);
|
|
8775
9059
|
const record = records.get(id);
|
|
8776
|
-
if (record) record
|
|
9060
|
+
if (record) transitionStatus(record, "pending");
|
|
8777
9061
|
},
|
|
8778
9062
|
async start() {
|
|
8779
9063
|
if (stopped) {
|
|
@@ -8800,12 +9084,16 @@ function createTaskQueue(deps) {
|
|
|
8800
9084
|
};
|
|
8801
9085
|
return queue;
|
|
8802
9086
|
}
|
|
9087
|
+
var TERMINAL_STATUSES, MAX_FINISHED_RECORDS;
|
|
8803
9088
|
var init_taskQueue = __esm({
|
|
8804
9089
|
"src/task/taskQueue.ts"() {
|
|
8805
9090
|
"use strict";
|
|
8806
9091
|
init_httpErrors();
|
|
8807
9092
|
init_taskWorker();
|
|
8808
9093
|
init_registries();
|
|
9094
|
+
init_logger();
|
|
9095
|
+
TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
9096
|
+
MAX_FINISHED_RECORDS = 1e3;
|
|
8809
9097
|
}
|
|
8810
9098
|
});
|
|
8811
9099
|
|
|
@@ -9151,6 +9439,7 @@ async function createAppBase(options) {
|
|
|
9151
9439
|
);
|
|
9152
9440
|
}
|
|
9153
9441
|
const config = await loadConfig(rootDir, dist);
|
|
9442
|
+
configureLogging(config?.log);
|
|
9154
9443
|
const serialized = await importWithCacheBust(routesPath);
|
|
9155
9444
|
const hydrated = await hydrateRoutes(serialized);
|
|
9156
9445
|
let sorted = sortRoutes(hydrated.routes);
|
|
@@ -9487,6 +9776,7 @@ var init_createAppCore = __esm({
|
|
|
9487
9776
|
init_loadTaskDriver();
|
|
9488
9777
|
init_idleTaskDriver();
|
|
9489
9778
|
init_cronScheduler();
|
|
9779
|
+
init_logger();
|
|
9490
9780
|
init_loadPlugins();
|
|
9491
9781
|
init_importWithCacheBust();
|
|
9492
9782
|
init_registries();
|
|
@@ -9510,6 +9800,7 @@ var init_createAppCore = __esm({
|
|
|
9510
9800
|
"etag",
|
|
9511
9801
|
"bodyLimit",
|
|
9512
9802
|
"logger",
|
|
9803
|
+
"log",
|
|
9513
9804
|
"http2",
|
|
9514
9805
|
"trustedProxy",
|
|
9515
9806
|
"response",
|