fluxion-ts 0.7.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -286,7 +286,7 @@ interface FluxionOptions {
286
286
  nativeWatcher?: boolean;
287
287
  injections?: InjectionConfig[];
288
288
  moduleDir?: string;
289
- workerOptions?: Partial<WorkerOptions>;
289
+ workerOptions?: WorkerOptions;
290
290
  maxRequestBytes?: number;
291
291
  logger?: 'one-line' | 'json-line' | InjectionConfig;
292
292
  apiExts?: string[];
@@ -393,24 +393,46 @@ globalThis[Symbol.for('fluxion.injection')]
393
393
 
394
394
  ### `workerOptions`
395
395
 
396
- Runtime tuning options:
396
+ Worker pool tuning: how many workers to spawn, and when to proactively recycle one.
397
397
 
398
398
  ```ts
399
399
  interface WorkerOptions {
400
- maxWorkerCount: number;
401
- requestTimeoutMs: number;
402
- maxInflight: number;
403
- memorySoftLimitMb: number;
404
- memoryHardLimitMb: number;
405
- memorySampleIntervalMs: number;
406
- maxOldGenerationSizeMb: number;
407
- maxYoungGenerationSizeMb: number;
408
- stackSizeMb: number;
409
- maxResponseBytes: number;
400
+ maxWorkerCount?: number;
401
+ restartWhen?: Partial<WorkerRestartWhen>;
402
+ }
403
+
404
+ interface WorkerRestartWhen {
405
+ /** Recycle when RSS exceeds this many MB. Infinity (default) = disabled. */
406
+ memoryUsageGreaterThan: number;
407
+ /** Recycle when no Ping answer within this many ms. Default 30000. */
408
+ healthzTimeout: number;
409
+ /** Recycle after this many ms of uptime (scheduled rotation). Infinity (default) = disabled. */
410
+ uptimeGreaterThan: number;
410
411
  }
411
412
  ```
412
413
 
413
- Current implementation uses `maxWorkerCount` for process count and reports CPU/memory telemetry from workers.
414
+ - `maxWorkerCount` defaults to `4`, clamped to the CPU count (minimum 1).
415
+ - `restartWhen` lets the primary proactively recycle an unhealthy worker. The worker is hard-killed and immediately respawned when **any** configured condition is met (OR semantics):
416
+ - `memoryUsageGreaterThan` — RSS growth / native leak, caught before the OS OOM-killer. Disabled by default.
417
+ - `healthzTimeout` — a wedged event loop (worker stopped answering Ping: infinite loop, deadlock, GC storm). **Defaults to `30000`ms.**
418
+ - `uptimeGreaterThan` — scheduled rotation to reclaim slow growth / fragmentation. Disabled by default.
419
+ - Conditions are evaluated by the primary against the telemetry it already collects (RSS from stats every ~2s, liveness from Ping every 5s, uptime).
420
+ - A shared **anti-storm guard** bounds recycling: a given slot is restarted at most 3 times per rolling 60s, after which further restarts are suppressed and alerted instead of fork-bombing.
421
+ - Independently of `restartWhen`, **any worker exit — crash, OOM, or proactive recycle — triggers a respawn**, so the pool stays at `maxWorkerCount`.
422
+
423
+ ```ts
424
+ fluxion({
425
+ // ...
426
+ workerOptions: {
427
+ maxWorkerCount: 4,
428
+ restartWhen: {
429
+ memoryUsageGreaterThan: 256, // MB; recycle a leaking worker at 256MB RSS
430
+ // healthzTimeout defaults to 30000 — recycle wedged workers after 30s
431
+ // uptimeGreaterThan: 6 * 3600_000, // optionally rotate every 6h
432
+ },
433
+ },
434
+ });
435
+ ```
414
436
 
415
437
  ### `https`
416
438
 
@@ -525,24 +547,46 @@ globalThis[Symbol.for('fluxion.injection')]
525
547
 
526
548
  ### `workerOptions`
527
549
 
528
- Runtime tuning options:
550
+ Worker pool tuning: how many workers to spawn, and when to proactively recycle one.
529
551
 
530
552
  ```ts
531
553
  interface WorkerOptions {
532
- maxWorkerCount: number;
533
- requestTimeoutMs: number;
534
- maxInflight: number;
535
- memorySoftLimitMb: number;
536
- memoryHardLimitMb: number;
537
- memorySampleIntervalMs: number;
538
- maxOldGenerationSizeMb: number;
539
- maxYoungGenerationSizeMb: number;
540
- stackSizeMb: number;
541
- maxResponseBytes: number;
554
+ maxWorkerCount?: number;
555
+ restartWhen?: Partial<WorkerRestartWhen>;
556
+ }
557
+
558
+ interface WorkerRestartWhen {
559
+ /** Recycle when RSS exceeds this many MB. Infinity (default) = disabled. */
560
+ memoryUsageGreaterThan: number;
561
+ /** Recycle when no Ping answer within this many ms. Default 30000. */
562
+ healthzTimeout: number;
563
+ /** Recycle after this many ms of uptime (scheduled rotation). Infinity (default) = disabled. */
564
+ uptimeGreaterThan: number;
542
565
  }
543
566
  ```
544
567
 
545
- Current implementation uses `maxWorkerCount` for process count and reports CPU/memory telemetry from workers.
568
+ - `maxWorkerCount` defaults to `4`, clamped to the CPU count (minimum 1).
569
+ - `restartWhen` lets the primary proactively recycle an unhealthy worker. The worker is hard-killed and immediately respawned when **any** configured condition is met (OR semantics):
570
+ - `memoryUsageGreaterThan` — RSS growth / native leak, caught before the OS OOM-killer. Disabled by default.
571
+ - `healthzTimeout` — a wedged event loop (worker stopped answering Ping: infinite loop, deadlock, GC storm). **Defaults to `30000`ms.**
572
+ - `uptimeGreaterThan` — scheduled rotation to reclaim slow growth / fragmentation. Disabled by default.
573
+ - Conditions are evaluated by the primary against the telemetry it already collects (RSS from stats every ~2s, liveness from Ping every 5s, uptime).
574
+ - A shared **anti-storm guard** bounds recycling: a given slot is restarted at most 3 times per rolling 60s, after which further restarts are suppressed and alerted instead of fork-bombing.
575
+ - Independently of `restartWhen`, **any worker exit — crash, OOM, or proactive recycle — triggers a respawn**, so the pool stays at `maxWorkerCount`.
576
+
577
+ ```ts
578
+ fluxion({
579
+ // ...
580
+ workerOptions: {
581
+ maxWorkerCount: 4,
582
+ restartWhen: {
583
+ memoryUsageGreaterThan: 256, // MB; recycle a leaking worker at 256MB RSS
584
+ // healthzTimeout defaults to 30000 — recycle wedged workers after 30s
585
+ // uptimeGreaterThan: 6 * 3600_000, // optionally rotate every 6h
586
+ },
587
+ },
588
+ });
589
+ ```
546
590
 
547
591
  ## Build and Test
548
592
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("node:fs");c=s(c);let l=require("node:path");l=s(l);let u=require("node:cluster");u=s(u);let d=require("node:os");d=s(d);let f=require("node:http");f=s(f);let p=require("node:https");p=s(p);let m=require("chokidar");m=s(m);let h=require("minimatch");function g(e=new Date){return`${e.getFullYear()}.${String(e.getMonth()+1).padStart(2,`0`)}.${String(e.getDate()).padStart(2,`0`)} ${String(e.getHours()).padStart(2,`0`)}:${String(e.getMinutes()).padStart(2,`0`)}:${String(e.getSeconds()).padStart(2,`0`)}.${String(e.getMilliseconds()).padStart(3,`0`)}`}const _=JSON.stringify,v=Object.keys,y=process.env.FLUXION_COLORS!==`0`;let b;(function(e){e.reset=y?`\x1B[0m`:``,e.bold=y?`\x1B[1m`:``,e.dim=y?`\x1B[2m`:``,e.italic=y?`\x1B[3m`:``,e.underline=y?`\x1B[4m`:``,e.blink=y?`\x1B[5m`:``,e.inverse=y?`\x1B[7m`:``,e.black=y?`\x1B[30m`:``,e.red=y?`\x1B[31m`:``,e.green=y?`\x1B[32m`:``,e.yellow=y?`\x1B[33m`:``,e.blue=y?`\x1B[34m`:``,e.magenta=y?`\x1B[35m`:``,e.cyan=y?`\x1B[36m`:``,e.white=y?`\x1B[37m`:``,e.brightBlack=y?`\x1B[90m`:``,e.brightRed=y?`\x1B[91m`:``,e.brightGreen=y?`\x1B[92m`:``,e.brightYellow=y?`\x1B[93m`:``,e.brightBlue=y?`\x1B[94m`:``,e.brightMagenta=y?`\x1B[95m`:``,e.brightCyan=y?`\x1B[96m`:``,e.brightWhite=y?`\x1B[97m`:``,e.bgBlack=y?`\x1B[40m`:``,e.bgRed=y?`\x1B[41m`:``,e.bgGreen=y?`\x1B[42m`:``,e.bgYellow=y?`\x1B[43m`:``,e.bgBlue=y?`\x1B[44m`:``,e.bgMagenta=y?`\x1B[45m`:``,e.bgCyan=y?`\x1B[46m`:``,e.bgWhite=y?`\x1B[47m`:``,e.bgBrightBlack=y?`\x1B[100m`:``,e.bgBrightRed=y?`\x1B[101m`:``,e.bgBrightGreen=y?`\x1B[102m`:``,e.bgBrightYellow=y?`\x1B[103m`:``,e.bgBrightBlue=y?`\x1B[104m`:``,e.bgBrightMagenta=y?`\x1B[105m`:``,e.bgBrightCyan=y?`\x1B[106m`:``,e.bgBrightWhite=y?`\x1B[107m`:``,e.purple=y?`\x1B[38;2;225;16;248m`:``,e.orange=y?`\x1B[38;2;248;147;16m`:``,e.darkGreen=y?`\x1B[38;2;22;101;52m`:``,e.claude=y?`\x1B[38;2;217;119;87m`:``,e.deepseek=y?`\x1B[38;2;57;100;254m`:``,e.gpt=y?`\x1B[38;2;41;60;77m`:``})(b||={});const x=e=>{try{return _(e)}catch{return`[unserializable]`}},S={INFO:`${b.cyan}INFO${b.reset}`,WARN:`${b.orange}WARN${b.reset}`,ERROR:`${b.red}ERROR${b.reset}`,SUCC:`${b.green}SUCC${b.reset}`,DEBUG:`${b.blue}DEBUG${b.reset}`,VERBOSE:`${b.purple}VERBOSE${b.reset}`},C=e=>{let{level:t,timestamp:n,event:r,message:i,...a}=e,o=`${b.darkGreen}[${n}]${b.reset}`,s=S[t]??t,c=i??r,l=v(a).length>0?`${b.dim}${x(a)}${b.reset}`:``;console.log(`${o} ${s} ${c}${l}`)};function w(e){let t=e.options.logger;return t===void 0||t===`one-line`?C:t===`json-line`?e=>console.log(x(e)):t}function ee(e){let t=w(e);return{write(e,n,r={}){let i={...r,timestamp:g(),level:e,event:n};try{t(i)}catch{}},info(e,t){this.write(`INFO`,e,t)},warn(e,t){this.write(`WARN`,e,t)},error(e,t){this.write(`ERROR`,e,t)},succ(e,t){this.write(`SUCC`,e,t)},debug(e,t){this.write(`DEBUG`,e,t)},verbose(e,t){this.write(`VERBOSE`,e,t)}}}function te(e,t){let n=`[${t}]`;return{write(t,r,i){e.write(t,`${n} ${r}`,i)},info(t,r){e.info(`${n} ${t}`,r)},warn(t,r){e.warn(`${n} ${t}`,r)},error(t,r){e.error(`${n} ${t}`,r)},succ(t,r){e.succ(`${n} ${t}`,r)},debug(t,r){e.debug(`${n} ${t}`,r)},verbose(t,r){e.verbose(`${n} ${t}`,r)}}}const T=typeof Error.isError==`function`?e=>Error.isError(e)?e.message:String(e):e=>e?.message||String(e);function E(e){return{maxWorkerCount:e.maxWorkerCount??4,maxInflight:e.maxInflight??64,memorySoftLimitMb:e.memorySoftLimitMb??96,memoryHardLimitMb:e.memoryHardLimitMb??128,memorySampleIntervalMs:e.memorySampleIntervalMs??5e3,maxOldGenerationSizeMb:e.maxOldGenerationSizeMb??128,maxYoungGenerationSizeMb:e.maxYoungGenerationSizeMb??32,stackSizeMb:e.stackSizeMb??4,maxResponseBytes:e.maxResponseBytes??2*1024*1024}}function D(e,t){if(Buffer.isBuffer(e))return e;if(typeof e==`string`){if(!e.startsWith(`-----BEGIN`)){let n=l.default.isAbsolute(e)?e:l.default.join(t,e);if(c.default.existsSync(n))return c.default.readFileSync(n)}return Buffer.from(e)}$throw(`Certificate content must be a string or Buffer`)}function O(e,t){if(!e)return;(typeof e!=`object`||!e||Array.isArray(e))&&$throw(`FluxionOptions.https must be an object`),typeof e.key!=`string`&&$throw(`FluxionOptions.https.key must be a string`),typeof e.cert!=`string`&&$throw(`FluxionOptions.https.cert must be a string`);let n={key:D(e.key,t),cert:D(e.cert,t)};return e.ca!==void 0&&(Array.isArray(e.ca)?n.ca=e.ca.map(e=>D(e,t)):n.ca=D(e.ca,t)),n}function k(e){(typeof e!=`object`||!e||Array.isArray(e))&&$throw(`FluxionOptions must be an object`);let{dir:t,host:n,port:r,handlerTimeoutMs:i=5e3,metaPort:a,moduleDir:o=process.cwd(),workerOptions:s={},maxRequestBytes:u=8e6,reloadDelay:d=500,include:f=[`**/*`],apiInclude:p=[`**/*.ts`],exclude:m=[`**/node_modules/**`,`**/.git/**`,`**/dist/**`,`**/build/**`,`**/.vscode/**`,`**/.idea/**`,`**/*.log`,`**/.DS_Store`,`**/coverage/**`,`**/.nyc_output/**`,`**/*.tmp`,`**/*.temp`],https:h,nativeWatcher:g=!1}=e,_=e.logger??`one-line`;return _!==`one-line`&&_!==`json-line`&&typeof _!=`function`&&$throw(`Invalid logger option, Must be 'one-line', 'json-line' or a custom logger function`),typeof t!=`string`&&$throw(`FluxionOptions.dir must be a string`),typeof o!=`string`&&$throw(`FluxionOptions.moduleDir must be a string`),typeof n!=`string`&&$throw(`FluxionOptions.host must be a string`),(!Number.isSafeInteger(i)||i<=100)&&$throw(`FluxionOptions.handlerTimeoutMs must be an integer greater than 100`),(typeof d!=`number`||d<=0||!Number.isSafeInteger(d))&&$throw(`FluxionOptions.reloadDelay must be a positive integer`),d<50&&$throw(`FluxionOptions.reloadDelay must be greater than or equal to 50`),(typeof r!=`number`||!Number.isSafeInteger(r))&&$throw(`FluxionOptions.port must be a positive integer`),(r<=1||r>65535)&&$throw(`FluxionOptions.port must be 1 ~ 65535`),a??=r+1,(typeof a!=`number`||!Number.isSafeInteger(a))&&$throw(`FluxionOptions.metaPort must be a positive integer`),(a<=1||a>65535)&&$throw(`FluxionOptions.metaPort must be 1 ~ 65535`),a===r&&$throw(`FluxionOptions.metaPort must be different from FluxionOptions.port`),(typeof s!=`object`||!s||Array.isArray(s))&&$throw(`FluxionOptions.workerOptions must be an object`),(typeof u!=`number`||u<=0||!Number.isSafeInteger(u))&&$throw(`FluxionOptions.maxRequestBytes must be a positive integer`),t=l.default.isAbsolute(t)?t:l.default.join(process.cwd(),t),c.default.existsSync(t)||c.default.mkdirSync(t,{recursive:!0}),{dir:t,host:n,port:r,handlerTimeoutMs:i,reloadDelay:d,metaPort:a,moduleDir:o,workerOptions:E(s),maxRequestBytes:u,logger:_,include:f,apiInclude:p,exclude:m,nativeWatcher:g,https:O(h,o)}}const A=e=>[100].includes(e?.type),j=e=>[202,200,201,203].includes(e?.type),M=e=>process.send?.(e),N=(e,t)=>e.send(t),P=Symbol.for(`fluxion.router.StaticHandled`),F=Symbol.for(`fluxion.handlerTimeout`),ne={".css":`text/css; charset=utf-8`,".html":`text/html; charset=utf-8`,".ico":`image/x-icon`,".js":`text/javascript; charset=utf-8`,".json":`application/json; charset=utf-8`,".map":`application/json; charset=utf-8`,".png":`image/png`,".jpg":`image/jpeg`,".jpeg":`image/jpeg`,".svg":`image/svg+xml`,".txt":`text/plain; charset=utf-8`,".webp":`image/webp`},re=()=>{};function I(e,t,n=200){e.statusCode=n,e.setHeader(`Content-Type`,`application/json; charset=utf-8`),e.end(JSON.stringify(t))}function L(e,t,n=200){if(!e.writableEnded){if(e.headersSent){e.end();return}I(e,t,n)}}function R(e,t){let n=f.default.createServer((e,n)=>{let r=e.method??`GET`,i=`/`;try{i=new URL(e.url??`/`,`http://fluxion.local`).pathname}catch{I(n,{message:`Bad Request: invalid url`},400);return}if(r===`GET`&&i===`/_fluxion/healthz`){I(n,{ok:!0,role:`primary`,pid:process.pid,now:Date.now(),uptimeSeconds:Number(process.uptime().toFixed(3))});return}if(r===`GET`&&i===`/_fluxion/workers`){I(n,{ok:!0,now:Date.now(),workers:t()});return}I(n,{message:`Not Found`},404)});return n.on(`listening`,()=>{e.logger.info(`MetaApiStarted`,{pid:process.pid,host:e.options.host,port:e.options.metaPort,prefix:`/_fluxion`})}),n.on(`error`,t=>{e.logger.error(`MetaApiError`,{host:e.options.host,port:e.options.metaPort,error:T(t)}),process.exit(1)}),n.listen(e.options.metaPort,e.options.host),n}const z=e=>Number((e/1024/1024).toFixed(2));function B(e){u.default.isPrimary||$throw(`createPrimary should only be called in primary process`);let{workerOptions:t}=e.options,n=Math.max(1,d.default.cpus().length),r=Math.max(1,Math.min(t.maxWorkerCount??Math.min(2,n),n));e.logger.info(`PrimaryStarted`,{pid:process.pid,workers:r,host:e.options.host,port:e.options.port,metaPort:e.options.metaPort});let i=new Map;R(e,()=>({primaryPid:process.pid,host:e.options.host,port:e.options.port,metaPort:e.options.metaPort,uptimeSeconds:Number(process.uptime().toFixed(3)),workers:Array.from(i.entries()).map(([e,t])=>{let{instance:n}=t,r=t.lastStats;return{workerId:e,pid:t.pid??n.process.pid??null,state:t.state,createdAt:t.createdAt,readyAt:t.readyAt??null,connected:n.isConnected(),dead:n.isDead(),exitedAfterDisconnect:n.exitedAfterDisconnect,lastPongAt:t.lastPongAt??null,lastRttMs:t.lastRttMs??null,stats:r===void 0?null:{at:r.at,uptimeSeconds:r.uptimeSeconds,cpu:r.cpu,memory:{...r.memory,rssMb:z(r.memory.rss),heapTotalMb:z(r.memory.heapTotal),heapUsedMb:z(r.memory.heapUsed),externalMb:z(r.memory.external),arrayBuffersMb:z(r.memory.arrayBuffers)}}}})}));let a=t=>{let n={state:`creating`,pid:t.process.pid,createdAt:Date.now(),instance:t};i.set(t.id,n),t.on(`message`,r=>{if(j(r)){if(r.type===202){let e=Date.now()-r.sentAt;n.pid=r.pid,n.lastPongAt=Date.now(),n.lastRttMs=e;return}if(r.type===201){n.state=`ready`,n.pid=r.pid,n.readyAt=Date.now(),e.logger.info(`WorkerReady`,{workerId:t.id,pid:r.pid});return}if(r.type===200){n.state=`created`,n.pid=r.pid,e.logger.info(`WorkerCreated`,{workerId:t.id,pid:r.pid});return}r.type===203&&(n.pid=r.pid,n.lastStats=r.stats)}}),t.on(`exit`,(n,r)=>{i.delete(t.id),e.logger.warn(`WorkerExited`,{workerId:t.id,pid:t.process.pid??`unknown`,code:n,signal:r??`none`})})};for(let e=0;e<r;e++)a(u.default.fork({WORKER_ID:String(e+1)}));setInterval(()=>{let e=Date.now();for(let t of i.values())if(t.instance.isConnected())try{N(t.instance,{type:100,sentAt:e})}catch{}},5e3).unref()}function V(e,...t){return new Promise((n,r)=>{try{let i=e(...t);i instanceof Promise?i.then(n).catch(r):n(i)}catch(e){r(e)}})}function H(e){let t=e.headersDistinct[`x-forwarded-for`];if(t){let e=t[0]?.split(`,`)[0]?.trim();if(e&&e.length>0)return e}let n=e.headersDistinct[`x-real-ip`]?.[0].trim();return n===void 0?e.socket.remoteAddress??`unknown`:n}function U(e){if(e===void 0)return!1;let t=e.toLowerCase();return t.startsWith(`text/`)||t.includes(`json`)||t.includes(`xml`)||t.includes(`x-www-form-urlencoded`)||t.includes(`javascript`)}function W(e){if(e!==void 0)try{return new URL(e,`http://fluxion.local`)}catch{return}}function G(e){let t={};for(let[n,r]of e.entries()){let e=t[n];if(e===void 0){t[n]=r;continue}if(Array.isArray(e)){e.push(r);continue}t[n]=[e,r]}return t}function K(e,t){let n=Error(`request body too large: ${e.toString()} bytes exceeds ${t.toString()} bytes`);return n.code=`REQUEST_BODY_TOO_LARGE`,n}function q(e){return Array.isArray(e)?e[0]:e}function J(){return{exists:!1,bytes:0,truncated:!1}}function Y(e,t,n,r){return t===0?J():U(n)?{exists:!0,value:e.toString(`utf8`),bytes:t,truncated:r}:{exists:!0,value:`<binary body: ${t} bytes>`,bytes:t,truncated:r}}async function ie(e,t,n,r=8192){if(t===`GET`||t===`HEAD`||e.readableEnded)return{rawBody:void 0,preview:J()};let i=q(e.headers[`content-length`]),a=i===void 0?NaN:Number.parseInt(i,10);if(Number.isFinite(a)&&a>n)throw K(a,n);return new Promise((t,i)=>{let a=[],o=[],s=0,c=0,l=!1,u=!1,d=()=>{e.off(`data`,p),e.off(`end`,m),e.off(`error`,h),e.off(`aborted`,g)},f=e=>{u||(u=!0,e())},p=t=>{let u=Buffer.isBuffer(t)?t:Buffer.from(t);if(s+=u.byteLength,s>n){d(),e.resume(),f(()=>{i(K(s,n))});return}if(a.push(u),c<r){let e=r-c,t=u.subarray(0,e);o.push(t),c+=t.length,t.length<u.length&&(l=!0)}else l=!0},m=()=>{d(),f(()=>{let n=a.length>0?Buffer.concat(a):void 0;t({rawBody:n,preview:Y(o.length>0?Buffer.concat(o):Buffer.alloc(0),n?.byteLength??0,q(e.headers[`content-type`]),l)})})},h=e=>{d(),f(()=>{i(e)})},g=()=>{d(),f(()=>{i(Error(`request aborted while reading body`))})};e.on(`data`,p),e.once(`end`,m),e.once(`error`,h),e.once(`aborted`,g)})}async function ae(e,t,n){let{rawBody:r,preview:i}=await ie(e,t,n);if(r===void 0||r.byteLength===0)return{body:{},preview:i};let a=q(e.headers[`content-type`])?.toLowerCase()??``;if(a.includes(`json`)){let e=r.toString(`utf8`).trim();if(e.length===0)return{body:{},preview:i};try{let t=JSON.parse(e);return typeof t==`object`&&t&&!Array.isArray(t)?{body:t,preview:i}:{body:{value:t},preview:i}}catch{return{body:{raw:e},preview:i}}}return a.includes(`x-www-form-urlencoded`)?{body:G(new URLSearchParams(r.toString(`utf8`))),preview:i}:U(a)?{body:{raw:r.toString(`utf8`)},preview:i}:{body:{},preview:i}}function oe(e){if(!e)return{};let t={},n=e.split(`;`);for(let e of n){let[n,...r]=e.split(`=`);if(!n)continue;let i=n.trim(),a=r.join(`=`).trim();t[i]=decodeURIComponent(a)}return t}function se(e){let t=async(t,n)=>{let r=t.method??`GET`,i=H(t),a=W(t.url);if(a===void 0){L(n,{message:`Bad Request: req.url is undefined`},400);return}let o={method:r,ip:i,url:a,query:G(a.searchParams),body:{},headers:t.headers,cookie:oe(t.headers.cookie)},s={exists:!1,bytes:0,truncated:!1};e.logger.info(`Req`,{method:r,ip:i,path:a.pathname});let c=performance.now();n.once(`finish`,()=>{let t={workerId:process.env.WORKER_ID??`[primary]`,method:r,ip:i,path:a.pathname,status:n.statusCode,duration:(performance.now()-c).toFixed(4)};v(o.query).length>0&&(t.query=o.query),s.exists&&(t.body=s.value,t.bodyBytes=s.bytes,t.bodyTruncated=s.truncated),e.logger.info(`Res`,t)});try{if(o.url.pathname.startsWith(`/_fluxion/`)){L(n,{message:`Meta APIs are available on port ${e.options.metaPort}`},404);return}let r=await ae(t,o.method,e.options.maxRequestBytes);o.body=r.body,s=r.preview;let i=await e.router.getModule(a);if(!i){L(n,{message:`Not Found`},404);return}let c=i.handlerTimeoutMs??e.options.handlerTimeoutMs,l=await Promise.race([V(i.handler,o,t,n),new Promise(e=>setTimeout(()=>e(F),c))]);if(l===F){e.logger.warn(`HandlerTimeout`,{method:o.method,ip:o.ip}),L(n,{message:`Handler timed out`},500);return}l!==P&&L(n,l)}catch(t){e.logger.error(`RequestFailed`,{method:o.method,ip:o.ip,path:o.url.pathname,error:T(t)}),t.code===`REQUEST_BODY_TOO_LARGE`?L(n,{message:T(t)},413):L(n,{message:T(t)},500)}},n=e.options.https?p.default.createServer({key:e.options.https.key,cert:e.options.https.cert,ca:e.options.https.ca},t):f.default.createServer(t);return n.on(`close`,()=>{e.logger.info(`ServerClosed`,{host:e.options.host,port:e.options.port})}),n.listen(e.options.port,e.options.host,()=>{e.logger.info(`ServerStarted`,{pid:process.pid,protocol:e.options.https?`https`:`http`,host:e.options.host,port:e.options.port}),e.logger.info(`DynamicDirectory`,{directory:e.options.dir})}),n.on(`error`,t=>{e.logger.error(`ServerError`,{error:T(t)})}),n}const ce=()=>{let e=process.cpuUsage(),t=Date.now();setInterval(()=>{let n=Date.now(),r=Math.max(1,(n-t)*1e3),i=process.cpuUsage(e),a=Number(((i.user+i.system)/r*100).toFixed(2));e=process.cpuUsage(),t=n;let o=process.memoryUsage();M({type:203,pid:process.pid,stats:{at:n,pid:process.pid,uptimeSeconds:Number(process.uptime().toFixed(3)),cpu:{userMicros:i.user,systemMicros:i.system,percent:a},memory:{rss:o.rss,heapTotal:o.heapTotal,heapUsed:o.heapUsed,external:o.external,arrayBuffers:o.arrayBuffers}}})},2e3).unref()};function le(e){u.default.isPrimary&&$throw(`createWorker should only be called in worker process`),process.on(`message`,e=>{if(A(e)&&e.type===100){M({type:202,pid:process.pid,sentAt:e.sentAt,receivedAt:Date.now()});return}}),M({type:200,pid:process.pid}),ce();try{se(e),M({type:201,pid:process.pid})}catch(t){e.logger.error(`WorkerBootstrapFailed`,{pid:process.pid,error:T(t)}),process.exit(1)}}var X=class{cx;timer=null;filesChanged=new Map;constructor(e){this.cx=e}async init(){let e=this.cx.options.dir;if(!c.default.existsSync(e))return this.cx.logger.warn(`Directory does not exist: ${e}`),this;let t=[],n=(e,r)=>{let i=c.default.readdirSync(e,{withFileTypes:!0});for(let a=0;a<i.length;a++){let o=i[a],s=l.default.join(e,o.name),c=l.default.join(r,o.name);if(o.isDirectory())n(s,c);else if(o.isFile()){let e=this.cx.router.register(s,c).catch(e=>{this.cx.logger.error(`Error registering file ${c}: ${e.message}`)});t.push(e)}}};return n(e,``),await Promise.all(t),this.cx.logger.info(`Initial registration complete for directory: ${e}`),this}queueUp(e,t){this.filesChanged.set(e,t),!this.timer&&(this.timer=setTimeout(async()=>{let e=[...this.filesChanged].map(([e,t])=>this.cx.router.register(e,t).catch(e=>this.cx.logger.error(`Error refreshing handlers: ${e.message}`)).finally(()=>this.filesChanged.delete(e)));await Promise.all(e),this.timer=null},this.cx.options.reloadDelay))}stopCore(){this.timer&&=(clearTimeout(this.timer),null),this.filesChanged.clear()}},ue=class extends X{watcher=null;constructor(e){super(e)}async start(){this.stop(),await this.init();let e=this.cx.options.dir;return this.watcher=m.default.watch(e,{persistent:!0,ignoreInitial:!0,usePolling:!1,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}}).on(`all`,(t,n)=>{n&&this.queueUp(n,l.default.relative(e,n))}).on(`error`,e=>{let t=e instanceof Error?e:Error(String(e));this.cx.logger.error(`Watcher error: ${t.message}`),this.cx.logger.error(`Restarting watcher...`),this.stop().start()}).on(`ready`,()=>{this.cx.logger.info(`Watcher ready and watching directory: ${e}`)}),this.cx.logger.info(`Watcher started on directory: ${e}`),this}stop(){return this.watcher&&=(this.watcher.close(),null),this.stopCore(),this}},de=class extends X{watcher=null;constructor(e){super(e)}async start(){this.stop(),await this.init();let e=this.cx.options.dir;return this.watcher=c.default.watch(e,{recursive:!0},(t,n)=>{n&&this.queueUp(l.default.join(e,n),n)}).on(`error`,e=>{this.cx.logger.error(`Watcher error: ${e.message}`),this.cx.logger.error(`Restarting watcher...`),this.stop().start()}),this.cx.logger.info(`Watcher started on directory: ${e}`),this}stop(){return this.watcher&&=(this.watcher.close(),null),this.stopCore(),this}};function Z(e){}const fe=Z;function Q(e){return!(typeof e!=`object`||!e||(fe(e),typeof e.handler!=`function`)||e.disposer!==void 0&&typeof e.disposer!=`function`||e.handlerTimeoutMs!==void 0&&typeof e.handlerTimeoutMs!=`function`)}function pe(e,t){delete require.cache[t];let n=require(t);return Q(n)||(Q(n.default)?n=n.default:$throw(`Invalid handler module '${t}', make sure it satisfies defineFluxionModule(...) helper`)),n}var me=class{cx;handlers=new Map;constructor(e){this.cx=e}makeStaticResource(e){return{handler:async(t,n,r)=>{if(t.method!==`GET`&&t.method!==`HEAD`){r.statusCode=405,r.setHeader(`Allow`,`GET, HEAD`),r.end();return}if(!c.default.existsSync(e)){r.statusCode=404,r.end(`Not Found`);return}let i=c.default.statSync(e);if(!i.isFile()){r.statusCode=404,r.end(`Not Found`);return}let a=ne[l.default.extname(e).toLowerCase()]??`application/octet-stream`;if(r.statusCode=200,r.setHeader(`Content-Type`,a),r.setHeader(`Content-Length`,String(i.size)),t.method===`HEAD`){r.end();return}return new Promise((t,n)=>{let i=c.default.createReadStream(e);i.on(`error`,n),i.on(`end`,()=>t(P)),i.pipe(r)})}}}async register(e,t){if(!c.default.existsSync(e)){let e=this.handlers.get(t)?.disposer;e&&await V(e),this.handlers.delete(t),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${t}`);return}if(!this.cx.options.include.some(e=>(0,h.minimatch)(t,e))){this.handlers.delete(t),this.cx.logger.info(`${b.yellow}Skipped ${b.reset} - ${t}`);return}if(this.cx.options.exclude.some(e=>(0,h.minimatch)(t,e))){this.handlers.delete(t),this.cx.logger.info(`${b.orange}Excluded${b.reset} - ${t}`);return}if(this.cx.options.apiInclude.some(e=>(0,h.minimatch)(t,e))){let n=pe(this.cx,e);this.handlers.set(t,n),this.cx.logger.info(`${b.green}Api ${b.reset} - ${t}`);return}this.handlers.set(t,this.makeStaticResource(t)),this.cx.logger.info(`${b.brightBlue}Static ${b.reset} - ${t}`)}getModule(e){let t=e.pathname.replace(/^[\/]+/,``).replace(/[\/]+$/,``);return this.handlers.get(t)}remove(e){this.handlers.has(e)&&(this.handlers.delete(e),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${e}`));let t=e.endsWith(`/`)?e:e+`/`;for(let e of this.handlers.keys())e.startsWith(t)&&(this.handlers.delete(e),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${e}`))}};async function $(e){let t={options:k(e)};t.logger=ee(t),t.router=new me(t),u.default.isPrimary?B(t):(t.logger=te(t.logger,process.pid),t.watcher=await new(t.options.nativeWatcher?de:ue)(t).start(),le(t))}function he(e,t=re){return typeof e==`function`?(typeof t!=`function`&&$throw(`Invalid disposer, expected a function but got ${typeof t}`),{handler:e,disposer:t}):((typeof e!=`object`||!e)&&$throw(`Invalid argument, expected a FluxionModule object or a handler function, but got ${typeof e}`),typeof e.handler!=`function`&&$throw(`Invalid FluxionModule, "handler" must be a function`),e.disposer!==void 0&&typeof e.disposer!=`function`&&$throw(`Invalid FluxionModule, "disposer" must be a function if provided`),e)}if(process.env.NODE_ENV!==`production`){globalThis.$throw=e=>{throw Error(`[fluxion error]`+e)};let e=(e,t)=>{if(e===void 0)return t;let n=Number.parseInt(e,10);return Number.isNaN(n)?t:n};$({dir:process.env.DYNAMIC_DIRECTORY??`dynamicDirectory`,host:process.env.HOST??`localhost`,port:e(process.env.PORT,9e3),metaPort:e(process.env.META_PORT,9001),reloadDelay:process.env.RELOAD_DELAY?Number.parseInt(process.env.RELOAD_DELAY,10):void 0,workerOptions:{maxWorkerCount:1}})}exports.defineFluxionModule=he,exports.fluxion=$;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("node:fs");c=s(c);let l=require("node:path");l=s(l);let u=require("node:cluster");u=s(u);let d=require("node:os");d=s(d);let f=require("node:http");f=s(f);let p=require("node:https");p=s(p);let m=require("chokidar");m=s(m);let h=require("minimatch");function g(e=new Date){return`${e.getFullYear()}.${String(e.getMonth()+1).padStart(2,`0`)}.${String(e.getDate()).padStart(2,`0`)} ${String(e.getHours()).padStart(2,`0`)}:${String(e.getMinutes()).padStart(2,`0`)}:${String(e.getSeconds()).padStart(2,`0`)}.${String(e.getMilliseconds()).padStart(3,`0`)}`}const _=JSON.stringify,v=Object.keys,y=process.env.FLUXION_COLORS!==`0`;let b;(function(e){e.reset=y?`\x1B[0m`:``,e.bold=y?`\x1B[1m`:``,e.dim=y?`\x1B[2m`:``,e.italic=y?`\x1B[3m`:``,e.underline=y?`\x1B[4m`:``,e.blink=y?`\x1B[5m`:``,e.inverse=y?`\x1B[7m`:``,e.black=y?`\x1B[30m`:``,e.red=y?`\x1B[31m`:``,e.green=y?`\x1B[32m`:``,e.yellow=y?`\x1B[33m`:``,e.blue=y?`\x1B[34m`:``,e.magenta=y?`\x1B[35m`:``,e.cyan=y?`\x1B[36m`:``,e.white=y?`\x1B[37m`:``,e.brightBlack=y?`\x1B[90m`:``,e.brightRed=y?`\x1B[91m`:``,e.brightGreen=y?`\x1B[92m`:``,e.brightYellow=y?`\x1B[93m`:``,e.brightBlue=y?`\x1B[94m`:``,e.brightMagenta=y?`\x1B[95m`:``,e.brightCyan=y?`\x1B[96m`:``,e.brightWhite=y?`\x1B[97m`:``,e.bgBlack=y?`\x1B[40m`:``,e.bgRed=y?`\x1B[41m`:``,e.bgGreen=y?`\x1B[42m`:``,e.bgYellow=y?`\x1B[43m`:``,e.bgBlue=y?`\x1B[44m`:``,e.bgMagenta=y?`\x1B[45m`:``,e.bgCyan=y?`\x1B[46m`:``,e.bgWhite=y?`\x1B[47m`:``,e.bgBrightBlack=y?`\x1B[100m`:``,e.bgBrightRed=y?`\x1B[101m`:``,e.bgBrightGreen=y?`\x1B[102m`:``,e.bgBrightYellow=y?`\x1B[103m`:``,e.bgBrightBlue=y?`\x1B[104m`:``,e.bgBrightMagenta=y?`\x1B[105m`:``,e.bgBrightCyan=y?`\x1B[106m`:``,e.bgBrightWhite=y?`\x1B[107m`:``,e.purple=y?`\x1B[38;2;225;16;248m`:``,e.orange=y?`\x1B[38;2;248;147;16m`:``,e.darkGreen=y?`\x1B[38;2;22;101;52m`:``,e.claude=y?`\x1B[38;2;217;119;87m`:``,e.deepseek=y?`\x1B[38;2;57;100;254m`:``,e.gpt=y?`\x1B[38;2;41;60;77m`:``})(b||={});const x=e=>{try{return _(e)}catch{return`[unserializable]`}},ee={INFO:`${b.cyan}INFO${b.reset}`,WARN:`${b.orange}WARN${b.reset}`,ERROR:`${b.red}ERROR${b.reset}`,SUCC:`${b.green}SUCC${b.reset}`,DEBUG:`${b.blue}DEBUG${b.reset}`,VERBOSE:`${b.purple}VERBOSE${b.reset}`},S=e=>{let{level:t,timestamp:n,event:r,message:i,...a}=e,o=`${b.darkGreen}[${n}]${b.reset}`,s=ee[t]??t,c=i??r,l=v(a).length>0?`${b.dim}${x(a)}${b.reset}`:``;console.log(`${o} ${s} ${c}${l}`)};function C(e){let t=e.options.logger;return t===void 0||t===`one-line`?S:t===`json-line`?e=>console.log(x(e)):t}function w(e){let t=C(e);return{write(e,n,r={}){let i={...r,timestamp:g(),level:e,event:n};try{t(i)}catch{}},info(e,t){this.write(`INFO`,e,t)},warn(e,t){this.write(`WARN`,e,t)},error(e,t){this.write(`ERROR`,e,t)},succ(e,t){this.write(`SUCC`,e,t)},debug(e,t){this.write(`DEBUG`,e,t)},verbose(e,t){this.write(`VERBOSE`,e,t)}}}function te(e,t){let n=`[${t}]`;return{write(t,r,i){e.write(t,`${n} ${r}`,i)},info(t,r){e.info(`${n} ${t}`,r)},warn(t,r){e.warn(`${n} ${t}`,r)},error(t,r){e.error(`${n} ${t}`,r)},succ(t,r){e.succ(`${n} ${t}`,r)},debug(t,r){e.debug(`${n} ${t}`,r)},verbose(t,r){e.verbose(`${n} ${t}`,r)}}}const T=typeof Error.isError==`function`?e=>Error.isError(e)?e.message:String(e):e=>e?.message||String(e);function E(e={}){let t=e.restartWhen??{},n=t.healthzTimeout??3e4;return n!==1/0&&(!Number.isFinite(n)||n<1e4)&&$throw(`workerOptions.restartWhen.healthzTimeout must be a finite number >= 10000 (ms) or Infinity`),{maxWorkerCount:e.maxWorkerCount??4,restartWhen:{memoryUsageGreaterThan:t.memoryUsageGreaterThan??1/0,healthzTimeout:n,uptimeGreaterThan:t.uptimeGreaterThan??1/0}}}function D(e,t){if(Buffer.isBuffer(e))return e;if(typeof e==`string`){if(!e.startsWith(`-----BEGIN`)){let n=l.default.isAbsolute(e)?e:l.default.join(t,e);if(c.default.existsSync(n))return c.default.readFileSync(n)}return Buffer.from(e)}$throw(`Certificate content must be a string or Buffer`)}function O(e,t){if(!e)return;(typeof e!=`object`||!e||Array.isArray(e))&&$throw(`FluxionOptions.https must be an object`),typeof e.key!=`string`&&$throw(`FluxionOptions.https.key must be a string`),typeof e.cert!=`string`&&$throw(`FluxionOptions.https.cert must be a string`);let n={key:D(e.key,t),cert:D(e.cert,t)};return e.ca!==void 0&&(Array.isArray(e.ca)?n.ca=e.ca.map(e=>D(e,t)):n.ca=D(e.ca,t)),n}function k(e){(typeof e!=`object`||!e||Array.isArray(e))&&$throw(`FluxionOptions must be an object`);let{dir:t=l.default.isAbsolute(e.dir)?e.dir:l.default.join(process.cwd(),e.dir),host:n,port:r,handlerTimeoutMs:i=5e3,staticResourceTimeoutMs:a=10*6e5,metaPort:o=r+1,moduleDir:s=process.cwd(),workerOptions:u={},maxRequestBytes:d=8e6,reloadDelay:f=500,include:p=[`**/*`],apiInclude:m=[`**/*.ts`],exclude:h=[`**/node_modules/**`,`**/.git/**`,`**/dist/**`,`**/build/**`,`**/.vscode/**`,`**/.idea/**`,`**/*.log`,`**/.DS_Store`,`**/coverage/**`,`**/.nyc_output/**`,`**/*.tmp`,`**/*.temp`],https:g,nativeWatcher:_=!1}=e,v=e.logger??`one-line`;return v!==`one-line`&&v!==`json-line`&&typeof v!=`function`&&$throw(`Invalid logger option, Must be 'one-line', 'json-line' or a custom logger function`),typeof t!=`string`&&$throw(`FluxionOptions.dir must be a string`),typeof s!=`string`&&$throw(`FluxionOptions.moduleDir must be a string`),typeof n!=`string`&&$throw(`FluxionOptions.host must be a string`),(!Number.isSafeInteger(i)||i<=100)&&$throw(`FluxionOptions.handlerTimeoutMs must be an integer greater than 100`),(typeof f!=`number`||f<=0||!Number.isSafeInteger(f))&&$throw(`FluxionOptions.reloadDelay must be a positive integer`),f<50&&$throw(`FluxionOptions.reloadDelay must be greater than or equal to 50`),(typeof r!=`number`||!Number.isSafeInteger(r))&&$throw(`FluxionOptions.port must be a positive integer`),(r<=1||r>65535)&&$throw(`FluxionOptions.port must be 1 ~ 65535`),(typeof o!=`number`||!Number.isSafeInteger(o))&&$throw(`FluxionOptions.metaPort must be a positive integer`),(o<=1||o>65535)&&$throw(`FluxionOptions.metaPort must be 1 ~ 65535`),o===r&&$throw(`FluxionOptions.metaPort must be different from FluxionOptions.port`),(typeof u!=`object`||!u||Array.isArray(u))&&$throw(`FluxionOptions.workerOptions must be an object`),(typeof d!=`number`||d<=0||!Number.isSafeInteger(d))&&$throw(`FluxionOptions.maxRequestBytes must be a positive integer`),c.default.existsSync(t)||c.default.mkdirSync(t,{recursive:!0}),{dir:t,host:n,port:r,handlerTimeoutMs:i,staticResourceTimeoutMs:a,reloadDelay:f,metaPort:o,moduleDir:s,workerOptions:E(u),maxRequestBytes:d,logger:v,include:p,apiInclude:m,exclude:h,nativeWatcher:_,https:O(g,s)}}const A=e=>[100].includes(e?.type),j=e=>[202,200,201,203].includes(e?.type),M=e=>process.send?.(e),N=(e,t)=>e.send(t),P=Symbol.for(`fluxion.router.StaticHandled`),F=Symbol.for(`fluxion.handlerTimeout`),ne={".css":`text/css; charset=utf-8`,".html":`text/html; charset=utf-8`,".ico":`image/x-icon`,".js":`text/javascript; charset=utf-8`,".json":`application/json; charset=utf-8`,".map":`application/json; charset=utf-8`,".png":`image/png`,".jpg":`image/jpeg`,".jpeg":`image/jpeg`,".svg":`image/svg+xml`,".txt":`text/plain; charset=utf-8`,".webp":`image/webp`},re=()=>{};function I(e,t,n=200){e.statusCode=n,e.setHeader(`Content-Type`,`application/json; charset=utf-8`),e.end(JSON.stringify(t))}function L(e,t,n=200){if(!e.writableEnded){if(e.headersSent){e.end();return}I(e,t,n)}}function R(e,t){let n=f.default.createServer((e,n)=>{let r=e.method??`GET`,i=`/`;try{i=new URL(e.url??`/`,`http://fluxion.local`).pathname}catch{I(n,{message:`Bad Request: invalid url`},400);return}if(r===`GET`&&i===`/_fluxion/healthz`){I(n,{ok:!0,role:`primary`,pid:process.pid,now:Date.now(),uptimeSeconds:Number(process.uptime().toFixed(3))});return}if(r===`GET`&&i===`/_fluxion/workers`){I(n,{ok:!0,now:Date.now(),workers:t()});return}I(n,{message:`Not Found`},404)});return n.on(`listening`,()=>{e.logger.info(`MetaApiStarted`,{pid:process.pid,host:e.options.host,port:e.options.metaPort,prefix:`/_fluxion`})}),n.on(`error`,t=>{e.logger.error(`MetaApiError`,{host:e.options.host,port:e.options.metaPort,error:T(t)}),process.exit(1)}),n.listen(e.options.metaPort,e.options.host),n}const z=e=>Number((e/1024/1024).toFixed(2)),B=6e4;function V(e){u.default.isPrimary||$throw(`createPrimary should only be called in primary process`);let{workerOptions:t}=e.options,n=t.restartWhen,r=Math.max(1,d.default.cpus().length),i=Math.max(1,Math.min(t.maxWorkerCount??Math.min(2,r),r));e.logger.info(`PrimaryStarted`,{pid:process.pid,workers:i,host:e.options.host,port:e.options.port,metaPort:e.options.metaPort});let a=new Map,o=new Map,s=e=>{let t=Date.now(),n=(o.get(e)??[]).filter(e=>t-e<B);return o.set(e,n),n.length},c=e=>{let t=Date.now(),n=(o.get(e)??[]).filter(e=>t-e<B);n.push(t),o.set(e,n)},l=e=>s(e)>=3;R(e,()=>({primaryPid:process.pid,host:e.options.host,port:e.options.port,metaPort:e.options.metaPort,uptimeSeconds:Number(process.uptime().toFixed(3)),workers:Array.from(a.entries()).map(([e,t])=>{let{instance:n}=t,r=t.lastStats;return{workerId:e,slot:t.slot,pid:t.pid??n.process.pid??null,state:t.state,restartReason:t.restartReason??null,createdAt:t.createdAt,readyAt:t.readyAt??null,connected:n.isConnected(),dead:n.isDead(),exitedAfterDisconnect:n.exitedAfterDisconnect,lastPongAt:t.lastPongAt??null,lastRttMs:t.lastRttMs??null,stats:r===void 0?null:{at:r.at,uptimeSeconds:r.uptimeSeconds,cpu:r.cpu,memory:{...r.memory,rssMb:z(r.memory.rss),heapTotalMb:z(r.memory.heapTotal),heapUsedMb:z(r.memory.heapUsed),externalMb:z(r.memory.external),arrayBuffersMb:z(r.memory.arrayBuffers)}}}})}));let f=(t,n)=>{for(let e of a.values())if(e.state===`restarting`)return;if(l(t.slot)){e.logger.warn(`WorkerRecycleSuppressed`,{slot:t.slot,pid:t.pid,reason:n,windowMs:B,max:3});return}c(t.slot),t.state=`restarting`,t.restartReason=n,e.logger.warn(`WorkerRecycling`,{slot:t.slot,pid:t.pid,reason:n}),t.instance.kill()},p=(e,t)=>{let r=z(t.memory.rss);if(r>n.memoryUsageGreaterThan){f(e,`memoryUsageGreaterThan: rss ${r}MB > ${n.memoryUsageGreaterThan}MB`);return}let i=t.uptimeSeconds*1e3;i>n.uptimeGreaterThan&&f(e,`uptimeGreaterThan: ${Math.round(i/1e3)}s > ${Math.round(n.uptimeGreaterThan/1e3)}s`)},m=e=>{for(let t of a.values()){if(t.state!==`ready`||t.lastPongAt===void 0)continue;let r=e-t.lastPongAt;r>n.healthzTimeout&&f(t,`healthzTimeout: no pong for ${Math.round(r/1e3)}s > ${Math.round(n.healthzTimeout/1e3)}s`)}},h=e=>{g(u.default.fork({WORKER_ID:String(e)}),e)},g=(t,n)=>{let r={state:`creating`,pid:t.process.pid,slot:n,createdAt:Date.now(),instance:t};a.set(t.id,r),t.on(`message`,i=>{if(j(i)){if(i.type===202){let e=Date.now()-i.sentAt;r.pid=i.pid,r.lastPongAt=Date.now(),r.lastRttMs=e;return}if(i.type===201){r.state=`ready`,r.pid=i.pid,r.readyAt=Date.now(),e.logger.info(`WorkerReady`,{workerId:t.id,slot:n,pid:i.pid});return}if(i.type===200){r.state=`created`,r.pid=i.pid,e.logger.info(`WorkerCreated`,{workerId:t.id,slot:n,pid:i.pid});return}i.type===203&&(r.pid=i.pid,r.lastStats=i.stats,r.state===`ready`&&p(r,i.stats))}}),t.on(`exit`,(n,r)=>{let i=a.get(t.id);a.delete(t.id);let o=i?.slot,s=i?.state===`restarting`,u=i?.restartReason??null;if(e.logger.warn(`WorkerExited`,{workerId:t.id,slot:o??null,pid:t.process.pid??`unknown`,code:n,signal:r??`none`,expected:s,reason:u}),o!==void 0){if(s){h(o);return}if(c(o),l(o)){e.logger.error(`WorkerRespawnSuppressed`,{slot:o,windowMs:B,max:3});return}h(o)}})};for(let e=0;e<i;e++)h(e+1);setInterval(()=>{let e=Date.now();for(let t of a.values())if(t.instance.isConnected())try{N(t.instance,{type:100,sentAt:e})}catch{}m(Date.now())},5e3).unref()}function H(e,...t){return new Promise((n,r)=>{try{let i=e(...t);i instanceof Promise?i.then(n).catch(r):n(i)}catch(e){r(e)}})}function U(e){let t=e.headersDistinct[`x-forwarded-for`];if(t){let e=t[0]?.split(`,`)[0]?.trim();if(e&&e.length>0)return e}let n=e.headersDistinct[`x-real-ip`]?.[0].trim();return n===void 0?e.socket.remoteAddress??`unknown`:n}function W(e){if(e===void 0)return!1;let t=e.toLowerCase();return t.startsWith(`text/`)||t.includes(`json`)||t.includes(`xml`)||t.includes(`x-www-form-urlencoded`)||t.includes(`javascript`)}function G(e){if(e!==void 0)try{return new URL(e,`http://fluxion.local`)}catch{return}}function K(e){let t={};for(let[n,r]of e.entries()){let e=t[n];if(e===void 0){t[n]=r;continue}if(Array.isArray(e)){e.push(r);continue}t[n]=[e,r]}return t}function q(e,t){let n=Error(`request body too large: ${e.toString()} bytes exceeds ${t.toString()} bytes`);return n.code=`REQUEST_BODY_TOO_LARGE`,n}function J(e){return Array.isArray(e)?e[0]:e}function Y(){return{exists:!1,bytes:0,truncated:!1}}function ie(e,t,n,r){return t===0?Y():W(n)?{exists:!0,value:e.toString(`utf8`),bytes:t,truncated:r}:{exists:!0,value:`<binary body: ${t} bytes>`,bytes:t,truncated:r}}async function ae(e,t,n,r=8192){if(t===`GET`||t===`HEAD`||e.readableEnded)return{rawBody:void 0,preview:Y()};let i=J(e.headers[`content-length`]),a=i===void 0?NaN:Number.parseInt(i,10);if(Number.isFinite(a)&&a>n)throw q(a,n);return new Promise((t,i)=>{let a=[],o=[],s=0,c=0,l=!1,u=!1,d=()=>{e.off(`data`,p),e.off(`end`,m),e.off(`error`,h),e.off(`aborted`,g)},f=e=>{u||(u=!0,e())},p=t=>{let u=Buffer.isBuffer(t)?t:Buffer.from(t);if(s+=u.byteLength,s>n){d(),e.resume(),f(()=>{i(q(s,n))});return}if(a.push(u),c<r){let e=r-c,t=u.subarray(0,e);o.push(t),c+=t.length,t.length<u.length&&(l=!0)}else l=!0},m=()=>{d(),f(()=>{let n=a.length>0?Buffer.concat(a):void 0;t({rawBody:n,preview:ie(o.length>0?Buffer.concat(o):Buffer.alloc(0),n?.byteLength??0,J(e.headers[`content-type`]),l)})})},h=e=>{d(),f(()=>{i(e)})},g=()=>{d(),f(()=>{i(Error(`request aborted while reading body`))})};e.on(`data`,p),e.once(`end`,m),e.once(`error`,h),e.once(`aborted`,g)})}async function oe(e,t,n){let{rawBody:r,preview:i}=await ae(e,t,n);if(r===void 0||r.byteLength===0)return{body:{},preview:i};let a=J(e.headers[`content-type`])?.toLowerCase()??``;if(a.includes(`json`)){let e=r.toString(`utf8`).trim();if(e.length===0)return{body:{},preview:i};try{let t=JSON.parse(e);return typeof t==`object`&&t&&!Array.isArray(t)?{body:t,preview:i}:{body:{value:t},preview:i}}catch{return{body:{raw:e},preview:i}}}return a.includes(`x-www-form-urlencoded`)?{body:K(new URLSearchParams(r.toString(`utf8`))),preview:i}:W(a)?{body:{raw:r.toString(`utf8`)},preview:i}:{body:{},preview:i}}function se(e){if(!e)return{};let t={},n=e.split(`;`);for(let e of n){let[n,...r]=e.split(`=`);if(!n)continue;let i=n.trim(),a=r.join(`=`).trim();t[i]=decodeURIComponent(a)}return t}function ce(e){let t=async(t,n)=>{let r=t.method??`GET`,i=U(t),a=G(t.url);if(a===void 0){L(n,{message:`Bad Request: req.url is undefined`},400);return}let o={method:r,ip:i,url:a,query:K(a.searchParams),body:{},headers:t.headers,cookie:se(t.headers.cookie)},s={exists:!1,bytes:0,truncated:!1};e.logger.info(`Req`,{method:r,ip:i,path:a.pathname});let c=performance.now();n.once(`finish`,()=>{let t={workerId:process.env.WORKER_ID??`[primary]`,method:r,ip:i,path:a.pathname,status:n.statusCode,duration:(performance.now()-c).toFixed(4)};v(o.query).length>0&&(t.query=o.query),s.exists&&(t.body=s.value,t.bodyBytes=s.bytes,t.bodyTruncated=s.truncated),e.logger.info(`Res`,t)});try{if(o.url.pathname.startsWith(`/_fluxion/`)){L(n,{message:`Meta APIs are available on port ${e.options.metaPort}`},404);return}let r=await oe(t,o.method,e.options.maxRequestBytes);o.body=r.body,s=r.preview;let i=await e.router.getModule(a);if(!i){L(n,{message:`Not Found`},404);return}if(t.method&&i.methods&&!i.methods.includes(t.method)){L(n,{message:`Method Not Allowed`},405);return}let c=i.type===0?i.handlerTimeoutMs??e.options.handlerTimeoutMs:e.options.staticResourceTimeoutMs,l=await Promise.race([H(i.handler,o,t,n),new Promise(e=>setTimeout(()=>e(F),c))]);if(l===F){e.logger.warn(`HandlerTimeout`,{method:o.method,ip:o.ip}),L(n,{message:`Handler timed out`},500);return}l!==P&&L(n,l)}catch(t){e.logger.error(`RequestFailed`,{method:o.method,ip:o.ip,path:o.url.pathname,error:T(t)}),t.code===`REQUEST_BODY_TOO_LARGE`?L(n,{message:T(t)},413):L(n,{message:T(t)},500)}},n=e.options.https?p.default.createServer({key:e.options.https.key,cert:e.options.https.cert,ca:e.options.https.ca},t):f.default.createServer(t);return n.on(`close`,()=>{e.logger.info(`ServerClosed`,{host:e.options.host,port:e.options.port})}),n.listen(e.options.port,e.options.host,()=>{e.logger.info(`ServerStarted`,{pid:process.pid,protocol:e.options.https?`https`:`http`,host:e.options.host,port:e.options.port}),e.logger.info(`DynamicDirectory`,{directory:e.options.dir})}),n.on(`error`,t=>{e.logger.error(`ServerError`,{error:T(t)})}),n}const le=()=>{let e=process.cpuUsage(),t=Date.now();setInterval(()=>{let n=Date.now(),r=Math.max(1,(n-t)*1e3),i=process.cpuUsage(e),a=Number(((i.user+i.system)/r*100).toFixed(2));e=process.cpuUsage(),t=n;let o=process.memoryUsage();M({type:203,pid:process.pid,stats:{at:n,pid:process.pid,uptimeSeconds:Number(process.uptime().toFixed(3)),cpu:{userMicros:i.user,systemMicros:i.system,percent:a},memory:{rss:o.rss,heapTotal:o.heapTotal,heapUsed:o.heapUsed,external:o.external,arrayBuffers:o.arrayBuffers}}})},2e3).unref()};function X(e){u.default.isPrimary&&$throw(`createWorker should only be called in worker process`),process.on(`message`,e=>{if(A(e)&&e.type===100){M({type:202,pid:process.pid,sentAt:e.sentAt,receivedAt:Date.now()});return}}),M({type:200,pid:process.pid}),le();try{ce(e),M({type:201,pid:process.pid})}catch(t){e.logger.error(`WorkerBootstrapFailed`,{pid:process.pid,error:T(t)}),process.exit(1)}}var Z=class{cx;timer=null;filesChanged=new Map;constructor(e){this.cx=e}async init(){let e=this.cx.options.dir;if(!c.default.existsSync(e))return this.cx.logger.warn(`Directory does not exist: ${e}`),this;let t=[],n=(e,r)=>{let i=c.default.readdirSync(e,{withFileTypes:!0});for(let a=0;a<i.length;a++){let o=i[a],s=l.default.join(e,o.name),c=l.default.join(r,o.name);if(o.isDirectory())n(s,c);else if(o.isFile()){let e=this.cx.router.register(s,c).catch(e=>{this.cx.logger.error(`Error registering file ${c}: ${e.message}`)});t.push(e)}}};return n(e,``),await Promise.all(t),this.cx.logger.info(`Initial registration complete for directory: ${e}`),this}queueUp(e,t){this.filesChanged.set(e,t),!this.timer&&(this.timer=setTimeout(async()=>{let e=[...this.filesChanged].map(([e,t])=>this.cx.router.register(e,t).catch(e=>this.cx.logger.error(`Error refreshing handlers: ${e.message}`)).finally(()=>this.filesChanged.delete(e)));await Promise.all(e),this.timer=null},this.cx.options.reloadDelay))}stopCore(){this.timer&&=(clearTimeout(this.timer),null),this.filesChanged.clear()}},ue=class extends Z{watcher=null;constructor(e){super(e)}async start(){this.stop(),await this.init();let e=this.cx.options.dir;return this.watcher=m.default.watch(e,{persistent:!0,ignoreInitial:!0,usePolling:!1,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}}).on(`all`,(t,n)=>{n&&this.queueUp(n,l.default.relative(e,n))}).on(`error`,e=>{let t=e instanceof Error?e:Error(String(e));this.cx.logger.error(`Watcher error: ${t.message}`),this.cx.logger.error(`Restarting watcher...`),this.start()}).on(`ready`,()=>{this.cx.logger.info(`Watcher ready and watching directory: ${e}`)}),this.cx.logger.info(`Watcher started on directory: ${e}`),this}stop(){return this.watcher&&=(this.watcher.close(),null),this.stopCore(),this}},de=class extends Z{watcher=null;constructor(e){super(e)}async start(){this.stop(),await this.init();let e=this.cx.options.dir;return this.watcher=c.default.watch(e,{recursive:!0},(t,n)=>{n&&this.queueUp(l.default.join(e,n),n)}).on(`error`,e=>{this.cx.logger.error(`Watcher error: ${e.message}`),this.cx.logger.error(`Restarting watcher...`),this.start()}),this.cx.logger.info(`Watcher started on directory: ${e}`),this}stop(){return this.watcher&&=(this.watcher.close(),null),this.stopCore(),this}};function fe(e){}const pe=fe;function Q(e){if(typeof e!=`object`||!e||(pe(e),typeof e.handler!=`function`)||e.disposer!==void 0&&typeof e.disposer!=`function`)return!1;let t=e.handlerTimeoutMs;return!(t!==void 0&&(!Number.isSafeInteger(t)||t<100))}function me(e,t){delete require.cache[t];let n=require(t);return Q(n)||(Q(n.default)?n=n.default:$throw(`Invalid handler module '${t}', make sure it satisfies defineFluxionModule(...) helper`)),{...n,type:0}}var he=class{cx;handlers=new Map;constructor(e){this.cx=e}makeStaticResource(e){return{type:1,handler:async(t,n,r)=>{if(t.method!==`GET`&&t.method!==`HEAD`){r.statusCode=405,r.setHeader(`Allow`,`GET, HEAD`),r.end();return}if(!c.default.existsSync(e)){r.statusCode=404,r.end(`Not Found`);return}let i=c.default.statSync(e);if(!i.isFile()){r.statusCode=404,r.end(`Not Found`);return}let a=ne[l.default.extname(e).toLowerCase()]??`application/octet-stream`;if(r.statusCode=200,r.setHeader(`Content-Type`,a),r.setHeader(`Content-Length`,String(i.size)),t.method===`HEAD`){r.end();return}return new Promise((t,n)=>{let i=c.default.createReadStream(e);i.on(`error`,n),i.on(`end`,()=>t(P)),i.pipe(r)})}}}async register(e,t){let n=this.handlers.get(t)?.disposer;if(n&&await H(n),!c.default.existsSync(e)){this.handlers.delete(t),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${t}`);return}if(!this.cx.options.include.some(e=>(0,h.minimatch)(t,e))){this.handlers.delete(t),this.cx.logger.info(`${b.yellow}Skipped ${b.reset} - ${t}`);return}if(this.cx.options.exclude.some(e=>(0,h.minimatch)(t,e))){this.handlers.delete(t),this.cx.logger.info(`${b.orange}Excluded${b.reset} - ${t}`);return}if(this.cx.options.apiInclude.some(e=>(0,h.minimatch)(t,e))){let n=me(this.cx,e);this.handlers.set(t,n),this.cx.logger.info(`${b.green}Api ${b.reset} - ${t}`);return}this.handlers.set(t,this.makeStaticResource(t)),this.cx.logger.info(`${b.brightBlue}Static ${b.reset} - ${t}`)}getModule(e){let t=e.pathname.replace(/^[/]+/,``).replace(/[/]+$/,``);return this.handlers.get(t)}remove(e){this.handlers.has(e)&&(this.handlers.delete(e),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${e}`));let t=e.endsWith(`/`)?e:e+`/`;for(let e of this.handlers.keys())e.startsWith(t)&&(this.handlers.delete(e),this.cx.logger.info(`${b.red}Deleted ${b.reset} - ${e}`))}};async function $(e){let t={options:k(e)};t.logger=w(t),t.router=new he(t),u.default.isPrimary?V(t):(t.logger=te(t.logger,process.pid),t.watcher=await new(t.options.nativeWatcher?de:ue)(t).start(),X(t))}function ge(e,t=re){return typeof e==`function`?(typeof t!=`function`&&$throw(`Invalid disposer, expected a function but got ${typeof t}`),{handler:e,disposer:t}):((typeof e!=`object`||!e)&&$throw(`Invalid argument, expected a FluxionModule object or a handler function, but got ${typeof e}`),typeof e.handler!=`function`&&$throw(`Invalid FluxionModule, "handler" must be a function`),e.disposer!==void 0&&typeof e.disposer!=`function`&&$throw(`Invalid FluxionModule, "disposer" must be a function if provided`),e.methods!==void 0&&!Array.isArray(e.methods)&&$throw(`Invalid FluxionModule, "methods" must be an array of strings if provided`),e)}if(process.env.NODE_ENV!==`production`){globalThis.$throw=e=>{throw Error(`[fluxion error]`+e)};let e=(e,t)=>{if(e===void 0)return t;let n=Number.parseInt(e,10);return Number.isNaN(n)?t:n};$({dir:process.env.DYNAMIC_DIRECTORY??`dynamicDirectory`,host:process.env.HOST??`localhost`,port:e(process.env.PORT,9e3),metaPort:e(process.env.META_PORT,9001),reloadDelay:process.env.RELOAD_DELAY?Number.parseInt(process.env.RELOAD_DELAY,10):void 0,workerOptions:{maxWorkerCount:4}})}exports.defineFluxionModule=ge,exports.fluxion=$;
2
2
  //# sourceMappingURL=index.cjs.map