fluxion-ts 0.7.1 → 0.8.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","fs","http","cluster","os","https","http","cluster","fs","path","path","fs","path","fs","path","cluster"],"sources":["../src/common/dtm.ts","../src/common/native.ts","../src/common/color.ts","../src/common/logger.ts","../src/http/options.ts","../src/cluster/consts.ts","../src/cluster/communicate.ts","../src/common/consts.ts","../src/http/respond.ts","../src/cluster/meta-api.ts","../src/cluster/primary.ts","../src/common/promise-try.ts","../src/http/headers.ts","../src/http/request.ts","../src/http/query.ts","../src/http/body.ts","../src/http/cookie.ts","../src/cluster/server.ts","../src/cluster/worker.ts","../src/watcher/base.ts","../src/watcher/chokidar.ts","../src/watcher/native.ts","../node_modules/.pnpm/type-narrow@0.2.2/node_modules/type-narrow/dist/index.mjs","../src/common/injector.ts","../src/router/index.ts","../src/fluxion.ts","../src/index.ts"],"sourcesContent":["export function dtm(dt = new Date()) {\n const y = dt.getFullYear();\n const m = String(dt.getMonth() + 1).padStart(2, '0');\n const d = String(dt.getDate()).padStart(2, '0');\n const hh = String(dt.getHours()).padStart(2, '0');\n const mm = String(dt.getMinutes()).padStart(2, '0');\n const ss = String(dt.getSeconds()).padStart(2, '0');\n const ms = String(dt.getMilliseconds()).padStart(3, '0');\n return `${y}.${m}.${d} ${hh}:${mm}:${ss}.${ms}`;\n}\n","export const $stringify = JSON.stringify;\n\nexport const $keys = Object.keys;\n","const useColor = process.env.FLUXION_COLORS !== '0';\n\n/**\n * Color Control Characters for Terminal (cctl)\n */\nexport namespace cctl {\n export const reset = useColor ? '\\x1b[0m' : '';\n export const bold = useColor ? '\\x1b[1m' : '';\n export const dim = useColor ? '\\x1b[2m' : '';\n export const italic = useColor ? '\\x1b[3m' : '';\n export const underline = useColor ? '\\x1b[4m' : '';\n export const blink = useColor ? '\\x1b[5m' : '';\n export const inverse = useColor ? '\\x1b[7m' : '';\n\n export const black = useColor ? '\\x1b[30m' : '';\n export const red = useColor ? '\\x1b[31m' : '';\n export const green = useColor ? '\\x1b[32m' : '';\n export const yellow = useColor ? '\\x1b[33m' : '';\n export const blue = useColor ? '\\x1b[34m' : '';\n export const magenta = useColor ? '\\x1b[35m' : '';\n export const cyan = useColor ? '\\x1b[36m' : '';\n export const white = useColor ? '\\x1b[37m' : '';\n\n export const brightBlack = useColor ? '\\x1b[90m' : '';\n export const brightRed = useColor ? '\\x1b[91m' : '';\n export const brightGreen = useColor ? '\\x1b[92m' : '';\n export const brightYellow = useColor ? '\\x1b[93m' : '';\n export const brightBlue = useColor ? '\\x1b[94m' : '';\n export const brightMagenta = useColor ? '\\x1b[95m' : '';\n export const brightCyan = useColor ? '\\x1b[96m' : '';\n export const brightWhite = useColor ? '\\x1b[97m' : '';\n\n export const bgBlack = useColor ? '\\x1b[40m' : '';\n export const bgRed = useColor ? '\\x1b[41m' : '';\n export const bgGreen = useColor ? '\\x1b[42m' : '';\n export const bgYellow = useColor ? '\\x1b[43m' : '';\n export const bgBlue = useColor ? '\\x1b[44m' : '';\n export const bgMagenta = useColor ? '\\x1b[45m' : '';\n export const bgCyan = useColor ? '\\x1b[46m' : '';\n export const bgWhite = useColor ? '\\x1b[47m' : '';\n\n export const bgBrightBlack = useColor ? '\\x1b[100m' : '';\n export const bgBrightRed = useColor ? '\\x1b[101m' : '';\n export const bgBrightGreen = useColor ? '\\x1b[102m' : '';\n export const bgBrightYellow = useColor ? '\\x1b[103m' : '';\n export const bgBrightBlue = useColor ? '\\x1b[104m' : '';\n export const bgBrightMagenta = useColor ? '\\x1b[105m' : '';\n export const bgBrightCyan = useColor ? '\\x1b[106m' : '';\n export const bgBrightWhite = useColor ? '\\x1b[107m' : '';\n\n // 'rgb(225, 16, 248)';\n export const purple = useColor ? '\\x1b[38;2;225;16;248m' : '';\n // 'rgb(248, 147, 16)';\n export const orange = useColor ? '\\x1b[38;2;248;147;16m' : '';\n export const darkGreen = useColor ? '\\x1b[38;2;22;101;52m' : '';\n export const claude = useColor ? '\\x1b[38;2;217;119;87m' : '';\n export const deepseek = useColor ? '\\x1b[38;2;57;100;254m' : '';\n export const gpt = useColor ? '\\x1b[38;2;41;60;77m' : '';\n}\n","import type { FluxionContext } from '@/types.js';\nimport type { otherstring } from '@/global.js';\n\nimport { dtm } from './dtm.js';\nimport { $keys, $stringify } from './native.js';\nimport { cctl } from './color.js';\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'SUCC' | 'DEBUG' | 'VERBOSE' | otherstring;\n\ninterface LogEntry {\n timestamp: string;\n level: LogLevel;\n event: string;\n message?: string;\n [key: string]: unknown;\n}\n\nexport type LoggerOption = 'one-line' | 'json-line' | FluxionLoggerFn;\n\nexport type FluxionLoggerFn = (entry: LogEntry) => void;\n\nexport interface FluxionLogger {\n /**\n * [WARN] We assert that `fields` is an object or undefined.\n */\n write(level: LogLevel, event: string, fields?: object): void;\n info(event: string, fields?: object): void;\n warn(event: string, fields?: object): void;\n error(event: string, fields?: object): void;\n succ(event: string, fields?: object): void;\n debug(event: string, fields?: object): void;\n verbose(event: string, fields?: object): void;\n}\n\nconst safeStringify = (value: unknown): string => {\n try {\n return $stringify(value);\n } catch {\n return '[unserializable]';\n }\n};\n\nconst ColoredLevels: Record<LogLevel, string> = {\n INFO: `${cctl.cyan}INFO${cctl.reset}`,\n WARN: `${cctl.orange}WARN${cctl.reset}`,\n ERROR: `${cctl.red}ERROR${cctl.reset}`,\n SUCC: `${cctl.green}SUCC${cctl.reset}`,\n DEBUG: `${cctl.blue}DEBUG${cctl.reset}`,\n VERBOSE: `${cctl.purple}VERBOSE${cctl.reset}`,\n};\n\nexport const oneLineLogger: FluxionLoggerFn = (entry: LogEntry) => {\n const { level: rawLevel, timestamp: rawTimestamp, event: rawEvent, message: rawMessage, ...fields } = entry;\n\n const timestamp = `${cctl.darkGreen}[${rawTimestamp}]${cctl.reset}`;\n const level = ColoredLevels[rawLevel] ?? rawLevel;\n const body = rawMessage ?? rawEvent;\n const fieldsText = $keys(fields).length > 0 ? `${cctl.dim}${safeStringify(fields)}${cctl.reset}` : '';\n\n console.log(`${timestamp} ${level} ${body}${fieldsText}`);\n};\n\n/**\n * & Logger Options here is checked by normalizeOptions function.\n */\nfunction resolveLoggerSink(cx: Pick<FluxionContext, 'options'>): FluxionLoggerFn {\n const loggerOption = cx.options.logger;\n if (loggerOption === undefined || loggerOption === 'one-line') {\n return oneLineLogger;\n }\n\n if (loggerOption === 'json-line') {\n return (entry: LogEntry) => console.log(safeStringify(entry));\n }\n\n return loggerOption;\n}\n\nexport function createLogger(cx: Pick<FluxionContext, 'options'>): FluxionLogger {\n const sink = resolveLoggerSink(cx);\n\n const logger: FluxionLogger = {\n write(level: LogLevel, event: string, fields: Record<string, unknown> = {}): void {\n const entry: LogEntry = {\n ...fields,\n timestamp: dtm(),\n level,\n event,\n };\n\n try {\n sink(entry);\n } catch {\n // Ignore logger sink failures to avoid breaking request handling.\n }\n },\n info(event: string, fields?: Record<string, unknown>): void {\n this.write('INFO', event, fields);\n },\n warn(event: string, fields?: Record<string, unknown>): void {\n this.write('WARN', event, fields);\n },\n error(event: string, fields?: Record<string, unknown>): void {\n this.write('ERROR', event, fields);\n },\n succ(event: string, fields?: Record<string, unknown>): void {\n this.write('SUCC', event, fields);\n },\n debug(event: string, fields?: Record<string, unknown>): void {\n this.write('DEBUG', event, fields);\n },\n verbose(event: string, fields?: Record<string, unknown>): void {\n this.write('VERBOSE', event, fields);\n },\n };\n\n return logger;\n}\n\n/**\n * Create a worker logger that prefixes all log messages with the worker PID.\n */\nexport function createWorkerLogger(baseLogger: FluxionLogger, pid: number): FluxionLogger {\n const pidPrefix = `[${pid}]`;\n\n return {\n write(level: LogLevel, event: string, fields?: object): void {\n baseLogger.write(level, `${pidPrefix} ${event}`, fields);\n },\n info(event: string, fields?: object): void {\n baseLogger.info(`${pidPrefix} ${event}`, fields);\n },\n warn(event: string, fields?: object): void {\n baseLogger.warn(`${pidPrefix} ${event}`, fields);\n },\n error(event: string, fields?: object): void {\n baseLogger.error(`${pidPrefix} ${event}`, fields);\n },\n succ(event: string, fields?: object): void {\n baseLogger.succ(`${pidPrefix} ${event}`, fields);\n },\n debug(event: string, fields?: object): void {\n baseLogger.debug(`${pidPrefix} ${event}`, fields);\n },\n verbose(event: string, fields?: object): void {\n baseLogger.verbose(`${pidPrefix} ${event}`, fields);\n },\n };\n}\n\n/**\n * ! Error.isError needs Node.js 24\n */\nexport const getErrorMessage =\n typeof Error.isError === 'function'\n ? (e: unknown): string => (Error.isError(e) ? e.message : String(e))\n : (e: unknown): string => (e as any)?.message || String(e);\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { WorkerOptions, FluxionOptions, NormalizedFluxionOptions } from '../types.js';\n\n/**\n * Resolves runtime options with framework defaults.\n */\nfunction resolveWorkerOptions(options: Partial<WorkerOptions>): WorkerOptions {\n return {\n maxWorkerCount: options.maxWorkerCount ?? 4,\n maxInflight: options.maxInflight ?? 64,\n memorySoftLimitMb: options.memorySoftLimitMb ?? 96,\n memoryHardLimitMb: options.memoryHardLimitMb ?? 128,\n memorySampleIntervalMs: options.memorySampleIntervalMs ?? 5000,\n maxOldGenerationSizeMb: options.maxOldGenerationSizeMb ?? 128,\n maxYoungGenerationSizeMb: options.maxYoungGenerationSizeMb ?? 32,\n stackSizeMb: options.stackSizeMb ?? 4,\n maxResponseBytes: options.maxResponseBytes ?? 2 * 1024 * 1024,\n };\n}\n\n/**\n * Read certificate content from a file path or return the content directly.\n */\nfunction readCertificateContent(content: string | Buffer, moduleDir: string): Buffer {\n if (Buffer.isBuffer(content)) {\n return content;\n }\n if (typeof content === 'string') {\n // Check if it looks like a file path (not a PEM certificate)\n // PEM certificates start with \"-----BEGIN\"\n if (!content.startsWith('-----BEGIN')) {\n const filePath = path.isAbsolute(content) ? content : path.join(moduleDir, content);\n if (fs.existsSync(filePath)) {\n return fs.readFileSync(filePath);\n }\n }\n return Buffer.from(content);\n }\n $throw('Certificate content must be a string or Buffer');\n}\n\n/**\n * Normalize HTTPS options.\n */\nfunction normalizeHttpsOptions(\n https: FluxionOptions['https'],\n moduleDir: string,\n): NormalizedFluxionOptions['https'] | undefined {\n if (!https) {\n return undefined;\n }\n\n if (typeof https !== 'object' || https === null || Array.isArray(https)) {\n $throw('FluxionOptions.https must be an object');\n }\n if (typeof https.key !== 'string') {\n $throw('FluxionOptions.https.key must be a string');\n }\n if (typeof https.cert !== 'string') {\n $throw('FluxionOptions.https.cert must be a string');\n }\n\n const result: NormalizedFluxionOptions['https'] = {\n key: readCertificateContent(https.key, moduleDir),\n cert: readCertificateContent(https.cert, moduleDir),\n };\n\n if (https.ca !== undefined) {\n if (Array.isArray(https.ca)) {\n result.ca = https.ca.map((item) => readCertificateContent(item, moduleDir));\n } else {\n result.ca = readCertificateContent(https.ca, moduleDir);\n }\n }\n\n return result;\n}\n\n/**\n * Normalize options and create necessary resources like the dynamic directory and logger.\n */\nexport function normalizeOptions(options: FluxionOptions): NormalizedFluxionOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n $throw('FluxionOptions must be an object');\n }\n\n let {\n dir,\n host,\n port,\n handlerTimeoutMs = 5000,\n metaPort,\n moduleDir = process.cwd(),\n workerOptions = {},\n maxRequestBytes = 8_000_000,\n reloadDelay = 500,\n include = ['**/*'],\n apiInclude = ['**/*.ts'],\n exclude = [\n '**/node_modules/**',\n '**/.git/**',\n '**/dist/**',\n '**/build/**',\n '**/.vscode/**',\n '**/.idea/**',\n '**/*.log',\n '**/.DS_Store',\n '**/coverage/**',\n '**/.nyc_output/**',\n '**/*.tmp',\n '**/*.temp',\n ],\n https,\n nativeWatcher = false,\n } = options as FluxionOptions;\n\n const logger = options.logger ?? 'one-line';\n if (logger !== 'one-line' && logger !== 'json-line' && typeof logger !== 'function') {\n $throw(`Invalid logger option, Must be 'one-line', 'json-line' or a custom logger function`);\n }\n\n if (typeof dir !== 'string') {\n $throw('FluxionOptions.dir must be a string');\n }\n\n if (typeof moduleDir !== 'string') {\n $throw('FluxionOptions.moduleDir must be a string');\n }\n\n if (typeof host !== 'string') {\n $throw('FluxionOptions.host must be a string');\n }\n\n if (!Number.isSafeInteger(handlerTimeoutMs) || handlerTimeoutMs <= 100) {\n $throw('FluxionOptions.handlerTimeoutMs must be an integer greater than 100');\n }\n\n if (typeof reloadDelay !== 'number' || reloadDelay <= 0 || !Number.isSafeInteger(reloadDelay)) {\n $throw('FluxionOptions.reloadDelay must be a positive integer');\n }\n\n if (reloadDelay < 50) {\n $throw('FluxionOptions.reloadDelay must be greater than or equal to 50');\n }\n\n if (typeof port !== 'number' || !Number.isSafeInteger(port)) {\n $throw('FluxionOptions.port must be a positive integer');\n }\n\n if (port <= 1 || port > 65535) {\n $throw('FluxionOptions.port must be 1 ~ 65535');\n }\n\n metaPort ??= port + 1;\n if (typeof metaPort !== 'number' || !Number.isSafeInteger(metaPort)) {\n $throw('FluxionOptions.metaPort must be a positive integer');\n }\n\n if (metaPort <= 1 || metaPort > 65535) {\n $throw('FluxionOptions.metaPort must be 1 ~ 65535');\n }\n\n if (metaPort === port) {\n $throw('FluxionOptions.metaPort must be different from FluxionOptions.port');\n }\n\n if (typeof workerOptions !== 'object' || workerOptions === null || Array.isArray(workerOptions)) {\n $throw('FluxionOptions.workerOptions must be an object');\n }\n\n if (typeof maxRequestBytes !== 'number' || maxRequestBytes <= 0 || !Number.isSafeInteger(maxRequestBytes)) {\n $throw('FluxionOptions.maxRequestBytes must be a positive integer');\n }\n\n dir = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n return {\n dir,\n host,\n port,\n handlerTimeoutMs,\n reloadDelay,\n metaPort,\n moduleDir,\n workerOptions: resolveWorkerOptions(workerOptions),\n maxRequestBytes,\n logger,\n include,\n apiInclude,\n exclude,\n nativeWatcher,\n https: normalizeHttpsOptions(https, moduleDir),\n };\n}\n","import type { PrimaryMessage as PrimaryMessage, WorkerMessage as WorkerMessage } from './types.js';\n\nexport const enum PrimaryAction {\n /**\n * Health check message, the worker should respond with Pong and the latency information\n */\n Ping = 100,\n}\n\nexport const enum WorkerAction {\n /**\n * Just created\n */\n Created = 200,\n\n /**\n * Ready for tasks\n * - fluxion options are injected\n */\n Ready,\n\n /**\n * Response to Ping, used for health check and latency measurement\n */\n Pong,\n\n /**\n * Runtime telemetry snapshot from worker process.\n */\n Stats,\n}\n\nexport const isPrimaryMessage = (v: PrimaryMessage): v is PrimaryMessage => [PrimaryAction.Ping].includes(v?.type);\n\nexport const isWorkerMessage = (v: WorkerMessage): v is WorkerMessage =>\n [WorkerAction.Pong, WorkerAction.Created, WorkerAction.Ready, WorkerAction.Stats].includes(v?.type);\n","import type cluster from 'node:cluster';\nimport type { PrimaryMessage, WorkerMessage } from './types.js';\n\nexport const sendToPrimary = (message: WorkerMessage) => process.send?.(message);\n\nexport const sendToWorker = (worker: cluster.Worker, message: PrimaryMessage) => worker.send(message);\n","export const DUMMY_BASE_URL = 'http://fluxion.local';\nexport const META_PREFIX = '/_fluxion';\n\nexport const STATIC_HANDLED_FLAG = Symbol.for('fluxion.router.StaticHandled');\nexport const HANDLER_TIMEOUT_FLAG = Symbol.for('fluxion.handlerTimeout');\n\nexport const STATIC_CONTENT_TYPES: Record<string, string> = {\n '.css': 'text/css; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.ico': 'image/x-icon',\n '.js': 'text/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain; charset=utf-8',\n '.webp': 'image/webp',\n};\n\nexport const enum HttpCode {\n Ok = 200,\n BadRequest = 400,\n PayloadTooLarge = 413,\n NotFound = 404,\n InternalServerError = 500,\n}\n\nexport const enum HandlerResult {\n NotFound,\n Handled,\n}\n\nexport const noop = () => {};\n","import type { ServerResponse } from 'node:http';\nimport { HttpCode } from '@/common/consts.js';\n\nexport function sendJson(res: ServerResponse, payload: unknown, statusCode: HttpCode = HttpCode.Ok): void {\n res.statusCode = statusCode;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n res.end(JSON.stringify(payload));\n}\n\nexport function safeSendJson(res: ServerResponse, payload: unknown, statusCode: HttpCode = HttpCode.Ok): void {\n if (res.writableEnded) {\n return;\n }\n\n if (res.headersSent) {\n res.end();\n return;\n }\n\n sendJson(res, payload, statusCode);\n}\n","import http from 'node:http';\n\nimport { getErrorMessage } from '@/common/logger.js';\nimport { HttpCode, META_PREFIX } from '@/common/consts.js';\nimport { sendJson } from '../http/respond.js';\nimport { FluxionContext } from '../types.js';\n\nexport function createPrimaryMetaApiServer(\n cx: Pick<FluxionContext, 'logger' | 'options' | 'router'>,\n getWorkersSnapshot: () => unknown,\n): http.Server {\n const server = http.createServer((req, res) => {\n const method = req.method ?? 'GET';\n\n let pathname = '/';\n try {\n pathname = new URL(req.url ?? '/', 'http://fluxion.local').pathname;\n } catch {\n sendJson(res, { message: 'Bad Request: invalid url' }, HttpCode.BadRequest);\n return;\n }\n\n if (method === 'GET' && pathname === META_PREFIX + '/healthz') {\n sendJson(res, {\n ok: true,\n role: 'primary',\n pid: process.pid,\n now: Date.now(),\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n });\n return;\n }\n\n if (method === 'GET' && pathname === META_PREFIX + '/workers') {\n sendJson(res, {\n ok: true,\n now: Date.now(),\n workers: getWorkersSnapshot(),\n });\n return;\n }\n\n sendJson(res, { message: 'Not Found' }, HttpCode.NotFound);\n });\n\n server.on('listening', () => {\n cx.logger.info('MetaApiStarted', {\n pid: process.pid,\n host: cx.options.host,\n port: cx.options.metaPort,\n prefix: META_PREFIX,\n });\n });\n\n server.on('error', (error) => {\n cx.logger.error('MetaApiError', {\n host: cx.options.host,\n port: cx.options.metaPort,\n error: getErrorMessage(error),\n });\n process.exit(1);\n });\n\n server.listen(cx.options.metaPort, cx.options.host);\n return server;\n}\n","import type { WorkerMessage, WorkerState } from './types.js';\nimport type { FluxionContext } from '../types.js';\nimport os from 'node:os';\nimport cluster from 'node:cluster';\n\nimport { isWorkerMessage, WorkerAction, PrimaryAction } from './consts.js';\nimport { sendToWorker } from './communicate.js';\nimport { createPrimaryMetaApiServer } from './meta-api.js';\n\nconst bytesToMb = (bytes: number) => Number((bytes / 1024 / 1024).toFixed(2));\n\nexport function initPrimary(cx: Pick<FluxionContext, 'logger' | 'options' | 'router'>) {\n if (!cluster.isPrimary) {\n $throw('createPrimary should only be called in primary process');\n }\n\n const { workerOptions } = cx.options;\n const cpuCount = Math.max(1, os.cpus().length);\n const workerCount = Math.max(1, Math.min(workerOptions.maxWorkerCount ?? Math.min(2, cpuCount), cpuCount));\n\n cx.logger.info('PrimaryStarted', {\n pid: process.pid,\n workers: workerCount,\n host: cx.options.host,\n port: cx.options.port,\n metaPort: cx.options.metaPort,\n });\n\n const workers = new Map<number, WorkerState>();\n\n const getWorkersSnapshot = () => {\n return {\n primaryPid: process.pid,\n host: cx.options.host,\n port: cx.options.port,\n metaPort: cx.options.metaPort,\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n workers: Array.from(workers.entries()).map(([workerId, info]) => {\n const { instance } = info;\n const stats = info.lastStats;\n return {\n workerId,\n pid: info.pid ?? instance.process.pid ?? null,\n state: info.state,\n createdAt: info.createdAt,\n readyAt: info.readyAt ?? null,\n connected: instance.isConnected(),\n dead: instance.isDead(),\n exitedAfterDisconnect: instance.exitedAfterDisconnect,\n lastPongAt: info.lastPongAt ?? null,\n lastRttMs: info.lastRttMs ?? null,\n stats:\n stats === undefined\n ? null\n : {\n at: stats.at,\n uptimeSeconds: stats.uptimeSeconds,\n cpu: stats.cpu,\n memory: {\n ...stats.memory,\n rssMb: bytesToMb(stats.memory.rss),\n heapTotalMb: bytesToMb(stats.memory.heapTotal),\n heapUsedMb: bytesToMb(stats.memory.heapUsed),\n externalMb: bytesToMb(stats.memory.external),\n arrayBuffersMb: bytesToMb(stats.memory.arrayBuffers),\n },\n },\n };\n }),\n };\n };\n\n createPrimaryMetaApiServer(cx, getWorkersSnapshot);\n\n const attachWorker = (worker: cluster.Worker): void => {\n const workerInfo: WorkerState = {\n state: 'creating',\n pid: worker.process.pid,\n createdAt: Date.now(),\n instance: worker,\n };\n workers.set(worker.id, workerInfo);\n\n worker.on('message', (raw: WorkerMessage) => {\n if (!isWorkerMessage(raw)) {\n return;\n }\n\n if (raw.type === WorkerAction.Pong) {\n const rtt = Date.now() - raw.sentAt;\n workerInfo.pid = raw.pid;\n workerInfo.lastPongAt = Date.now();\n workerInfo.lastRttMs = rtt;\n return;\n }\n\n if (raw.type === WorkerAction.Ready) {\n workerInfo.state = 'ready';\n workerInfo.pid = raw.pid;\n workerInfo.readyAt = Date.now();\n cx.logger.info('WorkerReady', { workerId: worker.id, pid: raw.pid });\n return;\n }\n\n if (raw.type === WorkerAction.Created) {\n workerInfo.state = 'created';\n workerInfo.pid = raw.pid;\n cx.logger.info('WorkerCreated', { workerId: worker.id, pid: raw.pid });\n return;\n }\n\n if (raw.type === WorkerAction.Stats) {\n workerInfo.pid = raw.pid;\n workerInfo.lastStats = raw.stats;\n }\n });\n\n worker.on('exit', (code, signal) => {\n workers.delete(worker.id);\n cx.logger.warn('WorkerExited', {\n workerId: worker.id,\n pid: worker.process.pid ?? 'unknown',\n code,\n signal: signal ?? 'none',\n });\n });\n };\n\n for (let i = 0; i < workerCount; i++) {\n attachWorker(cluster.fork({ WORKER_ID: String(i + 1) }));\n }\n\n const pingTimer = setInterval(() => {\n const sentAt = Date.now();\n for (const info of workers.values()) {\n if (!info.instance.isConnected()) {\n continue;\n }\n try {\n sendToWorker(info.instance, { type: PrimaryAction.Ping, sentAt });\n } catch {\n // Ignore transient IPC errors; worker lifecycle events will reconcile state.\n }\n }\n }, 5000);\n pingTimer.unref();\n}\n","/**\n * For low version of Node.js that does not support `Promise.try`, we can implement it ourselves.\n *\n * Only for async functions.\n */\nexport function PromiseTry<T extends (...args: any[]) => any>(fn: T, ...args: Parameters<T>) {\n return new Promise<ReturnType<T>>((resolve, reject) => {\n // in case `fn` throws synchronously, we catch it and reject the promise\n try {\n const r = fn(...args);\n if (r instanceof Promise) {\n r.then(resolve).catch(reject);\n } else {\n resolve(r);\n }\n } catch (error) {\n reject(error);\n }\n });\n}\n","import type { IncomingMessage } from 'node:http';\n\nexport function getRealIp(req: IncomingMessage): string {\n const forwardedFor = req.headersDistinct['x-forwarded-for'];\n if (forwardedFor) {\n const firstForwarded = forwardedFor[0]?.split(',')[0]?.trim();\n if (firstForwarded && firstForwarded.length > 0) {\n return firstForwarded;\n }\n }\n\n const realIp = req.headersDistinct['x-real-ip']?.[0].trim();\n if (realIp !== undefined) {\n return realIp;\n }\n\n return req.socket.remoteAddress ?? 'unknown';\n}\n\nexport function isTextualContentType(contentType: string | undefined): boolean {\n if (contentType === undefined) {\n return false;\n }\n\n const normalized = contentType.toLowerCase();\n\n return (\n normalized.startsWith('text/') ||\n normalized.includes('json') ||\n normalized.includes('xml') ||\n normalized.includes('x-www-form-urlencoded') ||\n normalized.includes('javascript')\n );\n}\n","import { DUMMY_BASE_URL } from '@/common/consts.js';\n\nexport function toURL(rawUrl: string | undefined): URL | undefined {\n if (rawUrl === undefined) {\n return undefined;\n }\n\n try {\n return new URL(rawUrl, DUMMY_BASE_URL);\n } catch {\n return undefined;\n }\n}\n","export function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const query: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = query[key];\n\n if (existing === undefined) {\n query[key] = value;\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n query[key] = [existing, value];\n }\n\n return query;\n}\n","import type http from 'node:http';\n\nimport { isTextualContentType } from './headers.js';\nimport { parseQuery } from './query.js';\n\nexport interface BodyPreview {\n exists: boolean;\n value?: string;\n bytes: number;\n truncated: boolean;\n}\n\nfunction createRequestBodyTooLargeError(receivedBytes: number, maxBytes: number): NodeJS.ErrnoException {\n const sizeError = new Error(\n `request body too large: ${receivedBytes.toString()} bytes exceeds ${maxBytes.toString()} bytes`,\n ) as NodeJS.ErrnoException;\n\n sizeError.code = 'REQUEST_BODY_TOO_LARGE';\n\n return sizeError;\n}\n\nfunction getHeaderValue(headerValue: string | string[] | undefined): string | undefined {\n return Array.isArray(headerValue) ? headerValue[0] : headerValue;\n}\n\nfunction createEmptyPreview(): BodyPreview {\n return {\n exists: false,\n bytes: 0,\n truncated: false,\n };\n}\n\nfunction createPreview(\n previewBuffer: Buffer,\n totalBytes: number,\n contentType: string | undefined,\n truncated: boolean,\n): BodyPreview {\n if (totalBytes === 0) {\n return createEmptyPreview();\n }\n\n if (isTextualContentType(contentType)) {\n return {\n exists: true,\n value: previewBuffer.toString('utf8'),\n bytes: totalBytes,\n truncated,\n };\n }\n\n return {\n exists: true,\n value: `<binary body: ${totalBytes} bytes>`,\n bytes: totalBytes,\n truncated,\n };\n}\n\nasync function readRequestBodyWithPreview(\n req: http.IncomingMessage,\n method: string,\n maxBytes: number,\n previewMaxBytes = 8192,\n): Promise<{ rawBody: Buffer | undefined; preview: BodyPreview }> {\n if (method === 'GET' || method === 'HEAD') {\n return {\n rawBody: undefined,\n preview: createEmptyPreview(),\n };\n }\n\n if (req.readableEnded) {\n return {\n rawBody: undefined,\n preview: createEmptyPreview(),\n };\n }\n\n const contentLengthRaw = getHeaderValue(req.headers['content-length']);\n const declaredBytes = contentLengthRaw !== undefined ? Number.parseInt(contentLengthRaw, 10) : NaN;\n\n if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {\n throw createRequestBodyTooLargeError(declaredBytes, maxBytes);\n }\n\n return new Promise((resolve, reject) => {\n const rawBodyChunks: Buffer[] = [];\n const previewChunks: Buffer[] = [];\n let totalBytes = 0;\n let previewBytes = 0;\n let previewTruncated = false;\n let settled = false;\n\n const cleanup = (): void => {\n req.off('data', onData);\n req.off('end', onEnd);\n req.off('error', onError);\n req.off('aborted', onAborted);\n };\n\n const settle = (action: () => void): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n action();\n };\n\n const onData = (chunk: Buffer | string | Uint8Array): void => {\n const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n totalBytes += bufferChunk.byteLength;\n\n if (totalBytes > maxBytes) {\n cleanup();\n req.resume();\n settle(() => {\n reject(createRequestBodyTooLargeError(totalBytes, maxBytes));\n });\n return;\n }\n\n rawBodyChunks.push(bufferChunk);\n\n if (previewBytes < previewMaxBytes) {\n const remaining = previewMaxBytes - previewBytes;\n const nextSlice = bufferChunk.subarray(0, remaining);\n previewChunks.push(nextSlice);\n previewBytes += nextSlice.length;\n\n if (nextSlice.length < bufferChunk.length) {\n previewTruncated = true;\n }\n } else {\n previewTruncated = true;\n }\n };\n\n const onEnd = (): void => {\n cleanup();\n settle(() => {\n const rawBody = rawBodyChunks.length > 0 ? Buffer.concat(rawBodyChunks) : undefined;\n const previewBuffer = previewChunks.length > 0 ? Buffer.concat(previewChunks) : Buffer.alloc(0);\n\n resolve({\n rawBody,\n preview: createPreview(\n previewBuffer,\n rawBody?.byteLength ?? 0,\n getHeaderValue(req.headers['content-type']),\n previewTruncated,\n ),\n });\n });\n };\n\n const onError = (error: Error): void => {\n cleanup();\n settle(() => {\n reject(error);\n });\n };\n\n const onAborted = (): void => {\n cleanup();\n settle(() => {\n reject(new Error('request aborted while reading body'));\n });\n };\n\n req.on('data', onData);\n req.once('end', onEnd);\n req.once('error', onError);\n req.once('aborted', onAborted);\n });\n}\n\nexport async function parseBody(\n req: http.IncomingMessage,\n method: string,\n maxBytes: number,\n): Promise<{ body: Record<string, any>; preview: BodyPreview }> {\n const { rawBody, preview } = await readRequestBodyWithPreview(req, method, maxBytes);\n\n if (rawBody === undefined || rawBody.byteLength === 0) {\n return {\n body: {},\n preview,\n };\n }\n\n const contentType = getHeaderValue(req.headers['content-type'])?.toLowerCase() ?? '';\n\n if (contentType.includes('json')) {\n const textBody = rawBody.toString('utf8').trim();\n\n if (textBody.length === 0) {\n return {\n body: {},\n preview,\n };\n }\n\n try {\n const parsed = JSON.parse(textBody) as unknown;\n\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return {\n body: parsed as Record<string, any>,\n preview,\n };\n }\n\n return {\n body: { value: parsed },\n preview,\n };\n } catch {\n return {\n body: { raw: textBody },\n preview,\n };\n }\n }\n\n if (contentType.includes('x-www-form-urlencoded')) {\n return {\n body: parseQuery(new URLSearchParams(rawBody.toString('utf8'))),\n preview,\n };\n }\n\n if (isTextualContentType(contentType)) {\n return {\n body: { raw: rawBody.toString('utf8') },\n preview,\n };\n }\n\n return {\n body: {},\n preview,\n };\n}\n","/**\n * Parse Cookie header string into an object\n */\nexport function parseCookie(cookieHeader: string | undefined): Record<string, string> {\n if (!cookieHeader) {\n return {};\n }\n\n const cookies: Record<string, string> = {};\n const pairs = cookieHeader.split(';');\n\n for (const pair of pairs) {\n const [key, ...valueParts] = pair.split('=');\n if (!key) continue;\n\n const trimmedKey = key.trim();\n const value = valueParts.join('=').trim();\n cookies[trimmedKey] = decodeURIComponent(value);\n }\n\n return cookies;\n}\n\n/**\n * Serialize an object into a Cookie header string\n */\nexport function serializeCookie(name: string, value: string, options?: {\n maxAge?: number;\n expires?: Date;\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n}): string {\n let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (options) {\n if (options.maxAge !== undefined) {\n cookie += `; Max-Age=${options.maxAge}`;\n }\n if (options.expires) {\n cookie += `; Expires=${options.expires.toUTCString()}`;\n }\n if (options.domain) {\n cookie += `; Domain=${options.domain}`;\n }\n if (options.path) {\n cookie += `; Path=${options.path}`;\n }\n if (options.secure) {\n cookie += '; Secure';\n }\n if (options.httpOnly) {\n cookie += '; HttpOnly';\n }\n if (options.sameSite) {\n cookie += `; SameSite=${options.sameSite}`;\n }\n }\n\n return cookie;\n}\n","import http from 'node:http';\nimport https from 'node:https';\n\nimport type { FluxionContext, NormalizedRequest } from '../types.js';\nimport { $keys } from '@/common/native.js';\nimport { HttpCode, HANDLER_TIMEOUT_FLAG, META_PREFIX, STATIC_HANDLED_FLAG } from '@/common/consts.js';\nimport { PromiseTry } from '@/common/promise-try.js';\nimport { getErrorMessage } from '@/common/logger.js';\n\nimport { getRealIp } from '../http/headers.js';\nimport { toURL } from '../http/request.js';\nimport { safeSendJson } from '../http/respond.js';\nimport { parseBody, type BodyPreview } from '../http/body.js';\nimport { parseQuery } from '../http/query.js';\nimport { parseCookie } from '../http/cookie.js';\n\nexport function createWorkerServer(cx: FluxionContext): http.Server | https.Server {\n const requestHandler = async (req: http.IncomingMessage, res: http.ServerResponse) => {\n const method = req.method ?? 'GET';\n const ip = getRealIp(req);\n const url = toURL(req.url);\n if (url === undefined) {\n safeSendJson(res, { message: 'Bad Request: req.url is undefined' }, HttpCode.BadRequest);\n return;\n }\n\n const normalized: NormalizedRequest = {\n method,\n ip,\n url,\n query: parseQuery(url.searchParams),\n body: {},\n headers: req.headers,\n cookie: parseCookie(req.headers.cookie as string | undefined),\n };\n\n let bodyPreview: BodyPreview = {\n exists: false,\n bytes: 0,\n truncated: false,\n };\n\n cx.logger.info('Req', { method, ip, path: url.pathname });\n\n const start = performance.now();\n res.once('finish', () => {\n const fields: Record<string, unknown> = {\n workerId: process.env.WORKER_ID ?? '[primary]',\n method,\n ip,\n path: url.pathname,\n status: res.statusCode,\n duration: (performance.now() - start).toFixed(4),\n };\n\n if ($keys(normalized.query).length > 0) {\n fields.query = normalized.query;\n }\n\n if (bodyPreview.exists) {\n fields.body = bodyPreview.value;\n fields.bodyBytes = bodyPreview.bytes;\n fields.bodyTruncated = bodyPreview.truncated;\n }\n\n cx.logger.info('Res', fields);\n });\n\n // * Start request handling\n try {\n if (normalized.url.pathname.startsWith(META_PREFIX + '/')) {\n safeSendJson(res, { message: `Meta APIs are available on port ${cx.options.metaPort}` }, HttpCode.NotFound);\n return;\n }\n\n const parsed = await parseBody(req, normalized.method, cx.options.maxRequestBytes);\n normalized.body = parsed.body;\n bodyPreview = parsed.preview;\n\n const m = await cx.router.getModule(url);\n if (!m) {\n safeSendJson(res, { message: 'Not Found' }, HttpCode.NotFound);\n return;\n }\n\n // TODO 准备加入超时机制、methods等更多选项\n const ms = m.handlerTimeoutMs ?? cx.options.handlerTimeoutMs;\n const result = await Promise.race([\n PromiseTry(m.handler, normalized, req, res),\n new Promise((r) => setTimeout(() => r(HANDLER_TIMEOUT_FLAG), ms)),\n ]);\n\n if (result === HANDLER_TIMEOUT_FLAG) {\n cx.logger.warn('HandlerTimeout', {\n method: normalized.method,\n ip: normalized.ip,\n });\n safeSendJson(res, { message: 'Handler timed out' }, HttpCode.InternalServerError);\n return;\n }\n\n if (result !== STATIC_HANDLED_FLAG) {\n safeSendJson(res, result);\n }\n } catch (error) {\n cx.logger.error('RequestFailed', {\n method: normalized.method,\n ip: normalized.ip,\n path: normalized.url.pathname,\n error: getErrorMessage(error),\n });\n\n if ((error as NodeJS.ErrnoException).code === 'REQUEST_BODY_TOO_LARGE') {\n safeSendJson(res, { message: getErrorMessage(error) }, HttpCode.PayloadTooLarge);\n } else {\n safeSendJson(res, { message: getErrorMessage(error) }, HttpCode.InternalServerError);\n }\n }\n };\n\n const server = cx.options.https\n ? https.createServer(\n {\n key: cx.options.https.key,\n cert: cx.options.https.cert,\n ca: cx.options.https.ca,\n },\n requestHandler,\n )\n : http.createServer(requestHandler);\n\n server.on('close', () => {\n cx.logger.info('ServerClosed', {\n host: cx.options.host,\n port: cx.options.port,\n });\n });\n\n server.listen(cx.options.port, cx.options.host, () => {\n cx.logger.info('ServerStarted', {\n pid: process.pid,\n protocol: cx.options.https ? 'https' : 'http',\n host: cx.options.host,\n port: cx.options.port,\n });\n cx.logger.info('DynamicDirectory', { directory: cx.options.dir });\n });\n\n server.on('error', (error) => {\n cx.logger.error('ServerError', {\n error: getErrorMessage(error),\n });\n });\n\n return server;\n}\n","import type { PrimaryMessage } from './types.js';\nimport type { FluxionContext } from '../types.js';\nimport cluster from 'node:cluster';\n\nimport { getErrorMessage } from '@/common/logger.js';\nimport { WorkerAction, PrimaryAction, isPrimaryMessage } from './consts.js';\nimport { sendToPrimary } from './communicate.js';\nimport { createWorkerServer } from './server.js';\n\nconst startStatsReporter = () => {\n let previousCpuUsage = process.cpuUsage();\n let previousAt = Date.now();\n\n const interval = setInterval(() => {\n const now = Date.now();\n const elapsedMicros = Math.max(1, (now - previousAt) * 1000);\n const cpuDelta = process.cpuUsage(previousCpuUsage);\n const cpuPercent = Number((((cpuDelta.user + cpuDelta.system) / elapsedMicros) * 100).toFixed(2));\n\n previousCpuUsage = process.cpuUsage();\n previousAt = now;\n\n const memoryUsage = process.memoryUsage();\n sendToPrimary({\n type: WorkerAction.Stats,\n pid: process.pid,\n stats: {\n at: now,\n pid: process.pid,\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n cpu: {\n userMicros: cpuDelta.user,\n systemMicros: cpuDelta.system,\n percent: cpuPercent,\n },\n memory: {\n rss: memoryUsage.rss,\n heapTotal: memoryUsage.heapTotal,\n heapUsed: memoryUsage.heapUsed,\n external: memoryUsage.external,\n arrayBuffers: memoryUsage.arrayBuffers,\n },\n },\n });\n }, 2000);\n\n interval.unref();\n};\n\nexport function initWorker(cx: FluxionContext) {\n if (cluster.isPrimary) {\n $throw('createWorker should only be called in worker process');\n }\n\n process.on('message', (raw: PrimaryMessage) => {\n if (!isPrimaryMessage(raw)) {\n return;\n }\n\n if (raw.type === PrimaryAction.Ping) {\n sendToPrimary({ type: WorkerAction.Pong, pid: process.pid, sentAt: raw.sentAt, receivedAt: Date.now() });\n return;\n }\n });\n\n sendToPrimary({ type: WorkerAction.Created, pid: process.pid });\n startStatsReporter();\n\n try {\n createWorkerServer(cx);\n sendToPrimary({ type: WorkerAction.Ready, pid: process.pid });\n } catch (e) {\n cx.logger.error('WorkerBootstrapFailed', {\n pid: process.pid,\n error: getErrorMessage(e),\n });\n process.exit(1);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { FluxionContext } from '../types.js';\n\nexport type WatcherContext = Pick<FluxionContext, 'options' | 'logger' | 'router'>;\n\nexport abstract class FluxionWatcherBase {\n protected readonly cx: WatcherContext;\n\n private timer: NodeJS.Timeout | null = null;\n private readonly filesChanged = new Map<string, string>();\n\n constructor(cx: WatcherContext) {\n this.cx = cx;\n }\n\n /**\n * Recursively register all files in the options directory.\n */\n protected async init(): Promise<this> {\n const dir = this.cx.options.dir;\n if (!fs.existsSync(dir)) {\n this.cx.logger.warn(`Directory does not exist: ${dir}`);\n return this;\n }\n\n const registerList: Promise<void>[] = [];\n\n const registerRecursive = (absoluteDir: string, relativeDir: string) => {\n const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const absolutePath = path.join(absoluteDir, entry.name);\n const relativePath = path.join(relativeDir, entry.name);\n\n if (entry.isDirectory()) {\n registerRecursive(absolutePath, relativePath);\n } else if (entry.isFile()) {\n const p = this.cx.router.register(absolutePath, relativePath).catch((e) => {\n this.cx.logger.error(`Error registering file ${relativePath}: ${(e as Error).message}`);\n });\n registerList.push(p);\n }\n }\n };\n\n registerRecursive(dir, '');\n await Promise.all(registerList);\n\n this.cx.logger.info(`Initial registration complete for directory: ${dir}`);\n return this;\n }\n\n protected queueUp(absolutePath: string, relativePath: string): void {\n this.filesChanged.set(absolutePath, relativePath);\n if (this.timer) {\n return;\n }\n\n this.timer = setTimeout(async () => {\n const promises = [...this.filesChanged].map(([absolutePath, relativePath]) =>\n this.cx.router\n .register(absolutePath, relativePath)\n .catch((err) => this.cx.logger.error(`Error refreshing handlers: ${(err as Error).message}`))\n .finally(() => this.filesChanged.delete(absolutePath)),\n );\n await Promise.all(promises);\n this.timer = null;\n }, this.cx.options.reloadDelay);\n }\n\n protected stopCore(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n\n this.filesChanged.clear();\n }\n\n abstract start(): Promise<this>;\n abstract stop(): this;\n}\n","import path from 'node:path';\nimport type { FSWatcher } from 'chokidar';\nimport chokidar from 'chokidar';\n\nimport { FluxionWatcherBase, type WatcherContext } from './base.js';\n\nexport class FluxionChokidarWatcher extends FluxionWatcherBase {\n private watcher: FSWatcher | null = null;\n\n constructor(cx: WatcherContext) {\n super(cx);\n }\n\n /**\n * Start watching files with chokidar.\n *\n * Using chokidar provides:\n * - Cross-platform recursive watch support (including Linux/CentOS)\n * - Better event handling and stability\n * - Automatic resource management\n */\n async start(): Promise<this> {\n this.stop();\n await this.init();\n\n const dir = this.cx.options.dir;\n this.watcher = chokidar\n .watch(dir, {\n persistent: true, // Keep the process running\n ignoreInitial: true, // Don't emit 'add' events for initial scan\n usePolling: false, // Use polling as fallback (helps with some network drives)\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n }, // Atomic writes handling\n })\n .on('all', (_event, absolutePath) => {\n if (!absolutePath) {\n return;\n }\n\n // & `filename` is absolute(Maybe because of watching an absolute path `dir`)\n this.queueUp(absolutePath, path.relative(dir, absolutePath));\n })\n .on('error', (err: unknown) => {\n const error = err instanceof Error ? err : new Error(String(err));\n this.cx.logger.error(`Watcher error: ${error.message}`);\n this.cx.logger.error(`Restarting watcher...`);\n this.stop().start();\n })\n .on('ready', () => {\n this.cx.logger.info(`Watcher ready and watching directory: ${dir}`);\n });\n\n this.cx.logger.info(`Watcher started on directory: ${dir}`);\n return this;\n }\n\n stop(): this {\n if (this.watcher) {\n void this.watcher.close();\n this.watcher = null;\n }\n\n this.stopCore();\n return this;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { FluxionWatcherBase, type WatcherContext } from './base.js';\n\nexport class FluxionNativeWatcher extends FluxionWatcherBase {\n private watcher: fs.FSWatcher | null = null;\n\n constructor(cx: WatcherContext) {\n super(cx);\n }\n\n /**\n * Since all actions are mapped to `rename` and `change` (WatchEventType).\n *\n * We could only record every file and reload them all.\n */\n async start(): Promise<this> {\n this.stop();\n await this.init();\n\n const dir = this.cx.options.dir;\n this.watcher = fs\n .watch(dir, { recursive: true }, (_eventType, relativePath) => {\n if (!relativePath) {\n return;\n }\n\n // & Unlike chokidar, `filename` here is relativePath\n this.queueUp(path.join(dir, relativePath), relativePath);\n })\n .on('error', (err) => {\n this.cx.logger.error(`Watcher error: ${err.message}`);\n this.cx.logger.error(`Restarting watcher...`);\n this.stop().start();\n });\n\n this.cx.logger.info(`Watcher started on directory: ${dir}`);\n return this;\n }\n\n stop(): this {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n\n this.stopCore();\n return this;\n }\n}\n","function n(n) {}\n\nconst t = n;\n\nfunction o() {\n return n => {};\n}\n\nexport { o as createNarrower, n as narrow, t as static_cast };\n","import type { FluxionContext, FluxionModule } from '@/types.js';\nimport { static_cast } from 'type-narrow';\n\nfunction isFluxionModule(o: unknown): o is FluxionModule {\n if (typeof o !== 'object' || o === null) {\n return false;\n }\n\n static_cast<FluxionModule>(o);\n\n if (typeof o.handler !== 'function') {\n return false;\n }\n\n if (o.disposer !== undefined && typeof o.disposer !== 'function') {\n return false;\n }\n\n if (o.handlerTimeoutMs !== undefined && typeof o.handlerTimeoutMs !== 'function') {\n return false;\n }\n\n return true;\n}\n\nexport function loadFluxionModule(_cx: Pick<FluxionContext, 'options' | 'logger'>, fullpath: string): FluxionModule {\n delete require.cache[fullpath];\n let m = require(fullpath);\n if (isFluxionModule(m)) {\n } else if (isFluxionModule(m.default)) {\n m = m.default;\n } else {\n $throw(`Invalid handler module '${fullpath}', make sure it satisfies defineFluxionModule(...) helper`);\n }\n\n return m;\n}\n","import type { FluxionContext, FluxionModule } from '../types.js';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { minimatch } from 'minimatch';\nimport { STATIC_CONTENT_TYPES, STATIC_HANDLED_FLAG } from '@/common/consts.js';\nimport { loadFluxionModule } from '@/common/injector.js';\nimport { cctl } from '@/common/color.js';\nimport { PromiseTry } from '@/common/promise-try.js';\n\nexport class FluxionRouter {\n private readonly cx: Pick<FluxionContext, 'options' | 'logger'>;\n private readonly handlers: Map<string, FluxionModule> = new Map();\n\n constructor(cx: Pick<FluxionContext, 'options' | 'logger'>) {\n this.cx = cx;\n }\n\n makeStaticResource(filepath: string): FluxionModule {\n return {\n handler: async (normalized, _req, res) => {\n if (normalized.method !== 'GET' && normalized.method !== 'HEAD') {\n res.statusCode = 405;\n res.setHeader('Allow', 'GET, HEAD');\n res.end();\n return;\n }\n\n if (!fs.existsSync(filepath)) {\n res.statusCode = 404;\n res.end('Not Found');\n return;\n }\n\n const stat = fs.statSync(filepath);\n if (!stat.isFile()) {\n res.statusCode = 404;\n res.end('Not Found');\n return;\n }\n\n const extension = path.extname(filepath).toLowerCase();\n const contentType = STATIC_CONTENT_TYPES[extension] ?? 'application/octet-stream';\n\n res.statusCode = 200;\n res.setHeader('Content-Type', contentType);\n res.setHeader('Content-Length', String(stat.size));\n\n if (normalized.method === 'HEAD') {\n res.end();\n return;\n }\n\n return new Promise<symbol>((resolve, reject) => {\n const stream = fs.createReadStream(filepath);\n stream.on('error', reject);\n stream.on('end', () => resolve(STATIC_HANDLED_FLAG));\n stream.pipe(res);\n });\n },\n };\n }\n\n /**\n * File registration logic with fast-glob pattern matching:\n * 1. Check if the path exists, if not, delete the handler;\n * 2. If file doesn't match include patterns, skip registration;\n * 3. If file matches exclude patterns, skip registration;\n * 4. If file matches apiInclude patterns, register as API handler;\n * 5. Otherwise, register as static resource.\n */\n // TODO 检查使用的地方是否await了\n async register(absolutePath: string, relativePath: string) {\n if (!fs.existsSync(absolutePath)) {\n // Get the disposer and delete\n const disposer = this.handlers.get(relativePath)?.disposer;\n if (disposer) {\n await PromiseTry(disposer);\n }\n\n this.handlers.delete(relativePath);\n // & Watcher will emit recursively, so there is no need to use this.remove(rp);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 2: Check if file matches include patterns (default: all files)\n // If not matching, skip registration\n const matchesInclude = this.cx.options.include.some((pattern) => minimatch(relativePath, pattern));\n if (!matchesInclude) {\n this.handlers.delete(relativePath);\n this.cx.logger.info(`${cctl.yellow}Skipped ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 3: Check if file matches exclude patterns\n // If matching, skip registration\n const matchesExclude = this.cx.options.exclude.some((pattern) => minimatch(relativePath, pattern));\n if (matchesExclude) {\n this.handlers.delete(relativePath);\n this.cx.logger.info(`${cctl.orange}Excluded${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 4 & 5: Check if file matches apiInclude patterns\n // If matching, register as API handler; otherwise as static resource\n const matchesApiInclude = this.cx.options.apiInclude.some((pattern) => minimatch(relativePath, pattern));\n if (matchesApiInclude) {\n const handler = loadFluxionModule(this.cx, absolutePath);\n this.handlers.set(relativePath, handler);\n this.cx.logger.info(`${cctl.green}Api ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // register as static resource\n this.handlers.set(relativePath, this.makeStaticResource(relativePath));\n this.cx.logger.info(`${cctl.brightBlue}Static ${cctl.reset} - ${relativePath}`);\n }\n\n getModule(url: URL): FluxionModule | undefined {\n const relativePath = url.pathname.replace(/^[\\/]+/, '').replace(/[\\/]+$/, '');\n return this.handlers.get(relativePath);\n }\n\n /**\n * If the path points to a file, it would be simple.\n * But if it's a directory, we need to find all registered handlers under this directory and remove them.\n *\n * @param somepath\n * @deprecated\n */\n remove(somepath: string): void {\n if (this.handlers.has(somepath)) {\n this.handlers.delete(somepath);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${somepath}`);\n }\n // & Not in handler map -> It is a directory\n const prefix = somepath.endsWith('/') ? somepath : somepath + '/';\n for (const key of this.handlers.keys()) {\n if (key.startsWith(prefix)) {\n this.handlers.delete(key);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${key}`);\n }\n }\n }\n}\n","import type { FluxionContext, FluxionOptions } from './types.js';\n\nimport { createLogger, createWorkerLogger } from '@/common/logger.js';\nimport { normalizeOptions } from './http/options.js';\nimport cluster from 'node:cluster';\nimport { initPrimary } from './cluster/primary.js';\nimport { initWorker } from './cluster/worker.js';\nimport { FluxionChokidarWatcher } from './watcher/chokidar.js';\nimport { FluxionNativeWatcher } from './watcher/native.js';\nimport { FluxionRouter } from './router/index.js';\n\nexport async function fluxion(options: FluxionOptions) {\n const context = { options: normalizeOptions(options) } as FluxionContext;\n\n context.logger = createLogger(context as Pick<FluxionContext, 'options'>);\n context.router = new FluxionRouter(context as Pick<FluxionContext, 'options' | 'logger'>);\n\n if (cluster.isPrimary) {\n initPrimary(context);\n } else {\n // Replace logger with worker logger that prefixes PID\n context.logger = createWorkerLogger(context.logger, process.pid);\n // Only worker creates the watcher\n const Watcher = context.options.nativeWatcher ? FluxionNativeWatcher : FluxionChokidarWatcher;\n context.watcher = await new Watcher(context as Pick<FluxionContext, 'options' | 'logger' | 'router'>).start();\n initWorker(context);\n }\n}\n","import type { FluxionDispose, FluxionHandler, FluxionModule } from './types.js';\nimport { fluxion } from './fluxion.js';\nimport { noop } from './common/consts.js';\n\nexport { fluxion };\nexport type { FluxionDispose, FluxionHandler, FluxionModule as FluxionHandlerModule, FluxionOptions } from './types.js';\n\n/**\n * Use handler function and optional disposer function to define a Fluxion module.\n * @param handler Main function that handles request and response instances\n * @param disposer Deal with resource cleanup when the server is about to close\n */\nexport function defineFluxionModule(handler: FluxionHandler, disposer?: FluxionDispose): FluxionModule;\n\n/**\n * Provides type safety for defining Fluxion modules.\n */\nexport function defineFluxionModule(fluxionModule: FluxionModule): FluxionModule;\nexport function defineFluxionModule(a: FluxionModule | FluxionHandler, disposer: FluxionDispose = noop): FluxionModule {\n if (typeof a === 'function') {\n if (typeof disposer !== 'function') {\n $throw(`Invalid disposer, expected a function but got ${typeof disposer}`);\n }\n return { handler: a, disposer };\n }\n\n if (typeof a !== 'object' || a === null) {\n $throw(`Invalid argument, expected a FluxionModule object or a handler function, but got ${typeof a}`);\n }\n\n if (typeof a.handler !== 'function') {\n $throw(`Invalid FluxionModule, \"handler\" must be a function`);\n }\n\n if (a.disposer !== undefined && typeof a.disposer !== 'function') {\n $throw(`Invalid FluxionModule, \"disposer\" must be a function if provided`);\n }\n\n return a;\n}\n\nif (process.env.NODE_ENV !== 'production') {\n globalThis.$throw = (message: string) => {\n throw new Error('[fluxion error]' + message);\n };\n\n const int = (n: string | undefined, defaultValue: number): number => {\n if (n === undefined) {\n return defaultValue;\n }\n const parsed = Number.parseInt(n, 10);\n if (Number.isNaN(parsed)) {\n return defaultValue;\n }\n return parsed;\n };\n\n fluxion({\n dir: process.env.DYNAMIC_DIRECTORY ?? 'dynamicDirectory',\n host: process.env.HOST ?? 'localhost',\n port: int(process.env.PORT, 9000),\n metaPort: int(process.env.META_PORT, 9001),\n reloadDelay: process.env.RELOAD_DELAY ? Number.parseInt(process.env.RELOAD_DELAY, 10) : undefined,\n workerOptions: {\n maxWorkerCount: 1,\n },\n });\n}\n"],"x_google_ignoreList":[22],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["path","fs","http","cluster","os","https","http","cluster","fs","path","path","fs","path","fs","path","cluster"],"sources":["../src/common/dtm.ts","../src/common/native.ts","../src/common/color.ts","../src/common/logger.ts","../src/http/options.ts","../src/cluster/consts.ts","../src/cluster/communicate.ts","../src/common/consts.ts","../src/http/respond.ts","../src/cluster/meta-api.ts","../src/cluster/primary.ts","../src/common/promise-try.ts","../src/http/headers.ts","../src/http/request.ts","../src/http/query.ts","../src/http/body.ts","../src/http/cookie.ts","../src/cluster/server.ts","../src/cluster/worker.ts","../src/watcher/base.ts","../src/watcher/chokidar.ts","../src/watcher/native.ts","../node_modules/.pnpm/type-narrow@0.2.2/node_modules/type-narrow/dist/index.mjs","../src/common/injector.ts","../src/router/index.ts","../src/fluxion.ts","../src/index.ts"],"sourcesContent":["export function dtm(dt = new Date()) {\n const y = dt.getFullYear();\n const m = String(dt.getMonth() + 1).padStart(2, '0');\n const d = String(dt.getDate()).padStart(2, '0');\n const hh = String(dt.getHours()).padStart(2, '0');\n const mm = String(dt.getMinutes()).padStart(2, '0');\n const ss = String(dt.getSeconds()).padStart(2, '0');\n const ms = String(dt.getMilliseconds()).padStart(3, '0');\n return `${y}.${m}.${d} ${hh}:${mm}:${ss}.${ms}`;\n}\n","export const $stringify = JSON.stringify;\n\nexport const $keys = Object.keys;\n","const useColor = process.env.FLUXION_COLORS !== '0';\n\n/**\n * Color Control Characters for Terminal (cctl)\n */\nexport namespace cctl {\n export const reset = useColor ? '\\x1b[0m' : '';\n export const bold = useColor ? '\\x1b[1m' : '';\n export const dim = useColor ? '\\x1b[2m' : '';\n export const italic = useColor ? '\\x1b[3m' : '';\n export const underline = useColor ? '\\x1b[4m' : '';\n export const blink = useColor ? '\\x1b[5m' : '';\n export const inverse = useColor ? '\\x1b[7m' : '';\n\n export const black = useColor ? '\\x1b[30m' : '';\n export const red = useColor ? '\\x1b[31m' : '';\n export const green = useColor ? '\\x1b[32m' : '';\n export const yellow = useColor ? '\\x1b[33m' : '';\n export const blue = useColor ? '\\x1b[34m' : '';\n export const magenta = useColor ? '\\x1b[35m' : '';\n export const cyan = useColor ? '\\x1b[36m' : '';\n export const white = useColor ? '\\x1b[37m' : '';\n\n export const brightBlack = useColor ? '\\x1b[90m' : '';\n export const brightRed = useColor ? '\\x1b[91m' : '';\n export const brightGreen = useColor ? '\\x1b[92m' : '';\n export const brightYellow = useColor ? '\\x1b[93m' : '';\n export const brightBlue = useColor ? '\\x1b[94m' : '';\n export const brightMagenta = useColor ? '\\x1b[95m' : '';\n export const brightCyan = useColor ? '\\x1b[96m' : '';\n export const brightWhite = useColor ? '\\x1b[97m' : '';\n\n export const bgBlack = useColor ? '\\x1b[40m' : '';\n export const bgRed = useColor ? '\\x1b[41m' : '';\n export const bgGreen = useColor ? '\\x1b[42m' : '';\n export const bgYellow = useColor ? '\\x1b[43m' : '';\n export const bgBlue = useColor ? '\\x1b[44m' : '';\n export const bgMagenta = useColor ? '\\x1b[45m' : '';\n export const bgCyan = useColor ? '\\x1b[46m' : '';\n export const bgWhite = useColor ? '\\x1b[47m' : '';\n\n export const bgBrightBlack = useColor ? '\\x1b[100m' : '';\n export const bgBrightRed = useColor ? '\\x1b[101m' : '';\n export const bgBrightGreen = useColor ? '\\x1b[102m' : '';\n export const bgBrightYellow = useColor ? '\\x1b[103m' : '';\n export const bgBrightBlue = useColor ? '\\x1b[104m' : '';\n export const bgBrightMagenta = useColor ? '\\x1b[105m' : '';\n export const bgBrightCyan = useColor ? '\\x1b[106m' : '';\n export const bgBrightWhite = useColor ? '\\x1b[107m' : '';\n\n // 'rgb(225, 16, 248)';\n export const purple = useColor ? '\\x1b[38;2;225;16;248m' : '';\n // 'rgb(248, 147, 16)';\n export const orange = useColor ? '\\x1b[38;2;248;147;16m' : '';\n export const darkGreen = useColor ? '\\x1b[38;2;22;101;52m' : '';\n export const claude = useColor ? '\\x1b[38;2;217;119;87m' : '';\n export const deepseek = useColor ? '\\x1b[38;2;57;100;254m' : '';\n export const gpt = useColor ? '\\x1b[38;2;41;60;77m' : '';\n}\n","import type { FluxionContext } from '@/types.js';\nimport type { otherstring } from '@/global.js';\n\nimport { dtm } from './dtm.js';\nimport { $keys, $stringify } from './native.js';\nimport { cctl } from './color.js';\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'SUCC' | 'DEBUG' | 'VERBOSE' | otherstring;\n\ninterface LogEntry {\n timestamp: string;\n level: LogLevel;\n event: string;\n message?: string;\n [key: string]: unknown;\n}\n\nexport type LoggerOption = 'one-line' | 'json-line' | FluxionLoggerFn;\n\nexport type FluxionLoggerFn = (entry: LogEntry) => void;\n\nexport interface FluxionLogger {\n /**\n * [WARN] We assert that `fields` is an object or undefined.\n */\n write(level: LogLevel, event: string, fields?: object): void;\n info(event: string, fields?: object): void;\n warn(event: string, fields?: object): void;\n error(event: string, fields?: object): void;\n succ(event: string, fields?: object): void;\n debug(event: string, fields?: object): void;\n verbose(event: string, fields?: object): void;\n}\n\nconst safeStringify = (value: unknown): string => {\n try {\n return $stringify(value);\n } catch {\n return '[unserializable]';\n }\n};\n\nconst ColoredLevels: Record<LogLevel, string> = {\n INFO: `${cctl.cyan}INFO${cctl.reset}`,\n WARN: `${cctl.orange}WARN${cctl.reset}`,\n ERROR: `${cctl.red}ERROR${cctl.reset}`,\n SUCC: `${cctl.green}SUCC${cctl.reset}`,\n DEBUG: `${cctl.blue}DEBUG${cctl.reset}`,\n VERBOSE: `${cctl.purple}VERBOSE${cctl.reset}`,\n};\n\nexport const oneLineLogger: FluxionLoggerFn = (entry: LogEntry) => {\n const { level: rawLevel, timestamp: rawTimestamp, event: rawEvent, message: rawMessage, ...fields } = entry;\n\n const timestamp = `${cctl.darkGreen}[${rawTimestamp}]${cctl.reset}`;\n const level = ColoredLevels[rawLevel] ?? rawLevel;\n const body = rawMessage ?? rawEvent;\n const fieldsText = $keys(fields).length > 0 ? `${cctl.dim}${safeStringify(fields)}${cctl.reset}` : '';\n\n console.log(`${timestamp} ${level} ${body}${fieldsText}`);\n};\n\n/**\n * & Logger Options here is checked by normalizeOptions function.\n */\nfunction resolveLoggerSink(cx: Pick<FluxionContext, 'options'>): FluxionLoggerFn {\n const loggerOption = cx.options.logger;\n if (loggerOption === undefined || loggerOption === 'one-line') {\n return oneLineLogger;\n }\n\n if (loggerOption === 'json-line') {\n return (entry: LogEntry) => console.log(safeStringify(entry));\n }\n\n return loggerOption;\n}\n\nexport function createLogger(cx: Pick<FluxionContext, 'options'>): FluxionLogger {\n const sink = resolveLoggerSink(cx);\n\n const logger: FluxionLogger = {\n write(level: LogLevel, event: string, fields: Record<string, unknown> = {}): void {\n const entry: LogEntry = {\n ...fields,\n timestamp: dtm(),\n level,\n event,\n };\n\n try {\n sink(entry);\n } catch {\n // Ignore logger sink failures to avoid breaking request handling.\n }\n },\n info(event: string, fields?: Record<string, unknown>): void {\n this.write('INFO', event, fields);\n },\n warn(event: string, fields?: Record<string, unknown>): void {\n this.write('WARN', event, fields);\n },\n error(event: string, fields?: Record<string, unknown>): void {\n this.write('ERROR', event, fields);\n },\n succ(event: string, fields?: Record<string, unknown>): void {\n this.write('SUCC', event, fields);\n },\n debug(event: string, fields?: Record<string, unknown>): void {\n this.write('DEBUG', event, fields);\n },\n verbose(event: string, fields?: Record<string, unknown>): void {\n this.write('VERBOSE', event, fields);\n },\n };\n\n return logger;\n}\n\n/**\n * Create a worker logger that prefixes all log messages with the worker PID.\n */\nexport function createWorkerLogger(baseLogger: FluxionLogger, pid: number): FluxionLogger {\n const pidPrefix = `[${pid}]`;\n\n return {\n write(level: LogLevel, event: string, fields?: object): void {\n baseLogger.write(level, `${pidPrefix} ${event}`, fields);\n },\n info(event: string, fields?: object): void {\n baseLogger.info(`${pidPrefix} ${event}`, fields);\n },\n warn(event: string, fields?: object): void {\n baseLogger.warn(`${pidPrefix} ${event}`, fields);\n },\n error(event: string, fields?: object): void {\n baseLogger.error(`${pidPrefix} ${event}`, fields);\n },\n succ(event: string, fields?: object): void {\n baseLogger.succ(`${pidPrefix} ${event}`, fields);\n },\n debug(event: string, fields?: object): void {\n baseLogger.debug(`${pidPrefix} ${event}`, fields);\n },\n verbose(event: string, fields?: object): void {\n baseLogger.verbose(`${pidPrefix} ${event}`, fields);\n },\n };\n}\n\n/**\n * ! Error.isError needs Node.js 24\n */\nexport const getErrorMessage =\n typeof Error.isError === 'function'\n ? (e: unknown): string => (Error.isError(e) ? e.message : String(e))\n : (e: unknown): string => (e as any)?.message || String(e);\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { WorkerOptions, NormalizedWorkerOptions, FluxionOptions, NormalizedFluxionOptions } from '../types.js';\n\n/**\n * Resolves worker options with framework defaults. All thresholds become\n * concrete numbers (`Infinity` disables a check) so the primary can evaluate\n * them without null-handling.\n */\nfunction resolveWorkerOptions(options: WorkerOptions = {}): NormalizedWorkerOptions {\n const rw = options.restartWhen ?? {};\n const healthzTimeout = rw.healthzTimeout ?? 30_000;\n // Ping runs every 5s; a threshold below 2x that would recycle healthy workers\n // (a ready worker's lastPongAt is normally ~5s old). Infinity disables.\n if (healthzTimeout !== Infinity && (!Number.isFinite(healthzTimeout) || healthzTimeout < 10_000)) {\n $throw('workerOptions.restartWhen.healthzTimeout must be a finite number >= 10000 (ms) or Infinity');\n }\n return {\n maxWorkerCount: options.maxWorkerCount ?? 4,\n restartWhen: {\n memoryUsageGreaterThan: rw.memoryUsageGreaterThan ?? Infinity,\n healthzTimeout,\n uptimeGreaterThan: rw.uptimeGreaterThan ?? Infinity,\n },\n };\n}\n\n/**\n * Read certificate content from a file path or return the content directly.\n */\nfunction readCertificateContent(content: string | Buffer, moduleDir: string): Buffer {\n if (Buffer.isBuffer(content)) {\n return content;\n }\n if (typeof content === 'string') {\n // Check if it looks like a file path (not a PEM certificate)\n // PEM certificates start with \"-----BEGIN\"\n if (!content.startsWith('-----BEGIN')) {\n const filePath = path.isAbsolute(content) ? content : path.join(moduleDir, content);\n if (fs.existsSync(filePath)) {\n return fs.readFileSync(filePath);\n }\n }\n return Buffer.from(content);\n }\n $throw('Certificate content must be a string or Buffer');\n}\n\n/**\n * Normalize HTTPS options.\n */\nfunction normalizeHttpsOptions(\n https: FluxionOptions['https'],\n moduleDir: string,\n): NormalizedFluxionOptions['https'] | undefined {\n if (!https) {\n return undefined;\n }\n\n if (typeof https !== 'object' || https === null || Array.isArray(https)) {\n $throw('FluxionOptions.https must be an object');\n }\n if (typeof https.key !== 'string') {\n $throw('FluxionOptions.https.key must be a string');\n }\n if (typeof https.cert !== 'string') {\n $throw('FluxionOptions.https.cert must be a string');\n }\n\n const result: NormalizedFluxionOptions['https'] = {\n key: readCertificateContent(https.key, moduleDir),\n cert: readCertificateContent(https.cert, moduleDir),\n };\n\n if (https.ca !== undefined) {\n if (Array.isArray(https.ca)) {\n result.ca = https.ca.map((item) => readCertificateContent(item, moduleDir));\n } else {\n result.ca = readCertificateContent(https.ca, moduleDir);\n }\n }\n\n return result;\n}\n\n/**\n * Normalize options and create necessary resources like the dynamic directory and logger.\n */\nexport function normalizeOptions(options: FluxionOptions): NormalizedFluxionOptions {\n if (typeof options !== 'object' || options === null || Array.isArray(options)) {\n $throw('FluxionOptions must be an object');\n }\n\n let {\n dir,\n host,\n port,\n handlerTimeoutMs = 5000,\n staticResourceTimeoutMs = 10 * 600000,\n metaPort,\n moduleDir = process.cwd(),\n workerOptions = {},\n maxRequestBytes = 8_000_000,\n reloadDelay = 500,\n include = ['**/*'],\n apiInclude = ['**/*.ts'],\n exclude = [\n '**/node_modules/**',\n '**/.git/**',\n '**/dist/**',\n '**/build/**',\n '**/.vscode/**',\n '**/.idea/**',\n '**/*.log',\n '**/.DS_Store',\n '**/coverage/**',\n '**/.nyc_output/**',\n '**/*.tmp',\n '**/*.temp',\n ],\n https,\n nativeWatcher = false,\n } = options as FluxionOptions;\n\n const logger = options.logger ?? 'one-line';\n if (logger !== 'one-line' && logger !== 'json-line' && typeof logger !== 'function') {\n $throw(`Invalid logger option, Must be 'one-line', 'json-line' or a custom logger function`);\n }\n\n if (typeof dir !== 'string') {\n $throw('FluxionOptions.dir must be a string');\n }\n\n if (typeof moduleDir !== 'string') {\n $throw('FluxionOptions.moduleDir must be a string');\n }\n\n if (typeof host !== 'string') {\n $throw('FluxionOptions.host must be a string');\n }\n\n if (!Number.isSafeInteger(handlerTimeoutMs) || handlerTimeoutMs <= 100) {\n $throw('FluxionOptions.handlerTimeoutMs must be an integer greater than 100');\n }\n\n if (typeof reloadDelay !== 'number' || reloadDelay <= 0 || !Number.isSafeInteger(reloadDelay)) {\n $throw('FluxionOptions.reloadDelay must be a positive integer');\n }\n\n if (reloadDelay < 50) {\n $throw('FluxionOptions.reloadDelay must be greater than or equal to 50');\n }\n\n if (typeof port !== 'number' || !Number.isSafeInteger(port)) {\n $throw('FluxionOptions.port must be a positive integer');\n }\n\n if (port <= 1 || port > 65535) {\n $throw('FluxionOptions.port must be 1 ~ 65535');\n }\n\n metaPort ??= port + 1;\n if (typeof metaPort !== 'number' || !Number.isSafeInteger(metaPort)) {\n $throw('FluxionOptions.metaPort must be a positive integer');\n }\n\n if (metaPort <= 1 || metaPort > 65535) {\n $throw('FluxionOptions.metaPort must be 1 ~ 65535');\n }\n\n if (metaPort === port) {\n $throw('FluxionOptions.metaPort must be different from FluxionOptions.port');\n }\n\n if (typeof workerOptions !== 'object' || workerOptions === null || Array.isArray(workerOptions)) {\n $throw('FluxionOptions.workerOptions must be an object');\n }\n\n if (typeof maxRequestBytes !== 'number' || maxRequestBytes <= 0 || !Number.isSafeInteger(maxRequestBytes)) {\n $throw('FluxionOptions.maxRequestBytes must be a positive integer');\n }\n\n dir = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n return {\n dir,\n host,\n port,\n handlerTimeoutMs,\n staticResourceTimeoutMs,\n reloadDelay,\n metaPort,\n moduleDir,\n workerOptions: resolveWorkerOptions(workerOptions),\n maxRequestBytes,\n logger,\n include,\n apiInclude,\n exclude,\n nativeWatcher,\n https: normalizeHttpsOptions(https, moduleDir),\n };\n}\n","import type { PrimaryMessage as PrimaryMessage, WorkerMessage as WorkerMessage } from './types.js';\n\nexport const enum PrimaryAction {\n /**\n * Health check message, the worker should respond with Pong and the latency information\n */\n Ping = 100,\n}\n\nexport const enum WorkerAction {\n /**\n * Just created\n */\n Created = 200,\n\n /**\n * Ready for tasks\n * - fluxion options are injected\n */\n Ready,\n\n /**\n * Response to Ping, used for health check and latency measurement\n */\n Pong,\n\n /**\n * Runtime telemetry snapshot from worker process.\n */\n Stats,\n}\n\nexport const isPrimaryMessage = (v: PrimaryMessage): v is PrimaryMessage => [PrimaryAction.Ping].includes(v?.type);\n\nexport const isWorkerMessage = (v: WorkerMessage): v is WorkerMessage =>\n [WorkerAction.Pong, WorkerAction.Created, WorkerAction.Ready, WorkerAction.Stats].includes(v?.type);\n","import type cluster from 'node:cluster';\nimport type { PrimaryMessage, WorkerMessage } from './types.js';\n\nexport const sendToPrimary = (message: WorkerMessage) => process.send?.(message);\n\nexport const sendToWorker = (worker: cluster.Worker, message: PrimaryMessage) => worker.send(message);\n","export const DUMMY_BASE_URL = 'http://fluxion.local';\nexport const META_PREFIX = '/_fluxion';\n\nexport const STATIC_HANDLED_FLAG = Symbol.for('fluxion.router.StaticHandled');\nexport const HANDLER_TIMEOUT_FLAG = Symbol.for('fluxion.handlerTimeout');\n\nexport const STATIC_CONTENT_TYPES: Record<string, string> = {\n '.css': 'text/css; charset=utf-8',\n '.html': 'text/html; charset=utf-8',\n '.ico': 'image/x-icon',\n '.js': 'text/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain; charset=utf-8',\n '.webp': 'image/webp',\n};\n\nexport const enum HttpCode {\n Ok = 200,\n MethodNotAllowed = 405,\n BadRequest = 400,\n PayloadTooLarge = 413,\n NotFound = 404,\n InternalServerError = 500,\n}\n\nexport const enum HandlerResult {\n NotFound,\n Handled,\n}\n\nexport const enum FluxionModuleType {\n Api,\n StaticResource,\n}\n\nexport const noop = () => {};\n","import type { ServerResponse } from 'node:http';\nimport { HttpCode } from '@/common/consts.js';\n\nexport function sendJson(res: ServerResponse, payload: unknown, statusCode: HttpCode = HttpCode.Ok): void {\n res.statusCode = statusCode;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n res.end(JSON.stringify(payload));\n}\n\nexport function safeSendJson(res: ServerResponse, payload: unknown, statusCode: HttpCode = HttpCode.Ok): void {\n if (res.writableEnded) {\n return;\n }\n\n if (res.headersSent) {\n res.end();\n return;\n }\n\n sendJson(res, payload, statusCode);\n}\n","import http from 'node:http';\n\nimport { getErrorMessage } from '@/common/logger.js';\nimport { HttpCode, META_PREFIX } from '@/common/consts.js';\nimport { sendJson } from '../http/respond.js';\nimport { FluxionContext } from '../types.js';\n\nexport function createPrimaryMetaApiServer(\n cx: Pick<FluxionContext, 'logger' | 'options' | 'router'>,\n getWorkersSnapshot: () => unknown,\n): http.Server {\n const server = http.createServer((req, res) => {\n const method = req.method ?? 'GET';\n\n let pathname = '/';\n try {\n pathname = new URL(req.url ?? '/', 'http://fluxion.local').pathname;\n } catch {\n sendJson(res, { message: 'Bad Request: invalid url' }, HttpCode.BadRequest);\n return;\n }\n\n if (method === 'GET' && pathname === META_PREFIX + '/healthz') {\n sendJson(res, {\n ok: true,\n role: 'primary',\n pid: process.pid,\n now: Date.now(),\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n });\n return;\n }\n\n if (method === 'GET' && pathname === META_PREFIX + '/workers') {\n sendJson(res, {\n ok: true,\n now: Date.now(),\n workers: getWorkersSnapshot(),\n });\n return;\n }\n\n sendJson(res, { message: 'Not Found' }, HttpCode.NotFound);\n });\n\n server.on('listening', () => {\n cx.logger.info('MetaApiStarted', {\n pid: process.pid,\n host: cx.options.host,\n port: cx.options.metaPort,\n prefix: META_PREFIX,\n });\n });\n\n server.on('error', (error) => {\n cx.logger.error('MetaApiError', {\n host: cx.options.host,\n port: cx.options.metaPort,\n error: getErrorMessage(error),\n });\n process.exit(1);\n });\n\n server.listen(cx.options.metaPort, cx.options.host);\n return server;\n}\n","import type { WorkerMessage, WorkerState, WorkerRuntimeStats } from './types.js';\nimport type { FluxionContext } from '../types.js';\nimport os from 'node:os';\nimport cluster from 'node:cluster';\n\nimport { isWorkerMessage, WorkerAction, PrimaryAction } from './consts.js';\nimport { sendToWorker } from './communicate.js';\nimport { createPrimaryMetaApiServer } from './meta-api.js';\n\nconst bytesToMb = (bytes: number) => Number((bytes / 1024 / 1024).toFixed(2));\n\n/**\n * Anti-storm guard shared by proactive recycle (restartWhen) and reactive\n * respawn (crash). A slot may be restarted at most MAX_RESTARTS_PER_WINDOW\n * times within RESTART_WINDOW_MS; further attempts are suppressed and alerted\n * rather than fork-bombing. The window is rolling, so a quiet minute restores\n * capacity — it throttles, never kills a slot permanently.\n */\nconst RESTART_WINDOW_MS = 60_000;\nconst MAX_RESTARTS_PER_WINDOW = 3;\n\nexport function initPrimary(cx: Pick<FluxionContext, 'logger' | 'options' | 'router'>) {\n if (!cluster.isPrimary) {\n $throw('createPrimary should only be called in primary process');\n }\n\n const { workerOptions } = cx.options;\n const restartWhen = workerOptions.restartWhen;\n const cpuCount = Math.max(1, os.cpus().length);\n const workerCount = Math.max(1, Math.min(workerOptions.maxWorkerCount ?? Math.min(2, cpuCount), cpuCount));\n\n cx.logger.info('PrimaryStarted', {\n pid: process.pid,\n workers: workerCount,\n host: cx.options.host,\n port: cx.options.port,\n metaPort: cx.options.metaPort,\n });\n\n const workers = new Map<number, WorkerState>();\n\n // slot -> recent restart timestamps (pruned to RESTART_WINDOW_MS on access).\n // Keyed by the stable 1-based slot, not cluster's worker.id (which changes\n // on every fork), so history survives respawn cycles.\n const restartLog = new Map<number, number[]>();\n\n const restartCountInWindow = (slot: number) => {\n const now = Date.now();\n const log = (restartLog.get(slot) ?? []).filter((t) => now - t < RESTART_WINDOW_MS);\n restartLog.set(slot, log);\n return log.length;\n };\n\n const recordRestart = (slot: number) => {\n const now = Date.now();\n const log = (restartLog.get(slot) ?? []).filter((t) => now - t < RESTART_WINDOW_MS);\n log.push(now);\n restartLog.set(slot, log);\n };\n\n const isStorming = (slot: number) => restartCountInWindow(slot) >= MAX_RESTARTS_PER_WINDOW;\n\n const getWorkersSnapshot = () => {\n return {\n primaryPid: process.pid,\n host: cx.options.host,\n port: cx.options.port,\n metaPort: cx.options.metaPort,\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n workers: Array.from(workers.entries()).map(([workerId, info]) => {\n const { instance } = info;\n const stats = info.lastStats;\n return {\n workerId,\n slot: info.slot,\n pid: info.pid ?? instance.process.pid ?? null,\n state: info.state,\n restartReason: info.restartReason ?? null,\n createdAt: info.createdAt,\n readyAt: info.readyAt ?? null,\n connected: instance.isConnected(),\n dead: instance.isDead(),\n exitedAfterDisconnect: instance.exitedAfterDisconnect,\n lastPongAt: info.lastPongAt ?? null,\n lastRttMs: info.lastRttMs ?? null,\n stats:\n stats === undefined\n ? null\n : {\n at: stats.at,\n uptimeSeconds: stats.uptimeSeconds,\n cpu: stats.cpu,\n memory: {\n ...stats.memory,\n rssMb: bytesToMb(stats.memory.rss),\n heapTotalMb: bytesToMb(stats.memory.heapTotal),\n heapUsedMb: bytesToMb(stats.memory.heapUsed),\n externalMb: bytesToMb(stats.memory.external),\n arrayBuffersMb: bytesToMb(stats.memory.arrayBuffers),\n },\n },\n };\n }),\n };\n };\n\n createPrimaryMetaApiServer(cx, getWorkersSnapshot);\n\n // Recycle a worker via hard kill (SIGTERM). Guards enforce one-at-a-time\n // (so a workload-wide condition rolls restarts instead of nuking the pool)\n // and the anti-storm window. Note: relies on the worker's default SIGTERM\n // disposition to exit; fluxion workers never trap SIGTERM.\n const initiateRecycle = (info: WorkerState, reason: string) => {\n for (const w of workers.values()) {\n if (w.state === 'restarting') return; // another recycle in flight; retried next tick\n }\n if (isStorming(info.slot)) {\n cx.logger.warn('WorkerRecycleSuppressed', {\n slot: info.slot,\n pid: info.pid,\n reason,\n windowMs: RESTART_WINDOW_MS,\n max: MAX_RESTARTS_PER_WINDOW,\n });\n return;\n }\n recordRestart(info.slot);\n info.state = 'restarting';\n info.restartReason = reason;\n cx.logger.warn('WorkerRecycling', { slot: info.slot, pid: info.pid, reason });\n info.instance.kill();\n };\n\n // Evaluate memory + uptime against a fresh stats report. Runs on every\n // Stats message (~every 2s), so reaction latency is bounded by the stats\n // interval, not the ping interval. Infinity thresholds short-circuit here.\n const evaluateResourceConditions = (info: WorkerState, stats: WorkerRuntimeStats) => {\n const rssMb = bytesToMb(stats.memory.rss);\n if (rssMb > restartWhen.memoryUsageGreaterThan) {\n initiateRecycle(info, `memoryUsageGreaterThan: rss ${rssMb}MB > ${restartWhen.memoryUsageGreaterThan}MB`);\n return;\n }\n const uptimeMs = stats.uptimeSeconds * 1000;\n if (uptimeMs > restartWhen.uptimeGreaterThan) {\n initiateRecycle(\n info,\n `uptimeGreaterThan: ${Math.round(uptimeMs / 1000)}s > ${Math.round(restartWhen.uptimeGreaterThan / 1000)}s`,\n );\n }\n };\n\n // Evaluate liveness against the last pong. Runs on the ping tick (5s); a\n // wedged worker stops replying, lastPongAt goes stale past the threshold.\n const evaluateLiveness = (now: number) => {\n for (const info of workers.values()) {\n if (info.state !== 'ready' || info.lastPongAt === undefined) continue;\n const staleMs = now - info.lastPongAt;\n if (staleMs > restartWhen.healthzTimeout) {\n initiateRecycle(\n info,\n `healthzTimeout: no pong for ${Math.round(staleMs / 1000)}s > ${Math.round(restartWhen.healthzTimeout / 1000)}s`,\n );\n }\n }\n };\n\n const spawnSlot = (slot: number) => {\n attachWorker(cluster.fork({ WORKER_ID: String(slot) }), slot);\n };\n\n const attachWorker = (worker: cluster.Worker, slot: number): void => {\n const workerInfo: WorkerState = {\n state: 'creating',\n pid: worker.process.pid,\n slot,\n createdAt: Date.now(),\n instance: worker,\n };\n workers.set(worker.id, workerInfo);\n\n worker.on('message', (raw: WorkerMessage) => {\n if (!isWorkerMessage(raw)) {\n return;\n }\n\n if (raw.type === WorkerAction.Pong) {\n const rtt = Date.now() - raw.sentAt;\n workerInfo.pid = raw.pid;\n workerInfo.lastPongAt = Date.now();\n workerInfo.lastRttMs = rtt;\n return;\n }\n\n if (raw.type === WorkerAction.Ready) {\n workerInfo.state = 'ready';\n workerInfo.pid = raw.pid;\n workerInfo.readyAt = Date.now();\n cx.logger.info('WorkerReady', { workerId: worker.id, slot, pid: raw.pid });\n return;\n }\n\n if (raw.type === WorkerAction.Created) {\n workerInfo.state = 'created';\n workerInfo.pid = raw.pid;\n cx.logger.info('WorkerCreated', { workerId: worker.id, slot, pid: raw.pid });\n return;\n }\n\n if (raw.type === WorkerAction.Stats) {\n workerInfo.pid = raw.pid;\n workerInfo.lastStats = raw.stats;\n if (workerInfo.state === 'ready') {\n evaluateResourceConditions(workerInfo, raw.stats);\n }\n }\n });\n\n worker.on('exit', (code, signal) => {\n const info = workers.get(worker.id);\n workers.delete(worker.id);\n const exitedSlot = info?.slot;\n const expected = info?.state === 'restarting';\n const reason = info?.restartReason ?? null;\n\n cx.logger.warn('WorkerExited', {\n workerId: worker.id,\n slot: exitedSlot ?? null,\n pid: worker.process.pid ?? 'unknown',\n code,\n signal: signal ?? 'none',\n expected,\n reason,\n });\n\n if (exitedSlot === undefined) return;\n\n if (expected) {\n // Proactive recycle: the restart was already counted when initiated.\n spawnSlot(exitedSlot);\n return;\n }\n\n // Unexpected crash: count it, then respawn unless anti-storm trips.\n recordRestart(exitedSlot);\n if (isStorming(exitedSlot)) {\n cx.logger.error('WorkerRespawnSuppressed', {\n slot: exitedSlot,\n windowMs: RESTART_WINDOW_MS,\n max: MAX_RESTARTS_PER_WINDOW,\n });\n return;\n }\n spawnSlot(exitedSlot);\n });\n };\n\n for (let i = 0; i < workerCount; i++) {\n spawnSlot(i + 1);\n }\n\n const pingTimer = setInterval(() => {\n const sentAt = Date.now();\n for (const info of workers.values()) {\n if (!info.instance.isConnected()) {\n continue;\n }\n try {\n sendToWorker(info.instance, { type: PrimaryAction.Ping, sentAt });\n } catch {\n // Ignore transient IPC errors; worker lifecycle events will reconcile state.\n }\n }\n evaluateLiveness(Date.now());\n }, 5000);\n pingTimer.unref();\n}\n","/**\n * For low version of Node.js that does not support `Promise.try`, we can implement it ourselves.\n *\n * Only for async functions.\n */\nexport function PromiseTry<T extends (...args: any[]) => any>(fn: T, ...args: Parameters<T>) {\n return new Promise<ReturnType<T>>((resolve, reject) => {\n // in case `fn` throws synchronously, we catch it and reject the promise\n try {\n const r = fn(...args);\n if (r instanceof Promise) {\n r.then(resolve).catch(reject);\n } else {\n resolve(r);\n }\n } catch (error) {\n reject(error);\n }\n });\n}\n","import type { IncomingMessage } from 'node:http';\n\nexport function getRealIp(req: IncomingMessage): string {\n const forwardedFor = req.headersDistinct['x-forwarded-for'];\n if (forwardedFor) {\n const firstForwarded = forwardedFor[0]?.split(',')[0]?.trim();\n if (firstForwarded && firstForwarded.length > 0) {\n return firstForwarded;\n }\n }\n\n const realIp = req.headersDistinct['x-real-ip']?.[0].trim();\n if (realIp !== undefined) {\n return realIp;\n }\n\n return req.socket.remoteAddress ?? 'unknown';\n}\n\nexport function isTextualContentType(contentType: string | undefined): boolean {\n if (contentType === undefined) {\n return false;\n }\n\n const normalized = contentType.toLowerCase();\n\n return (\n normalized.startsWith('text/') ||\n normalized.includes('json') ||\n normalized.includes('xml') ||\n normalized.includes('x-www-form-urlencoded') ||\n normalized.includes('javascript')\n );\n}\n","import { DUMMY_BASE_URL } from '@/common/consts.js';\n\nexport function toURL(rawUrl: string | undefined): URL | undefined {\n if (rawUrl === undefined) {\n return undefined;\n }\n\n try {\n return new URL(rawUrl, DUMMY_BASE_URL);\n } catch {\n return undefined;\n }\n}\n","export function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const query: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = query[key];\n\n if (existing === undefined) {\n query[key] = value;\n continue;\n }\n\n if (Array.isArray(existing)) {\n existing.push(value);\n continue;\n }\n\n query[key] = [existing, value];\n }\n\n return query;\n}\n","import type http from 'node:http';\n\nimport { isTextualContentType } from './headers.js';\nimport { parseQuery } from './query.js';\n\nexport interface BodyPreview {\n exists: boolean;\n value?: string;\n bytes: number;\n truncated: boolean;\n}\n\nfunction createRequestBodyTooLargeError(receivedBytes: number, maxBytes: number): NodeJS.ErrnoException {\n const sizeError = new Error(\n `request body too large: ${receivedBytes.toString()} bytes exceeds ${maxBytes.toString()} bytes`,\n ) as NodeJS.ErrnoException;\n\n sizeError.code = 'REQUEST_BODY_TOO_LARGE';\n\n return sizeError;\n}\n\nfunction getHeaderValue(headerValue: string | string[] | undefined): string | undefined {\n return Array.isArray(headerValue) ? headerValue[0] : headerValue;\n}\n\nfunction createEmptyPreview(): BodyPreview {\n return {\n exists: false,\n bytes: 0,\n truncated: false,\n };\n}\n\nfunction createPreview(\n previewBuffer: Buffer,\n totalBytes: number,\n contentType: string | undefined,\n truncated: boolean,\n): BodyPreview {\n if (totalBytes === 0) {\n return createEmptyPreview();\n }\n\n if (isTextualContentType(contentType)) {\n return {\n exists: true,\n value: previewBuffer.toString('utf8'),\n bytes: totalBytes,\n truncated,\n };\n }\n\n return {\n exists: true,\n value: `<binary body: ${totalBytes} bytes>`,\n bytes: totalBytes,\n truncated,\n };\n}\n\nasync function readRequestBodyWithPreview(\n req: http.IncomingMessage,\n method: string,\n maxBytes: number,\n previewMaxBytes = 8192,\n): Promise<{ rawBody: Buffer | undefined; preview: BodyPreview }> {\n if (method === 'GET' || method === 'HEAD') {\n return {\n rawBody: undefined,\n preview: createEmptyPreview(),\n };\n }\n\n if (req.readableEnded) {\n return {\n rawBody: undefined,\n preview: createEmptyPreview(),\n };\n }\n\n const contentLengthRaw = getHeaderValue(req.headers['content-length']);\n const declaredBytes = contentLengthRaw !== undefined ? Number.parseInt(contentLengthRaw, 10) : NaN;\n\n if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {\n throw createRequestBodyTooLargeError(declaredBytes, maxBytes);\n }\n\n return new Promise((resolve, reject) => {\n const rawBodyChunks: Buffer[] = [];\n const previewChunks: Buffer[] = [];\n let totalBytes = 0;\n let previewBytes = 0;\n let previewTruncated = false;\n let settled = false;\n\n const cleanup = (): void => {\n req.off('data', onData);\n req.off('end', onEnd);\n req.off('error', onError);\n req.off('aborted', onAborted);\n };\n\n const settle = (action: () => void): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n action();\n };\n\n const onData = (chunk: Buffer | string | Uint8Array): void => {\n const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n totalBytes += bufferChunk.byteLength;\n\n if (totalBytes > maxBytes) {\n cleanup();\n req.resume();\n settle(() => {\n reject(createRequestBodyTooLargeError(totalBytes, maxBytes));\n });\n return;\n }\n\n rawBodyChunks.push(bufferChunk);\n\n if (previewBytes < previewMaxBytes) {\n const remaining = previewMaxBytes - previewBytes;\n const nextSlice = bufferChunk.subarray(0, remaining);\n previewChunks.push(nextSlice);\n previewBytes += nextSlice.length;\n\n if (nextSlice.length < bufferChunk.length) {\n previewTruncated = true;\n }\n } else {\n previewTruncated = true;\n }\n };\n\n const onEnd = (): void => {\n cleanup();\n settle(() => {\n const rawBody = rawBodyChunks.length > 0 ? Buffer.concat(rawBodyChunks) : undefined;\n const previewBuffer = previewChunks.length > 0 ? Buffer.concat(previewChunks) : Buffer.alloc(0);\n\n resolve({\n rawBody,\n preview: createPreview(\n previewBuffer,\n rawBody?.byteLength ?? 0,\n getHeaderValue(req.headers['content-type']),\n previewTruncated,\n ),\n });\n });\n };\n\n const onError = (error: Error): void => {\n cleanup();\n settle(() => {\n reject(error);\n });\n };\n\n const onAborted = (): void => {\n cleanup();\n settle(() => {\n reject(new Error('request aborted while reading body'));\n });\n };\n\n req.on('data', onData);\n req.once('end', onEnd);\n req.once('error', onError);\n req.once('aborted', onAborted);\n });\n}\n\nexport async function parseBody(\n req: http.IncomingMessage,\n method: string,\n maxBytes: number,\n): Promise<{ body: Record<string, any>; preview: BodyPreview }> {\n const { rawBody, preview } = await readRequestBodyWithPreview(req, method, maxBytes);\n\n if (rawBody === undefined || rawBody.byteLength === 0) {\n return {\n body: {},\n preview,\n };\n }\n\n const contentType = getHeaderValue(req.headers['content-type'])?.toLowerCase() ?? '';\n\n if (contentType.includes('json')) {\n const textBody = rawBody.toString('utf8').trim();\n\n if (textBody.length === 0) {\n return {\n body: {},\n preview,\n };\n }\n\n try {\n const parsed = JSON.parse(textBody) as unknown;\n\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return {\n body: parsed as Record<string, any>,\n preview,\n };\n }\n\n return {\n body: { value: parsed },\n preview,\n };\n } catch {\n return {\n body: { raw: textBody },\n preview,\n };\n }\n }\n\n if (contentType.includes('x-www-form-urlencoded')) {\n return {\n body: parseQuery(new URLSearchParams(rawBody.toString('utf8'))),\n preview,\n };\n }\n\n if (isTextualContentType(contentType)) {\n return {\n body: { raw: rawBody.toString('utf8') },\n preview,\n };\n }\n\n return {\n body: {},\n preview,\n };\n}\n","/**\n * Parse Cookie header string into an object\n */\nexport function parseCookie(cookieHeader: string | undefined): Record<string, string> {\n if (!cookieHeader) {\n return {};\n }\n\n const cookies: Record<string, string> = {};\n const pairs = cookieHeader.split(';');\n\n for (const pair of pairs) {\n const [key, ...valueParts] = pair.split('=');\n if (!key) continue;\n\n const trimmedKey = key.trim();\n const value = valueParts.join('=').trim();\n cookies[trimmedKey] = decodeURIComponent(value);\n }\n\n return cookies;\n}\n\n/**\n * Serialize an object into a Cookie header string\n */\nexport function serializeCookie(name: string, value: string, options?: {\n maxAge?: number;\n expires?: Date;\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n}): string {\n let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;\n\n if (options) {\n if (options.maxAge !== undefined) {\n cookie += `; Max-Age=${options.maxAge}`;\n }\n if (options.expires) {\n cookie += `; Expires=${options.expires.toUTCString()}`;\n }\n if (options.domain) {\n cookie += `; Domain=${options.domain}`;\n }\n if (options.path) {\n cookie += `; Path=${options.path}`;\n }\n if (options.secure) {\n cookie += '; Secure';\n }\n if (options.httpOnly) {\n cookie += '; HttpOnly';\n }\n if (options.sameSite) {\n cookie += `; SameSite=${options.sameSite}`;\n }\n }\n\n return cookie;\n}\n","import http from 'node:http';\nimport https from 'node:https';\n\nimport type { FluxionContext, FluxionModuleWithType, NormalizedRequest } from '../types.js';\nimport { $keys } from '@/common/native.js';\nimport {\n HttpCode,\n HANDLER_TIMEOUT_FLAG,\n META_PREFIX,\n STATIC_HANDLED_FLAG,\n FluxionModuleType,\n} from '@/common/consts.js';\nimport { PromiseTry } from '@/common/promise-try.js';\nimport { getErrorMessage } from '@/common/logger.js';\n\nimport { getRealIp } from '../http/headers.js';\nimport { toURL } from '../http/request.js';\nimport { safeSendJson } from '../http/respond.js';\nimport { parseBody, type BodyPreview } from '../http/body.js';\nimport { parseQuery } from '../http/query.js';\nimport { parseCookie } from '../http/cookie.js';\n\nexport function createWorkerServer(cx: FluxionContext): http.Server | https.Server {\n const requestHandler = async (req: http.IncomingMessage, res: http.ServerResponse) => {\n const method = req.method ?? 'GET';\n const ip = getRealIp(req);\n const url = toURL(req.url);\n if (url === undefined) {\n safeSendJson(res, { message: 'Bad Request: req.url is undefined' }, HttpCode.BadRequest);\n return;\n }\n\n const normalized: NormalizedRequest = {\n method,\n ip,\n url,\n query: parseQuery(url.searchParams),\n body: {},\n headers: req.headers,\n cookie: parseCookie(req.headers.cookie as string | undefined),\n };\n\n let bodyPreview: BodyPreview = {\n exists: false,\n bytes: 0,\n truncated: false,\n };\n\n cx.logger.info('Req', { method, ip, path: url.pathname });\n\n const start = performance.now();\n res.once('finish', () => {\n const fields: Record<string, unknown> = {\n workerId: process.env.WORKER_ID ?? '[primary]',\n method,\n ip,\n path: url.pathname,\n status: res.statusCode,\n duration: (performance.now() - start).toFixed(4),\n };\n\n if ($keys(normalized.query).length > 0) {\n fields.query = normalized.query;\n }\n\n if (bodyPreview.exists) {\n fields.body = bodyPreview.value;\n fields.bodyBytes = bodyPreview.bytes;\n fields.bodyTruncated = bodyPreview.truncated;\n }\n\n cx.logger.info('Res', fields);\n });\n\n // * Start request handling\n try {\n if (normalized.url.pathname.startsWith(META_PREFIX + '/')) {\n safeSendJson(res, { message: `Meta APIs are available on port ${cx.options.metaPort}` }, HttpCode.NotFound);\n return;\n }\n\n const parsed = await parseBody(req, normalized.method, cx.options.maxRequestBytes);\n normalized.body = parsed.body;\n bodyPreview = parsed.preview;\n\n const m = await cx.router.getModule(url);\n if (!m) {\n safeSendJson(res, { message: 'Not Found' }, HttpCode.NotFound);\n return;\n }\n\n if (req.method && m.methods && !m.methods.includes(req.method)) {\n safeSendJson(res, { message: 'Method Not Allowed' }, HttpCode.MethodNotAllowed);\n return;\n }\n\n const timeoutMs =\n m.type === FluxionModuleType.Api\n ? (m.handlerTimeoutMs ?? cx.options.handlerTimeoutMs)\n : cx.options.staticResourceTimeoutMs;\n\n const result = await Promise.race([\n PromiseTry(m.handler, normalized, req, res),\n new Promise((r) => setTimeout(() => r(HANDLER_TIMEOUT_FLAG), timeoutMs)),\n ]);\n\n if (result === HANDLER_TIMEOUT_FLAG) {\n cx.logger.warn('HandlerTimeout', {\n method: normalized.method,\n ip: normalized.ip,\n });\n safeSendJson(res, { message: 'Handler timed out' }, HttpCode.InternalServerError);\n return;\n }\n\n if (result !== STATIC_HANDLED_FLAG) {\n safeSendJson(res, result);\n }\n } catch (error) {\n cx.logger.error('RequestFailed', {\n method: normalized.method,\n ip: normalized.ip,\n path: normalized.url.pathname,\n error: getErrorMessage(error),\n });\n\n if ((error as NodeJS.ErrnoException).code === 'REQUEST_BODY_TOO_LARGE') {\n safeSendJson(res, { message: getErrorMessage(error) }, HttpCode.PayloadTooLarge);\n } else {\n safeSendJson(res, { message: getErrorMessage(error) }, HttpCode.InternalServerError);\n }\n }\n };\n\n const server = cx.options.https\n ? https.createServer(\n {\n key: cx.options.https.key,\n cert: cx.options.https.cert,\n ca: cx.options.https.ca,\n },\n requestHandler,\n )\n : http.createServer(requestHandler);\n\n server.on('close', () => {\n cx.logger.info('ServerClosed', {\n host: cx.options.host,\n port: cx.options.port,\n });\n });\n\n server.listen(cx.options.port, cx.options.host, () => {\n cx.logger.info('ServerStarted', {\n pid: process.pid,\n protocol: cx.options.https ? 'https' : 'http',\n host: cx.options.host,\n port: cx.options.port,\n });\n cx.logger.info('DynamicDirectory', { directory: cx.options.dir });\n });\n\n server.on('error', (error) => {\n cx.logger.error('ServerError', {\n error: getErrorMessage(error),\n });\n });\n\n return server;\n}\n","import type { PrimaryMessage } from './types.js';\nimport type { FluxionContext } from '../types.js';\nimport cluster from 'node:cluster';\n\nimport { getErrorMessage } from '@/common/logger.js';\nimport { WorkerAction, PrimaryAction, isPrimaryMessage } from './consts.js';\nimport { sendToPrimary } from './communicate.js';\nimport { createWorkerServer } from './server.js';\n\nconst startStatsReporter = () => {\n let previousCpuUsage = process.cpuUsage();\n let previousAt = Date.now();\n\n const interval = setInterval(() => {\n const now = Date.now();\n const elapsedMicros = Math.max(1, (now - previousAt) * 1000);\n const cpuDelta = process.cpuUsage(previousCpuUsage);\n const cpuPercent = Number((((cpuDelta.user + cpuDelta.system) / elapsedMicros) * 100).toFixed(2));\n\n previousCpuUsage = process.cpuUsage();\n previousAt = now;\n\n const memoryUsage = process.memoryUsage();\n sendToPrimary({\n type: WorkerAction.Stats,\n pid: process.pid,\n stats: {\n at: now,\n pid: process.pid,\n uptimeSeconds: Number(process.uptime().toFixed(3)),\n cpu: {\n userMicros: cpuDelta.user,\n systemMicros: cpuDelta.system,\n percent: cpuPercent,\n },\n memory: {\n rss: memoryUsage.rss,\n heapTotal: memoryUsage.heapTotal,\n heapUsed: memoryUsage.heapUsed,\n external: memoryUsage.external,\n arrayBuffers: memoryUsage.arrayBuffers,\n },\n },\n });\n }, 2000);\n\n interval.unref();\n};\n\nexport function initWorker(cx: FluxionContext) {\n if (cluster.isPrimary) {\n $throw('createWorker should only be called in worker process');\n }\n\n process.on('message', (raw: PrimaryMessage) => {\n if (!isPrimaryMessage(raw)) {\n return;\n }\n\n if (raw.type === PrimaryAction.Ping) {\n sendToPrimary({ type: WorkerAction.Pong, pid: process.pid, sentAt: raw.sentAt, receivedAt: Date.now() });\n return;\n }\n });\n\n sendToPrimary({ type: WorkerAction.Created, pid: process.pid });\n startStatsReporter();\n\n try {\n createWorkerServer(cx);\n sendToPrimary({ type: WorkerAction.Ready, pid: process.pid });\n } catch (e) {\n cx.logger.error('WorkerBootstrapFailed', {\n pid: process.pid,\n error: getErrorMessage(e),\n });\n process.exit(1);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { FluxionContext } from '../types.js';\n\nexport type WatcherContext = Pick<FluxionContext, 'options' | 'logger' | 'router'>;\n\nexport abstract class FluxionWatcherBase {\n protected readonly cx: WatcherContext;\n\n private timer: NodeJS.Timeout | null = null;\n private readonly filesChanged = new Map<string, string>();\n\n constructor(cx: WatcherContext) {\n this.cx = cx;\n }\n\n /**\n * Recursively register all files in the options directory.\n */\n protected async init(): Promise<this> {\n const dir = this.cx.options.dir;\n if (!fs.existsSync(dir)) {\n this.cx.logger.warn(`Directory does not exist: ${dir}`);\n return this;\n }\n\n const registerList: Promise<void>[] = [];\n\n const registerRecursive = (absoluteDir: string, relativeDir: string) => {\n const entries = fs.readdirSync(absoluteDir, { withFileTypes: true });\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const absolutePath = path.join(absoluteDir, entry.name);\n const relativePath = path.join(relativeDir, entry.name);\n\n if (entry.isDirectory()) {\n registerRecursive(absolutePath, relativePath);\n } else if (entry.isFile()) {\n const p = this.cx.router.register(absolutePath, relativePath).catch((e) => {\n this.cx.logger.error(`Error registering file ${relativePath}: ${(e as Error).message}`);\n });\n registerList.push(p);\n }\n }\n };\n\n registerRecursive(dir, '');\n await Promise.all(registerList);\n\n this.cx.logger.info(`Initial registration complete for directory: ${dir}`);\n return this;\n }\n\n protected queueUp(absolutePath: string, relativePath: string): void {\n this.filesChanged.set(absolutePath, relativePath);\n if (this.timer) {\n return;\n }\n\n this.timer = setTimeout(async () => {\n const promises = [...this.filesChanged].map(([absolutePath, relativePath]) =>\n this.cx.router\n .register(absolutePath, relativePath)\n .catch((err) => this.cx.logger.error(`Error refreshing handlers: ${(err as Error).message}`))\n .finally(() => this.filesChanged.delete(absolutePath)),\n );\n await Promise.all(promises);\n this.timer = null;\n }, this.cx.options.reloadDelay);\n }\n\n protected stopCore(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n\n this.filesChanged.clear();\n }\n\n abstract start(): Promise<this>;\n abstract stop(): this;\n}\n","import path from 'node:path';\nimport type { FSWatcher } from 'chokidar';\nimport chokidar from 'chokidar';\n\nimport { FluxionWatcherBase, type WatcherContext } from './base.js';\n\nexport class FluxionChokidarWatcher extends FluxionWatcherBase {\n private watcher: FSWatcher | null = null;\n\n constructor(cx: WatcherContext) {\n super(cx);\n }\n\n /**\n * Start watching files with chokidar.\n *\n * Using chokidar provides:\n * - Cross-platform recursive watch support (including Linux/CentOS)\n * - Better event handling and stability\n * - Automatic resource management\n */\n async start(): Promise<this> {\n this.stop();\n await this.init();\n\n const dir = this.cx.options.dir;\n this.watcher = chokidar\n .watch(dir, {\n persistent: true, // Keep the process running\n ignoreInitial: true, // Don't emit 'add' events for initial scan\n usePolling: false, // Use polling as fallback (helps with some network drives)\n awaitWriteFinish: {\n stabilityThreshold: 100,\n pollInterval: 50,\n }, // Atomic writes handling\n })\n .on('all', (_event, absolutePath) => {\n if (!absolutePath) {\n return;\n }\n\n // & `filename` is absolute(Maybe because of watching an absolute path `dir`)\n this.queueUp(absolutePath, path.relative(dir, absolutePath));\n })\n .on('error', (err: unknown) => {\n const error = err instanceof Error ? err : new Error(String(err));\n this.cx.logger.error(`Watcher error: ${error.message}`);\n this.cx.logger.error(`Restarting watcher...`);\n this.start();\n })\n .on('ready', () => {\n this.cx.logger.info(`Watcher ready and watching directory: ${dir}`);\n });\n\n this.cx.logger.info(`Watcher started on directory: ${dir}`);\n return this;\n }\n\n stop(): this {\n if (this.watcher) {\n void this.watcher.close();\n this.watcher = null;\n }\n\n this.stopCore();\n return this;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { FluxionWatcherBase, type WatcherContext } from './base.js';\n\nexport class FluxionNativeWatcher extends FluxionWatcherBase {\n private watcher: fs.FSWatcher | null = null;\n\n constructor(cx: WatcherContext) {\n super(cx);\n }\n\n /**\n * Since all actions are mapped to `rename` and `change` (WatchEventType).\n *\n * We could only record every file and reload them all.\n */\n async start(): Promise<this> {\n this.stop();\n await this.init();\n\n const dir = this.cx.options.dir;\n this.watcher = fs\n .watch(dir, { recursive: true }, (_eventType, relativePath) => {\n if (!relativePath) {\n return;\n }\n\n // & Unlike chokidar, `filename` here is relativePath\n this.queueUp(path.join(dir, relativePath), relativePath);\n })\n .on('error', (err) => {\n this.cx.logger.error(`Watcher error: ${err.message}`);\n this.cx.logger.error(`Restarting watcher...`);\n this.start();\n });\n\n this.cx.logger.info(`Watcher started on directory: ${dir}`);\n return this;\n }\n\n stop(): this {\n if (this.watcher) {\n this.watcher.close();\n this.watcher = null;\n }\n\n this.stopCore();\n return this;\n }\n}\n","function n(n) {}\n\nconst t = n;\n\nfunction o() {\n return n => {};\n}\n\nexport { o as createNarrower, n as narrow, t as static_cast };\n","import type { FluxionContext, FluxionModule, FluxionModuleWithType } from '@/types.js';\nimport { static_cast } from 'type-narrow';\nimport { FluxionModuleType } from './consts';\n\nfunction isFluxionModule(o: unknown): o is FluxionModule {\n if (typeof o !== 'object' || o === null) {\n return false;\n }\n\n static_cast<FluxionModule>(o);\n\n if (typeof o.handler !== 'function') {\n return false;\n }\n\n if (o.disposer !== undefined && typeof o.disposer !== 'function') {\n return false;\n }\n\n if (o.handlerTimeoutMs !== undefined && typeof o.handlerTimeoutMs !== 'function') {\n return false;\n }\n\n return true;\n}\n\nexport function loadFluxionModule(\n _cx: Pick<FluxionContext, 'options' | 'logger'>,\n fullpath: string,\n): FluxionModuleWithType {\n delete require.cache[fullpath];\n let m = require(fullpath);\n if (isFluxionModule(m)) {\n } else if (isFluxionModule(m.default)) {\n m = m.default;\n } else {\n $throw(`Invalid handler module '${fullpath}', make sure it satisfies defineFluxionModule(...) helper`);\n }\n\n return { ...m, type: FluxionModuleType.Api };\n}\n","import type { FluxionContext, FluxionModule, FluxionModuleWithType } from '../types.js';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { minimatch } from 'minimatch';\nimport { FluxionModuleType, STATIC_CONTENT_TYPES, STATIC_HANDLED_FLAG } from '@/common/consts.js';\nimport { loadFluxionModule } from '@/common/injector.js';\nimport { cctl } from '@/common/color.js';\nimport { PromiseTry } from '@/common/promise-try.js';\n\nexport class FluxionRouter {\n private readonly cx: Pick<FluxionContext, 'options' | 'logger'>;\n private readonly handlers: Map<string, FluxionModuleWithType> = new Map();\n\n constructor(cx: Pick<FluxionContext, 'options' | 'logger'>) {\n this.cx = cx;\n }\n\n makeStaticResource(filepath: string): FluxionModuleWithType {\n return {\n type: FluxionModuleType.StaticResource,\n handler: async (normalized, _req, res) => {\n if (normalized.method !== 'GET' && normalized.method !== 'HEAD') {\n res.statusCode = 405;\n res.setHeader('Allow', 'GET, HEAD');\n res.end();\n return;\n }\n\n if (!fs.existsSync(filepath)) {\n res.statusCode = 404;\n res.end('Not Found');\n return;\n }\n\n const stat = fs.statSync(filepath);\n if (!stat.isFile()) {\n res.statusCode = 404;\n res.end('Not Found');\n return;\n }\n\n const extension = path.extname(filepath).toLowerCase();\n const contentType = STATIC_CONTENT_TYPES[extension] ?? 'application/octet-stream';\n\n res.statusCode = 200;\n res.setHeader('Content-Type', contentType);\n res.setHeader('Content-Length', String(stat.size));\n\n if (normalized.method === 'HEAD') {\n res.end();\n return;\n }\n\n return new Promise<symbol>((resolve, reject) => {\n const stream = fs.createReadStream(filepath);\n stream.on('error', reject);\n stream.on('end', () => resolve(STATIC_HANDLED_FLAG));\n stream.pipe(res);\n });\n },\n };\n }\n\n /**\n * File registration logic with fast-glob pattern matching:\n * 1. Check if the path exists, if not, delete the handler;\n * 2. If file doesn't match include patterns, skip registration;\n * 3. If file matches exclude patterns, skip registration;\n * 4. If file matches apiInclude patterns, register as API handler;\n * 5. Otherwise, register as static resource.\n */\n async register(absolutePath: string, relativePath: string) {\n if (!fs.existsSync(absolutePath)) {\n // Get the disposer and delete\n const disposer = this.handlers.get(relativePath)?.disposer;\n if (disposer) {\n await PromiseTry(disposer);\n }\n\n this.handlers.delete(relativePath);\n // & Watcher will emit recursively, so there is no need to use this.remove(rp);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 2: Check if file matches include patterns (default: all files)\n // If not matching, skip registration\n const matchesInclude = this.cx.options.include.some((pattern) => minimatch(relativePath, pattern));\n if (!matchesInclude) {\n this.handlers.delete(relativePath);\n this.cx.logger.info(`${cctl.yellow}Skipped ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 3: Check if file matches exclude patterns\n // If matching, skip registration\n const matchesExclude = this.cx.options.exclude.some((pattern) => minimatch(relativePath, pattern));\n if (matchesExclude) {\n this.handlers.delete(relativePath);\n this.cx.logger.info(`${cctl.orange}Excluded${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // Step 4 & 5: Check if file matches apiInclude patterns\n // If matching, register as API handler; otherwise as static resource\n const matchesApiInclude = this.cx.options.apiInclude.some((pattern) => minimatch(relativePath, pattern));\n if (matchesApiInclude) {\n const handler = loadFluxionModule(this.cx, absolutePath);\n this.handlers.set(relativePath, handler);\n this.cx.logger.info(`${cctl.green}Api ${cctl.reset} - ${relativePath}`);\n return;\n }\n\n // register as static resource\n this.handlers.set(relativePath, this.makeStaticResource(relativePath));\n this.cx.logger.info(`${cctl.brightBlue}Static ${cctl.reset} - ${relativePath}`);\n }\n\n getModule(url: URL): FluxionModuleWithType | undefined {\n const relativePath = url.pathname.replace(/^[\\/]+/, '').replace(/[\\/]+$/, '');\n return this.handlers.get(relativePath);\n }\n\n /**\n * If the path points to a file, it would be simple.\n * But if it's a directory, we need to find all registered handlers under this directory and remove them.\n *\n * @param somepath\n * @deprecated\n */\n remove(somepath: string): void {\n if (this.handlers.has(somepath)) {\n this.handlers.delete(somepath);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${somepath}`);\n }\n // & Not in handler map -> It is a directory\n const prefix = somepath.endsWith('/') ? somepath : somepath + '/';\n for (const key of this.handlers.keys()) {\n if (key.startsWith(prefix)) {\n this.handlers.delete(key);\n this.cx.logger.info(`${cctl.red}Deleted ${cctl.reset} - ${key}`);\n }\n }\n }\n}\n","import type { FluxionContext, FluxionOptions } from './types.js';\n\nimport { createLogger, createWorkerLogger } from '@/common/logger.js';\nimport { normalizeOptions } from './http/options.js';\nimport cluster from 'node:cluster';\nimport { initPrimary } from './cluster/primary.js';\nimport { initWorker } from './cluster/worker.js';\nimport { FluxionChokidarWatcher } from './watcher/chokidar.js';\nimport { FluxionNativeWatcher } from './watcher/native.js';\nimport { FluxionRouter } from './router/index.js';\n\nexport async function fluxion(options: FluxionOptions) {\n const context = { options: normalizeOptions(options) } as FluxionContext;\n\n context.logger = createLogger(context as Pick<FluxionContext, 'options'>);\n context.router = new FluxionRouter(context as Pick<FluxionContext, 'options' | 'logger'>);\n\n if (cluster.isPrimary) {\n initPrimary(context);\n } else {\n // Replace logger with worker logger that prefixes PID\n context.logger = createWorkerLogger(context.logger, process.pid);\n // Only worker creates the watcher\n const Watcher = context.options.nativeWatcher ? FluxionNativeWatcher : FluxionChokidarWatcher;\n context.watcher = await new Watcher(context as Pick<FluxionContext, 'options' | 'logger' | 'router'>).start();\n initWorker(context);\n }\n}\n","import type { FluxionDispose, FluxionHandler, FluxionModule } from './types.js';\nimport { fluxion } from './fluxion.js';\nimport { noop } from './common/consts.js';\n\nexport { fluxion };\nexport type { FluxionDispose, FluxionHandler, FluxionModule as FluxionHandlerModule, FluxionOptions } from './types.js';\n\n/**\n * Use handler function and optional disposer function to define a Fluxion module.\n * @param handler Main function that handles request and response instances\n * @param disposer Deal with resource cleanup when the server is about to close\n */\nexport function defineFluxionModule(handler: FluxionHandler, disposer?: FluxionDispose): FluxionModule;\n\n/**\n * Provides type safety for defining Fluxion modules.\n */\nexport function defineFluxionModule(fluxionModule: FluxionModule): FluxionModule;\nexport function defineFluxionModule(a: FluxionModule | FluxionHandler, disposer: FluxionDispose = noop): FluxionModule {\n if (typeof a === 'function') {\n if (typeof disposer !== 'function') {\n $throw(`Invalid disposer, expected a function but got ${typeof disposer}`);\n }\n return { handler: a, disposer };\n }\n\n if (typeof a !== 'object' || a === null) {\n $throw(`Invalid argument, expected a FluxionModule object or a handler function, but got ${typeof a}`);\n }\n\n if (typeof a.handler !== 'function') {\n $throw(`Invalid FluxionModule, \"handler\" must be a function`);\n }\n\n if (a.disposer !== undefined && typeof a.disposer !== 'function') {\n $throw(`Invalid FluxionModule, \"disposer\" must be a function if provided`);\n }\n\n if (a.methods !== undefined && !Array.isArray(a.methods)) {\n $throw(`Invalid FluxionModule, \"methods\" must be an array of strings if provided`);\n }\n\n return a;\n}\n\nif (process.env.NODE_ENV !== 'production') {\n globalThis.$throw = (message: string) => {\n throw new Error('[fluxion error]' + message);\n };\n\n const int = (n: string | undefined, defaultValue: number): number => {\n if (n === undefined) {\n return defaultValue;\n }\n const parsed = Number.parseInt(n, 10);\n if (Number.isNaN(parsed)) {\n return defaultValue;\n }\n return parsed;\n };\n\n fluxion({\n dir: process.env.DYNAMIC_DIRECTORY ?? 'dynamicDirectory',\n host: process.env.HOST ?? 'localhost',\n port: int(process.env.PORT, 9000),\n metaPort: int(process.env.META_PORT, 9001),\n reloadDelay: process.env.RELOAD_DELAY ? Number.parseInt(process.env.RELOAD_DELAY, 10) : undefined,\n workerOptions: {\n maxWorkerCount: 4,\n },\n });\n}\n"],"x_google_ignoreList":[22],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -50,54 +50,48 @@ interface NormalizedRequest {
50
50
  cookie: Record<string, string>;
51
51
  }
