@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/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AppRegistries, R as RouteManifest, F as FaapiContext, C as CorsOptions, T as TaskClient, H as HelmetOptions, L as LoggerOptions, a as FaapiMiddleware, I as InjectorMap,
|
|
2
|
-
export {
|
|
1
|
+
import { A as AppRegistries, R as RouteManifest, F as FaapiContext, C as CorsOptions, T as TaskClient, H as HelmetOptions, L as LoggerOptions, a as LogConfig, b as FaapiMiddleware, I as InjectorMap, c as TaskFailedHandler, d as TaskQueueDeps, e as TaskQueue, f as TaskDriver, g as TaskRegistry, h as TaskManifest, i as ToolMetadata, j as AgentCore, k as AgentMetadata, l as CreateLoggerOptions, m as Logger, W as WsRouteManifest } from './routeTypes-J5pPbjwR.js';
|
|
2
|
+
export { n as AgentHandleFactory, o as AgentHandleStore, p as AgentPathMeta, q as AgentRegistry, r as AgentToolDescriptor, s as FaapiContextConfig, t as FaapiTaskMeta, u as FailOptions, v as Injector, w as LogEntry, x as LogLevel, y as LogSink, z as RouteInfo, B as RouteInputSchema, D as RouteOutputSchema, E as RouteParamSchema, S as SkillRegistry, G as SseEvent, J as SseWriter, K as TaskContext, M as TaskDriverJob, N as TaskDriverProcess, O as TaskDriverRecord, P as TaskFailedInfo, Q as TaskJob, U as TaskJobStatus, V as TaskMetadata, X as TaskModule, Y as TaskRegistriesSnapshot, Z as TaskRegistriesView, _ as ToolCore, $ as ToolPathMeta, a0 as ToolRegistry, a1 as cors, a2 as createAppRegistries, a3 as createTaskRegistriesView, a4 as createTaskRegistry, a5 as helmet, a6 as logger } from './routeTypes-J5pPbjwR.js';
|
|
3
3
|
import * as node_http from 'node:http';
|
|
4
4
|
import { Server, IncomingMessage, ServerResponse } from 'node:http';
|
|
5
5
|
import { Socket } from 'node:net';
|
|
@@ -552,6 +552,26 @@ interface FaapiConfig {
|
|
|
552
552
|
bodyLimit?: number;
|
|
553
553
|
/** 日志中间件配置 */
|
|
554
554
|
logger?: LoggerOptions | boolean;
|
|
555
|
+
/**
|
|
556
|
+
* 业务日志器全局配置(与 `logger` 请求日志中间件是两条独立管道,详见 logger/logger.md)
|
|
557
|
+
*
|
|
558
|
+
* - `false`:完全静默(含 error,测试降噪)
|
|
559
|
+
* - `true` / 缺省:默认级别(显式 level > LOG_LEVEL env > 'info')+ console 文本输出
|
|
560
|
+
* - `LogConfig`:精细配置 level 与 sink(sink 可整体接管接 pino/winston/文件)
|
|
561
|
+
*
|
|
562
|
+
* 生效范围:`ctx.log` / 参数注入 `log` / `createLogger` / `taskCtx.log`。
|
|
563
|
+
* 日志全局配置是进程级资源,多 app 同进程时后启动覆盖先启动。
|
|
564
|
+
*
|
|
565
|
+
* ```ts
|
|
566
|
+
* export default {
|
|
567
|
+
* log: {
|
|
568
|
+
* level: 'debug',
|
|
569
|
+
* sink: (entry) => pinoLogger[entry.level]({ scope: entry.scope, ...entry.fields }, entry.message),
|
|
570
|
+
* },
|
|
571
|
+
* } satisfies FaapiConfig;
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
log?: LogConfig | boolean;
|
|
555
575
|
/** HTTP/2 配置,false 禁用(默认 http/1.1) */
|
|
556
576
|
http2?: Http2Options | boolean;
|
|
557
577
|
/**
|
|
@@ -1466,6 +1486,26 @@ declare function loadConfig(rootDir: string, dist: string): Promise<Partial<Faap
|
|
|
1466
1486
|
*/
|
|
1467
1487
|
declare function loadEnv(rootDir: string): void;
|
|
1468
1488
|
|
|
1489
|
+
/**
|
|
1490
|
+
* 设置全局日志配置(进程级,`createAppBase` 启动时以 `config.log` 调用)
|
|
1491
|
+
*
|
|
1492
|
+
* - `false`:完全静默(含 error,测试降噪场景)
|
|
1493
|
+
* - `true` / `{}`:默认级别(显式 level > LOG_LEVEL env > 'info')+ 默认 console sink
|
|
1494
|
+
* - `{ level, sink }`:精细配置;非法 level 抛错(启动期 fail fast)
|
|
1495
|
+
* - `undefined`:重置为默认(编程式多 app 切换全局配置用)
|
|
1496
|
+
*
|
|
1497
|
+
* LOG_LEVEL env 仅在本函数读取——app 启动路径必经此处(env 非法在启动期报错);
|
|
1498
|
+
* 纯编程式不调本函数时 env 不生效(默认 info)。
|
|
1499
|
+
*/
|
|
1500
|
+
declare function configureLogging(config?: LogConfig | boolean | undefined): void;
|
|
1501
|
+
/**
|
|
1502
|
+
* 创建日志器
|
|
1503
|
+
*
|
|
1504
|
+
* @param scope 分类标签(如 'db'、'http'),默认无分类
|
|
1505
|
+
* @param options 实例级覆盖(level/sink/构造 fields)
|
|
1506
|
+
*/
|
|
1507
|
+
declare function createLogger(scope?: string, options?: CreateLoggerOptions): Logger;
|
|
1508
|
+
|
|
1469
1509
|
declare const VALIDATION_ERROR = "VALIDATION_ERROR";
|
|
1470
1510
|
declare const ROUTE_NOT_FOUND = "ROUTE_NOT_FOUND";
|
|
1471
1511
|
declare const METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED";
|
|
@@ -1666,4 +1706,4 @@ type ProdApp = AppBase;
|
|
|
1666
1706
|
*/
|
|
1667
1707
|
declare function createProdApp(options?: CreateAppOptions): Promise<ProdApp>;
|
|
1668
1708
|
|
|
1669
|
-
export { type AgentConfig, AgentCore, AgentMetadata, type AgentModule, type ProdApp as App, AppRegistries, CorsOptions, type CreateAppOptions, type CronScheduler, type DevApp, type FaapiConfig, FaapiContext, FaapiError, FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, HelmetOptions, type InjectOptions, type InjectResponse, InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LlmConfig, type LlmModelConfig, LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, TASK_PATTERNS, type TaskBullMqOptions, TaskClient, type TaskConfig, TaskDriver, TaskFailedHandler, TaskManifest, type TaskPgBossOptions, TaskQueue, TaskRegistry, ToolMetadata, type ToolModule, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, createProdApp as createApp, createCronScheduler, createDevApp, createProdApp, createProgram, createPrograms, createTaskQueue, extractTypeInfo, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadAgentModule, loadConfig, loadEnv, loadTaskDriver, loadToolModule, loadToolSchema, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, scanTasks, upsertSkill };
|
|
1709
|
+
export { type AgentConfig, AgentCore, AgentMetadata, type AgentModule, type ProdApp as App, AppRegistries, CorsOptions, type CreateAppOptions, CreateLoggerOptions, type CronScheduler, type DevApp, type FaapiConfig, FaapiContext, FaapiError, FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, HelmetOptions, type InjectOptions, type InjectResponse, InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LlmConfig, type LlmModelConfig, LogConfig, Logger, LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, TASK_PATTERNS, type TaskBullMqOptions, TaskClient, type TaskConfig, TaskDriver, TaskFailedHandler, TaskManifest, type TaskPgBossOptions, TaskQueue, TaskRegistry, ToolMetadata, type ToolModule, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, configureLogging, createProdApp as createApp, createCronScheduler, createDevApp, createLogger, createProdApp, createProgram, createPrograms, createTaskQueue, extractTypeInfo, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadAgentModule, loadConfig, loadEnv, loadTaskDriver, loadToolModule, loadToolSchema, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, scanTasks, upsertSkill };
|
package/dist/index.js
CHANGED
|
@@ -1122,8 +1122,10 @@ var init_resolveInjection = __esm({
|
|
|
1122
1122
|
// Phase 2.3
|
|
1123
1123
|
agents: "agents",
|
|
1124
1124
|
// Phase 2.3
|
|
1125
|
-
tasks: "tasks"
|
|
1125
|
+
tasks: "tasks",
|
|
1126
1126
|
// 任务子系统:TaskClient(入队/查询)
|
|
1127
|
+
log: "log"
|
|
1128
|
+
// 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
|
|
1127
1129
|
};
|
|
1128
1130
|
injectionCache = /* @__PURE__ */ new WeakMap();
|
|
1129
1131
|
}
|
|
@@ -2053,6 +2055,49 @@ function buildRegistriesView(snapshot) {
|
|
|
2053
2055
|
};
|
|
2054
2056
|
}
|
|
2055
2057
|
`;
|
|
2058
|
+
var SERIALIZE_ERROR_SOURCE = `
|
|
2059
|
+
function serializeError(err) {
|
|
2060
|
+
if (err instanceof Error) {
|
|
2061
|
+
var props = {};
|
|
2062
|
+
var keys = Object.keys(err);
|
|
2063
|
+
for (var i = 0; i < keys.length; i++) {
|
|
2064
|
+
props[keys[i]] = err[keys[i]];
|
|
2065
|
+
}
|
|
2066
|
+
return { name: err.name, message: err.message, stack: err.stack, props: props };
|
|
2067
|
+
}
|
|
2068
|
+
return { name: 'Error', message: String(err), stack: undefined, props: {} };
|
|
2069
|
+
}
|
|
2070
|
+
`;
|
|
2071
|
+
var BUILD_LOGGER_SOURCE = `
|
|
2072
|
+
var LOG_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2073
|
+
function createTaskLogger(level, scope, fields) {
|
|
2074
|
+
function make(suffix) {
|
|
2075
|
+
function write(lvl, message, callFields) {
|
|
2076
|
+
if (LOG_RANK[lvl] < LOG_RANK[level]) return;
|
|
2077
|
+
var entry = { level: lvl, message: message, time: new Date().toISOString() };
|
|
2078
|
+
var fullScope = suffix === undefined ? scope : scope === undefined ? suffix : scope + ':' + suffix;
|
|
2079
|
+
if (fullScope !== undefined) entry.scope = fullScope;
|
|
2080
|
+
if (fields !== undefined || callFields !== undefined) {
|
|
2081
|
+
entry.fields = Object.assign({}, fields, callFields);
|
|
2082
|
+
}
|
|
2083
|
+
try {
|
|
2084
|
+
parentPort.postMessage({ type: 'log', entry: entry });
|
|
2085
|
+
} catch (_) {
|
|
2086
|
+
entry.fields = { warning: 'log fields not cloneable across worker boundary, dropped' };
|
|
2087
|
+
parentPort.postMessage({ type: 'log', entry: entry });
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return {
|
|
2091
|
+
debug: function (m, f) { write('debug', m, f); },
|
|
2092
|
+
info: function (m, f) { write('info', m, f); },
|
|
2093
|
+
warn: function (m, f) { write('warn', m, f); },
|
|
2094
|
+
error: function (m, f) { write('error', m, f); },
|
|
2095
|
+
child: function (cs) { return make(suffix === undefined ? cs : suffix + ':' + cs); },
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
return make(undefined);
|
|
2099
|
+
}
|
|
2100
|
+
`;
|
|
2056
2101
|
function buildWrapperSource(moduleUrl) {
|
|
2057
2102
|
return `
|
|
2058
2103
|
import { parentPort } from 'node:worker_threads';
|
|
@@ -2060,9 +2105,16 @@ import * as mod from '${moduleUrl}';
|
|
|
2060
2105
|
|
|
2061
2106
|
${BUILD_VIEW_SOURCE}
|
|
2062
2107
|
|
|
2108
|
+
${SERIALIZE_ERROR_SOURCE}
|
|
2109
|
+
|
|
2110
|
+
${BUILD_LOGGER_SOURCE}
|
|
2111
|
+
|
|
2063
2112
|
const run = mod.run;
|
|
2064
2113
|
if (typeof run !== 'function') {
|
|
2065
|
-
parentPort.postMessage({
|
|
2114
|
+
parentPort.postMessage({
|
|
2115
|
+
type: 'error',
|
|
2116
|
+
error: { name: 'Error', message: 'Task module has no run export', stack: undefined, props: {} },
|
|
2117
|
+
});
|
|
2066
2118
|
} else {
|
|
2067
2119
|
let controller = null;
|
|
2068
2120
|
parentPort.on('message', async (msg) => {
|
|
@@ -2075,21 +2127,45 @@ if (typeof run !== 'function') {
|
|
|
2075
2127
|
const { payload, taskCtx } = msg;
|
|
2076
2128
|
try {
|
|
2077
2129
|
const registries = buildRegistriesView(msg.registries);
|
|
2078
|
-
const
|
|
2130
|
+
const progress = (value) => parentPort.postMessage({ type: 'progress', value });
|
|
2131
|
+
const taskLog = msg.log ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields) : undefined;
|
|
2132
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal, registries, progress, log: taskLog });
|
|
2079
2133
|
parentPort.postMessage({ type: 'done', result });
|
|
2080
2134
|
} catch (err) {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
})
|
|
2135
|
+
const serialized = serializeError(err);
|
|
2136
|
+
try {
|
|
2137
|
+
parentPort.postMessage({ type: 'error', error: serialized });
|
|
2138
|
+
} catch (_) {
|
|
2139
|
+
// props \u542B\u4E0D\u53EF\u514B\u9686\u503C\uFF08\u51FD\u6570\u7B49\uFF09\u65F6\u4E22\u5F03 props\uFF0C\u4FDD\u5E95 name/message/stack
|
|
2140
|
+
parentPort.postMessage({
|
|
2141
|
+
type: 'error',
|
|
2142
|
+
error: { name: serialized.name, message: serialized.message, stack: serialized.stack, props: {} },
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2085
2145
|
}
|
|
2086
2146
|
});
|
|
2087
2147
|
}
|
|
2088
2148
|
`;
|
|
2089
2149
|
}
|
|
2150
|
+
function reviveError(payload) {
|
|
2151
|
+
const err = new Error(payload?.message ?? "task worker error");
|
|
2152
|
+
if (payload?.name !== void 0) err.name = payload.name;
|
|
2153
|
+
if (payload?.stack !== void 0) err.stack = payload.stack;
|
|
2154
|
+
if (payload?.props) {
|
|
2155
|
+
for (const [key, value] of Object.entries(payload.props)) {
|
|
2156
|
+
err[key] = value;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return err;
|
|
2160
|
+
}
|
|
2090
2161
|
async function runTaskInWorker(options) {
|
|
2091
2162
|
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
2092
|
-
const killGraceMs = options.
|
|
2163
|
+
const killGraceMs = options.graceMs ?? KILL_GRACE_MS;
|
|
2164
|
+
if (externalSignal?.aborted) {
|
|
2165
|
+
throw new TaskCancelledError(
|
|
2166
|
+
`Task "${taskCtx.job.name}" cancelled before start (driver stop signal already aborted)`
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2093
2169
|
const moduleUrl = pathToFileURL(taskModulePath).href;
|
|
2094
2170
|
const wrapperUrl = new URL(
|
|
2095
2171
|
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
@@ -2137,27 +2213,39 @@ async function runTaskInWorker(options) {
|
|
|
2137
2213
|
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
2138
2214
|
timeoutMs
|
|
2139
2215
|
);
|
|
2140
|
-
worker.on(
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2216
|
+
worker.on(
|
|
2217
|
+
"message",
|
|
2218
|
+
(msg) => {
|
|
2219
|
+
if (phase === "grace") {
|
|
2220
|
+
if (msg?.type === "done") {
|
|
2221
|
+
finish(
|
|
2222
|
+
() => reject(
|
|
2223
|
+
new TaskCancelledError(
|
|
2224
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
2225
|
+
)
|
|
2147
2226
|
)
|
|
2148
|
-
)
|
|
2149
|
-
)
|
|
2227
|
+
);
|
|
2228
|
+
} else if (msg?.type === "error") {
|
|
2229
|
+
finish(() => reject(new TaskCancelledError(msg.error?.message ?? cancelReason)));
|
|
2230
|
+
}
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (phase !== "running") return;
|
|
2234
|
+
if (msg?.type === "done") {
|
|
2235
|
+
finish(() => resolve(msg.result));
|
|
2150
2236
|
} else if (msg?.type === "error") {
|
|
2151
|
-
finish(() => reject(
|
|
2237
|
+
finish(() => reject(reviveError(msg.error)));
|
|
2238
|
+
} else if (msg?.type === "progress") {
|
|
2239
|
+
options.onProgress?.(msg.value);
|
|
2240
|
+
} else if (msg?.type === "log") {
|
|
2241
|
+
options.onLog?.(msg.entry);
|
|
2152
2242
|
}
|
|
2153
|
-
return;
|
|
2154
|
-
}
|
|
2155
|
-
if (phase !== "running") return;
|
|
2156
|
-
if (msg?.type === "done") {
|
|
2157
|
-
finish(() => resolve(msg.result));
|
|
2158
|
-
} else if (msg?.type === "error") {
|
|
2159
|
-
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
2160
2243
|
}
|
|
2244
|
+
);
|
|
2245
|
+
worker.on("messageerror", () => {
|
|
2246
|
+
finish(
|
|
2247
|
+
() => reject(new Error(`Task "${taskCtx.job.name}" worker message could not be deserialized`))
|
|
2248
|
+
);
|
|
2161
2249
|
});
|
|
2162
2250
|
worker.on("error", (err) => {
|
|
2163
2251
|
finish(() => reject(err));
|
|
@@ -2174,7 +2262,8 @@ async function runTaskInWorker(options) {
|
|
|
2174
2262
|
type: "run",
|
|
2175
2263
|
payload,
|
|
2176
2264
|
taskCtx: { config: safeConfig(taskCtx.config), job: taskCtx.job },
|
|
2177
|
-
registries: options.registries
|
|
2265
|
+
registries: options.registries,
|
|
2266
|
+
log: options.log
|
|
2178
2267
|
});
|
|
2179
2268
|
} catch (err) {
|
|
2180
2269
|
finish(
|
|
@@ -2186,7 +2275,121 @@ async function runTaskInWorker(options) {
|
|
|
2186
2275
|
});
|
|
2187
2276
|
}
|
|
2188
2277
|
|
|
2278
|
+
// src/logger/logger.ts
|
|
2279
|
+
var LEVEL_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
2280
|
+
var VALID_LEVELS = ["debug", "info", "warn", "error"];
|
|
2281
|
+
function assertLogLevel(value, source) {
|
|
2282
|
+
if (typeof value !== "string" || !VALID_LEVELS.includes(value)) {
|
|
2283
|
+
throw new Error(
|
|
2284
|
+
`[faapi] Invalid log level from ${source}: ${String(value)}. Expected one of: ${VALID_LEVELS.join(", ")}.`
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
var globalState = {
|
|
2289
|
+
level: null,
|
|
2290
|
+
sink: null,
|
|
2291
|
+
disabled: false
|
|
2292
|
+
};
|
|
2293
|
+
function configureLogging(config) {
|
|
2294
|
+
if (config === void 0) {
|
|
2295
|
+
globalState.level = null;
|
|
2296
|
+
globalState.sink = null;
|
|
2297
|
+
globalState.disabled = false;
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
const objectConfig = typeof config === "object" ? config : {};
|
|
2301
|
+
globalState.disabled = config === false;
|
|
2302
|
+
globalState.sink = objectConfig.sink ?? null;
|
|
2303
|
+
if (objectConfig.level !== void 0) {
|
|
2304
|
+
assertLogLevel(objectConfig.level, "config.log.level");
|
|
2305
|
+
globalState.level = objectConfig.level;
|
|
2306
|
+
} else if (process.env.LOG_LEVEL !== void 0) {
|
|
2307
|
+
assertLogLevel(process.env.LOG_LEVEL, "LOG_LEVEL env");
|
|
2308
|
+
globalState.level = process.env.LOG_LEVEL;
|
|
2309
|
+
} else {
|
|
2310
|
+
globalState.level = null;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
function globalThreshold() {
|
|
2314
|
+
return globalState.level ?? "info";
|
|
2315
|
+
}
|
|
2316
|
+
function getEffectiveLogLevel() {
|
|
2317
|
+
return globalThreshold();
|
|
2318
|
+
}
|
|
2319
|
+
function serializeFieldValue(value) {
|
|
2320
|
+
if (value instanceof Error) {
|
|
2321
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
2322
|
+
}
|
|
2323
|
+
return value;
|
|
2324
|
+
}
|
|
2325
|
+
function formatEntry(entry) {
|
|
2326
|
+
let line = `[${entry.time}] ${entry.level.toUpperCase()} `;
|
|
2327
|
+
if (entry.scope) line += `[${entry.scope}] `;
|
|
2328
|
+
line += entry.message;
|
|
2329
|
+
if (entry.fields !== void 0) {
|
|
2330
|
+
const plain = Object.fromEntries(
|
|
2331
|
+
Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)])
|
|
2332
|
+
);
|
|
2333
|
+
try {
|
|
2334
|
+
line += ` ${JSON.stringify(plain)}`;
|
|
2335
|
+
} catch (err) {
|
|
2336
|
+
line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
return line;
|
|
2340
|
+
}
|
|
2341
|
+
var CONSOLE_METHOD = {
|
|
2342
|
+
debug: "debug",
|
|
2343
|
+
info: "info",
|
|
2344
|
+
warn: "warn",
|
|
2345
|
+
error: "error"
|
|
2346
|
+
};
|
|
2347
|
+
var consoleSink = (entry) => {
|
|
2348
|
+
console[CONSOLE_METHOD[entry.level]](formatEntry(entry));
|
|
2349
|
+
};
|
|
2350
|
+
function globalDispatch(entry) {
|
|
2351
|
+
if (globalState.disabled) return;
|
|
2352
|
+
(globalState.sink ?? consoleSink)(entry);
|
|
2353
|
+
}
|
|
2354
|
+
function writeLogEntry(entry) {
|
|
2355
|
+
if (LEVEL_RANK[entry.level] < LEVEL_RANK[globalThreshold()]) return;
|
|
2356
|
+
globalDispatch(entry);
|
|
2357
|
+
}
|
|
2358
|
+
function createLogger(scope, options) {
|
|
2359
|
+
const baseScope = scope;
|
|
2360
|
+
const baseFields = options?.fields;
|
|
2361
|
+
const write = (level, message, callFields, scopeSuffix) => {
|
|
2362
|
+
const threshold = options?.level ?? globalThreshold();
|
|
2363
|
+
if (LEVEL_RANK[level] < LEVEL_RANK[threshold]) return;
|
|
2364
|
+
const entry = {
|
|
2365
|
+
level,
|
|
2366
|
+
message,
|
|
2367
|
+
time: (/* @__PURE__ */ new Date()).toISOString()
|
|
2368
|
+
};
|
|
2369
|
+
const fullScope = scopeSuffix === void 0 ? baseScope : baseScope === void 0 ? scopeSuffix : `${baseScope}:${scopeSuffix}`;
|
|
2370
|
+
if (fullScope !== void 0) entry.scope = fullScope;
|
|
2371
|
+
if (baseFields !== void 0 || callFields !== void 0) {
|
|
2372
|
+
entry.fields = { ...baseFields, ...callFields };
|
|
2373
|
+
}
|
|
2374
|
+
if (options?.sink) {
|
|
2375
|
+
options.sink(entry);
|
|
2376
|
+
} else {
|
|
2377
|
+
globalDispatch(entry);
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
const makeLogger = (scopeSuffix) => ({
|
|
2381
|
+
debug: (message, fields) => write("debug", message, fields, scopeSuffix),
|
|
2382
|
+
info: (message, fields) => write("info", message, fields, scopeSuffix),
|
|
2383
|
+
warn: (message, fields) => write("warn", message, fields, scopeSuffix),
|
|
2384
|
+
error: (message, fields) => write("error", message, fields, scopeSuffix),
|
|
2385
|
+
child: (childScope) => makeLogger(scopeSuffix === void 0 ? childScope : `${scopeSuffix}:${childScope}`)
|
|
2386
|
+
});
|
|
2387
|
+
return makeLogger(void 0);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2189
2390
|
// src/task/taskQueue.ts
|
|
2391
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
2392
|
+
var MAX_FINISHED_RECORDS = 1e3;
|
|
2190
2393
|
function createTaskQueue(deps) {
|
|
2191
2394
|
const { registry, rootDir, driver } = deps;
|
|
2192
2395
|
const registriesView = deps.registries ?? createEmptyTaskRegistriesView();
|
|
@@ -2195,6 +2398,25 @@ function createTaskQueue(deps) {
|
|
|
2195
2398
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
2196
2399
|
let started = false;
|
|
2197
2400
|
let stopped = false;
|
|
2401
|
+
let finishedCount = 0;
|
|
2402
|
+
function transitionStatus(record, status) {
|
|
2403
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
2404
|
+
const nowTerminal = TERMINAL_STATUSES.has(status);
|
|
2405
|
+
if (!wasTerminal && nowTerminal) finishedCount++;
|
|
2406
|
+
else if (wasTerminal && !nowTerminal) finishedCount--;
|
|
2407
|
+
record.status = status;
|
|
2408
|
+
}
|
|
2409
|
+
function evictFinishedRecords() {
|
|
2410
|
+
let toEvict = finishedCount - MAX_FINISHED_RECORDS;
|
|
2411
|
+
for (const [id, job] of records) {
|
|
2412
|
+
if (toEvict <= 0) break;
|
|
2413
|
+
if (TERMINAL_STATUSES.has(job.status)) {
|
|
2414
|
+
records.delete(id);
|
|
2415
|
+
finishedCount--;
|
|
2416
|
+
toEvict--;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2198
2420
|
const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
|
|
2199
2421
|
const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
|
|
2200
2422
|
const zodPath = path3.join(path3.dirname(filePath), "zod.js");
|
|
@@ -2253,7 +2475,8 @@ function createTaskQueue(deps) {
|
|
|
2253
2475
|
createdAt: Date.now()
|
|
2254
2476
|
};
|
|
2255
2477
|
record.attempts = job.attempt;
|
|
2256
|
-
record
|
|
2478
|
+
transitionStatus(record, "running");
|
|
2479
|
+
delete record.progress;
|
|
2257
2480
|
record.error = void 0;
|
|
2258
2481
|
records.set(job.id, record);
|
|
2259
2482
|
try {
|
|
@@ -2267,8 +2490,20 @@ function createTaskQueue(deps) {
|
|
|
2267
2490
|
job: { id: job.id, name: job.name, attempt: job.attempt }
|
|
2268
2491
|
},
|
|
2269
2492
|
registries: snapshotRegistries(),
|
|
2493
|
+
// 日志配置随 workerData 下发(纯数据),worker 内联重建 taskCtx.log,
|
|
2494
|
+
// 条目回传宿主走统一管道(scope/fields 与进程内路径一致)
|
|
2495
|
+
log: {
|
|
2496
|
+
level: getEffectiveLogLevel(),
|
|
2497
|
+
scope: `task:${job.name}`,
|
|
2498
|
+
fields: { jobId: job.id, task: job.name, attempt: job.attempt }
|
|
2499
|
+
},
|
|
2270
2500
|
timeoutMs: meta.timeoutMs,
|
|
2271
|
-
|
|
2501
|
+
graceMs: meta.graceMs,
|
|
2502
|
+
externalSignal: job.signal,
|
|
2503
|
+
onProgress: (value) => {
|
|
2504
|
+
if (record.status === "running") record.progress = value;
|
|
2505
|
+
},
|
|
2506
|
+
onLog: writeLogEntry
|
|
2272
2507
|
});
|
|
2273
2508
|
} else {
|
|
2274
2509
|
let mod = moduleCache2.get(job.name);
|
|
@@ -2283,17 +2518,25 @@ function createTaskQueue(deps) {
|
|
|
2283
2518
|
signal: job.signal,
|
|
2284
2519
|
config: deps.config,
|
|
2285
2520
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
2286
|
-
registries: registriesView
|
|
2521
|
+
registries: registriesView,
|
|
2522
|
+
log: createLogger(`task:${job.name}`, {
|
|
2523
|
+
fields: { jobId: job.id, task: job.name, attempt: job.attempt }
|
|
2524
|
+
}),
|
|
2525
|
+
progress: (value) => {
|
|
2526
|
+
if (record.status === "running") record.progress = value;
|
|
2527
|
+
}
|
|
2287
2528
|
};
|
|
2288
2529
|
result = await mod.run(job.payload, taskCtx);
|
|
2289
2530
|
}
|
|
2290
|
-
record
|
|
2531
|
+
transitionStatus(record, "done");
|
|
2291
2532
|
record.result = result;
|
|
2533
|
+
evictFinishedRecords();
|
|
2292
2534
|
return result;
|
|
2293
2535
|
} catch (err) {
|
|
2294
2536
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
2295
|
-
record
|
|
2537
|
+
transitionStatus(record, cancelled ? "cancelled" : "failed");
|
|
2296
2538
|
record.error = err instanceof Error ? err.message : String(err);
|
|
2539
|
+
evictFinishedRecords();
|
|
2297
2540
|
if (deps.onFailed) {
|
|
2298
2541
|
void Promise.resolve().then(
|
|
2299
2542
|
() => deps.onFailed({
|
|
@@ -2388,7 +2631,8 @@ function createTaskQueue(deps) {
|
|
|
2388
2631
|
}
|
|
2389
2632
|
await driver.cancel(name, id);
|
|
2390
2633
|
const record = records.get(id);
|
|
2391
|
-
if (record) record
|
|
2634
|
+
if (record) transitionStatus(record, "cancelled");
|
|
2635
|
+
evictFinishedRecords();
|
|
2392
2636
|
},
|
|
2393
2637
|
async retry(name, id) {
|
|
2394
2638
|
if (!driver.retry) {
|
|
@@ -2398,7 +2642,7 @@ function createTaskQueue(deps) {
|
|
|
2398
2642
|
}
|
|
2399
2643
|
await driver.retry(name, id);
|
|
2400
2644
|
const record = records.get(id);
|
|
2401
|
-
if (record) record
|
|
2645
|
+
if (record) transitionStatus(record, "pending");
|
|
2402
2646
|
},
|
|
2403
2647
|
async start() {
|
|
2404
2648
|
if (stopped) {
|
|
@@ -2506,9 +2750,15 @@ import fs2 from "fs";
|
|
|
2506
2750
|
var TASK_PATTERNS = ["src/tasks/**/task.ts"];
|
|
2507
2751
|
var TASK_FILENAME = "task.ts";
|
|
2508
2752
|
var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
2509
|
-
var
|
|
2510
|
-
var
|
|
2511
|
-
var
|
|
2753
|
+
var NUM_LITERAL = "(\\d+(?:_\\d+)*)";
|
|
2754
|
+
var CONCURRENCY_RE = new RegExp(`(?:^|\\n)\\s*concurrency:\\s*${NUM_LITERAL}`);
|
|
2755
|
+
var RETRIES_RE = new RegExp(`(?:^|\\n)\\s*retries:\\s*${NUM_LITERAL}`);
|
|
2756
|
+
var TIMEOUT_MS_RE = new RegExp(`(?:^|\\n)\\s*timeoutMs:\\s*${NUM_LITERAL}`);
|
|
2757
|
+
var GRACE_MS_RE = new RegExp(`(?:^|\\n)\\s*graceMs:\\s*${NUM_LITERAL}`);
|
|
2758
|
+
function parseNumericLiteral(raw) {
|
|
2759
|
+
return Number(raw.replace(/_/g, ""));
|
|
2760
|
+
}
|
|
2761
|
+
var MIN_ISOLATED_TIMEOUT_MS = 6e4;
|
|
2512
2762
|
function filePathToTaskName(filePath) {
|
|
2513
2763
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2514
2764
|
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
@@ -2546,10 +2796,20 @@ async function scanTasks(rootDir, patterns) {
|
|
|
2546
2796
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
2547
2797
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
2548
2798
|
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
2799
|
+
const graceMs = GRACE_MS_RE.exec(source)?.[1];
|
|
2549
2800
|
if (cron !== void 0) manifest.cron = cron;
|
|
2550
|
-
if (concurrency !== void 0) manifest.concurrency =
|
|
2551
|
-
if (retries !== void 0) manifest.retries =
|
|
2552
|
-
if (timeoutMs !== void 0)
|
|
2801
|
+
if (concurrency !== void 0) manifest.concurrency = parseNumericLiteral(concurrency);
|
|
2802
|
+
if (retries !== void 0) manifest.retries = parseNumericLiteral(retries);
|
|
2803
|
+
if (timeoutMs !== void 0) {
|
|
2804
|
+
const value = parseNumericLiteral(timeoutMs);
|
|
2805
|
+
if (value < MIN_ISOLATED_TIMEOUT_MS) {
|
|
2806
|
+
throw new Error(
|
|
2807
|
+
`[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.`
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
manifest.timeoutMs = value;
|
|
2811
|
+
}
|
|
2812
|
+
if (graceMs !== void 0) manifest.graceMs = parseNumericLiteral(graceMs);
|
|
2553
2813
|
tasks.push(manifest);
|
|
2554
2814
|
}
|
|
2555
2815
|
return tasks.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -3673,6 +3933,7 @@ function logger(options = {}) {
|
|
|
3673
3933
|
const response = await next();
|
|
3674
3934
|
const duration = Date.now() - start;
|
|
3675
3935
|
const entry = {
|
|
3936
|
+
requestId: ctx.requestId,
|
|
3676
3937
|
method: ctx.method,
|
|
3677
3938
|
path: ctx.path,
|
|
3678
3939
|
status: response.status,
|
|
@@ -3685,6 +3946,7 @@ function logger(options = {}) {
|
|
|
3685
3946
|
const message = err instanceof Error ? err.message : String(err);
|
|
3686
3947
|
const status = err?.statusCode ?? 500;
|
|
3687
3948
|
const entry = {
|
|
3949
|
+
requestId: ctx.requestId,
|
|
3688
3950
|
method: ctx.method,
|
|
3689
3951
|
path: ctx.path,
|
|
3690
3952
|
status,
|
|
@@ -4307,6 +4569,12 @@ function formatErrorResponse(error, config) {
|
|
|
4307
4569
|
}
|
|
4308
4570
|
|
|
4309
4571
|
// src/runtime/createContext.ts
|
|
4572
|
+
function resolveRequestId(request) {
|
|
4573
|
+
const header = request.headers.get("x-request-id");
|
|
4574
|
+
const first = header?.split(",")[0]?.trim();
|
|
4575
|
+
if (first) return first;
|
|
4576
|
+
return crypto.randomUUID();
|
|
4577
|
+
}
|
|
4310
4578
|
function parseCookies(cookieHeader) {
|
|
4311
4579
|
const cookies = /* @__PURE__ */ new Map();
|
|
4312
4580
|
if (!cookieHeader) return cookies;
|
|
@@ -4340,7 +4608,13 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
4340
4608
|
for (const [key, val] of parsedCookies) {
|
|
4341
4609
|
cookiesObj[key] = val;
|
|
4342
4610
|
}
|
|
4611
|
+
const requestId = resolveRequestId(request);
|
|
4612
|
+
const log = createLogger("http", {
|
|
4613
|
+
fields: { requestId, method: request.method, path: url.pathname }
|
|
4614
|
+
});
|
|
4343
4615
|
const ctx = {
|
|
4616
|
+
requestId,
|
|
4617
|
+
log,
|
|
4344
4618
|
request,
|
|
4345
4619
|
params,
|
|
4346
4620
|
query: url.searchParams,
|
|
@@ -4725,6 +4999,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
4725
4999
|
// 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
|
|
4726
5000
|
case "tasks":
|
|
4727
5001
|
return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
|
|
5002
|
+
// 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)
|
|
5003
|
+
case "log":
|
|
5004
|
+
return ctx.log;
|
|
4728
5005
|
default:
|
|
4729
5006
|
return void 0;
|
|
4730
5007
|
}
|
|
@@ -6116,7 +6393,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
6116
6393
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
6117
6394
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
6118
6395
|
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6119
|
-
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
6396
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
6397
|
+
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
6120
6398
|
}));
|
|
6121
6399
|
}
|
|
6122
6400
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -6132,7 +6410,8 @@ function hydrateTasks(manifest) {
|
|
|
6132
6410
|
cron: t.cron ?? void 0,
|
|
6133
6411
|
concurrency: t.concurrency ?? void 0,
|
|
6134
6412
|
retries: t.retries ?? void 0,
|
|
6135
|
-
timeoutMs: t.timeoutMs ?? void 0
|
|
6413
|
+
timeoutMs: t.timeoutMs ?? void 0,
|
|
6414
|
+
graceMs: t.graceMs ?? void 0
|
|
6136
6415
|
}));
|
|
6137
6416
|
}
|
|
6138
6417
|
function collectTaskSchemaSources(tasks, rootDir) {
|
|
@@ -6558,6 +6837,7 @@ var FAAPI_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
6558
6837
|
"etag",
|
|
6559
6838
|
"bodyLimit",
|
|
6560
6839
|
"logger",
|
|
6840
|
+
"log",
|
|
6561
6841
|
"http2",
|
|
6562
6842
|
"trustedProxy",
|
|
6563
6843
|
"response",
|
|
@@ -6576,6 +6856,7 @@ async function createAppBase(options) {
|
|
|
6576
6856
|
);
|
|
6577
6857
|
}
|
|
6578
6858
|
const config = await loadConfig(rootDir, dist);
|
|
6859
|
+
configureLogging(config?.log);
|
|
6579
6860
|
const serialized = await importWithCacheBust(routesPath);
|
|
6580
6861
|
const hydrated = await hydrateRoutes(serialized);
|
|
6581
6862
|
let sorted = sortRoutes(hydrated.routes);
|
|
@@ -7274,11 +7555,13 @@ export {
|
|
|
7274
7555
|
ValidationError,
|
|
7275
7556
|
clearAgentHandleFactory,
|
|
7276
7557
|
collectRouteSchemaSources,
|
|
7558
|
+
configureLogging,
|
|
7277
7559
|
cors,
|
|
7278
7560
|
createProdApp as createApp,
|
|
7279
7561
|
createAppRegistries,
|
|
7280
7562
|
createCronScheduler,
|
|
7281
7563
|
createDevApp,
|
|
7564
|
+
createLogger,
|
|
7282
7565
|
createProdApp,
|
|
7283
7566
|
createProgram,
|
|
7284
7567
|
createPrograms,
|