@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 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 = Number(concurrency);
3418
- if (retries !== void 0) manifest.retries = Number(retries);
3419
- if (timeoutMs !== void 0) manifest.timeoutMs = Number(timeoutMs);
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
- CONCURRENCY_RE = /(?:^|\n)\s*concurrency:\s*(\d+)/;
3432
- RETRIES_RE = /(?:^|\n)\s*retries:\s*(\d+)/;
3433
- TIMEOUT_MS_RE = /(?:^|\n)\s*timeoutMs:\s*(\d+)/;
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 init_logger = __esm({
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
- init_logger();
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({ type: 'error', message: 'Task module has no run export' });
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 result = await run(payload, { ...taskCtx, signal: controller.signal, registries });
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
- parentPort.postMessage({
8388
- type: 'error',
8389
- message: err instanceof Error ? err.message : String(err),
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.killGraceMs ?? KILL_GRACE_MS;
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("message", (msg) => {
8447
- if (phase === "grace") {
8448
- if (msg?.type === "done") {
8449
- finish(
8450
- () => reject(
8451
- new TaskCancelledError(
8452
- `Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
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(new TaskCancelledError(msg.message ?? cancelReason)));
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.status = "running";
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
- externalSignal: job.signal
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.status = "done";
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.status = cancelled ? "cancelled" : "failed";
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.status = "cancelled";
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.status = "pending";
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",