52
52
  /**
53
- * Worker runtime tuning options.
53
+ * Conditions under which the primary proactively recycles a running worker.
54
+ *
55
+ * The primary observes every worker and restarts it when ANY configured
56
+ * condition is met. Thresholds are resolved at option-normalization time:
57
+ * `Infinity` disables a check, so that worker only restarts if it crashes on
58
+ * its own (respawn-on-exit then brings it back).
54
59
  */
55
- interface WorkerOptions {
56
- /**
57
- * Maximum number of worker processes to spawn.
58
- * @default 4
59
- */
60
- maxWorkerCount: number;
61
- /**
62
- * Maximum concurrent requests allowed in the pool.
63
- * @default 64
64
- */
65
- maxInflight: number;
60
+ interface WorkerRestartWhen {
66
61
  /**
67
- * Soft heap threshold in MB. Idle worker may restart after crossing it.
68
- * @default 96
62
+ * Recycle the worker when its RSS exceeds this many MB.
63
+ * Catches heap/native growth before the OS OOM-killer does.
64
+ * @default Infinity (disabled)
69
65
  */
70
- memorySoftLimitMb: number;
66
+ memoryUsageGreaterThan: number;
71
67
  /**
72
- * ! Hard heap threshold in MB. Worker is restarted once reached.
73
- * @default 128
68
+ * Recycle the worker when it has not answered a Ping within this many ms.
69
+ * Detects a wedged event loop (infinite loop, deadlock, GC storm).
70
+ * @default 30000
74
71
  */
75
- memoryHardLimitMb: number;
72
+ healthzTimeout: number;
76
73
  /**
77
- * Memory telemetry interval in milliseconds.
78
- * @default 5000
74
+ * Recycle the worker after it has run for this many ms (scheduled rotation).
75
+ * Reclaims slow growth / fragmentation even with no known leak.
76
+ * @default Infinity (disabled)
79
77
  */
80
- memorySampleIntervalMs: number;
81
- /**
82
- * ! V8 old-generation limit per worker in MB.
83
- * @default 128
84
- */
85
- maxOldGenerationSizeMb: number;
86
- /**
87
- * ! V8 young-generation limit per worker in MB.
88
- * @default 32
89
- */
90
- maxYoungGenerationSizeMb: number;
78
+ uptimeGreaterThan: number;
79
+ }
80
+ /**
81
+ * Worker options as supplied by the user. Everything is optional; omitted
82
+ * values fall back to the defaults resolved by `resolveWorkerOptions`.
83
+ */
84
+ interface WorkerOptions {
91
85
  /**
92
- * Worker stack size in MB.
86
+ * Maximum number of worker processes to spawn.
93
87
  * @default 4
94
88
  */
95
- stackSizeMb: number;
89
+ maxWorkerCount?: number;
96
90
  /**
97
- * ! Maximum response payload bytes allowed from worker to main thread.
98
- * @default 2097152 (2MB)
91
+ * When to proactively recycle a worker. Partial is allowed; omitted
92
+ * thresholds fall back to their {@link WorkerRestartWhen} defaults.
99
93
  */
100
- maxResponseBytes: number;
94
+ restartWhen?: Partial<WorkerRestartWhen>;
101
95
  }
102
96
  interface FluxionOptions {
103
97
  /**
@@ -111,6 +105,10 @@ interface FluxionOptions {
111
105
  * Default to 5000ms.
112
106
  */
113
107
  handlerTimeoutMs?: number;
108
+ /**
109
+ * Default to 10 minutes 10*60*1000ms.
110
+ */
111
+ staticResourceTimeoutMs?: number;
114
112
  /**
115
113
  * Delay in milliseconds for reloading handlers after file changes are detected.
116
114
  *
@@ -128,9 +126,9 @@ interface FluxionOptions {
128
126
  */
129
127
  moduleDir?: string;
130
128
  /**
131
- * Base worker runtime option overrides.
129
+ * Worker pool tuning: max worker count and proactive recycle conditions.
132
130
  */
133
- workerOptions?: Partial<WorkerOptions>;
131
+ workerOptions?: WorkerOptions;
134
132
  /**
135
133
  * Maximum request body bytes accepted by dynamic handlers.
136
134
  * Requests larger than this limit will return 413.
@@ -191,10 +189,29 @@ type FluxionHandler<Request extends typeof http.IncomingMessage = typeof http.In
191
189
  req: InstanceType<Request>;
192
190
  }) => Promise<unknown> | unknown;
193
191
  type FluxionDispose = () => Promise<void> | void;
192
+ /**
193
+ * Supported HTTP methods for FluxionModule
194
+ */
195
+ type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'TRACE' | 'CONNECT' | otherstring;
194
196
  interface FluxionModule {
197
+ /**
198
+ * Main handler for an api
199
+ */
195
200
  handler: FluxionHandler;
201
+ /**
202
+ * This is meant to clear resources used by handler while it's down.
203
+ */
196
204
  disposer?: FluxionDispose;
205
+ /**
206
+ * How many ms to wait until the request times out
207
+ */
197
208
  handlerTimeoutMs?: number;
209
+ /**
210
+ * Allowed HTTP methods for this module.
211
+ * If specified, only these methods will be accepted.
212
+ * @example ['GET', 'POST']
213
+ */
214
+ methods?: HTTPMethod[];
198
215
  }
199
216
  //#endregion
200
217
  //#region src/fluxion.d.ts