@forinda/kickjs 10.0.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-CvEm8Gpp.d.mts → adapter-ClEh-Oos.d.mts} +2 -2
- package/dist/{adapter-CvEm8Gpp.d.mts.map → adapter-ClEh-Oos.d.mts.map} +1 -1
- package/dist/{application-D21ZVAWG.mjs → application-Dv514EeR.mjs} +3 -3
- package/dist/{application-D21ZVAWG.mjs.map → application-Dv514EeR.mjs.map} +1 -1
- package/dist/{application-1Hln746x.d.mts → application-oIPzgjfk.d.mts} +20 -9
- package/dist/application-oIPzgjfk.d.mts.map +1 -0
- package/dist/bootstrap-BdyBlBMe.mjs +12 -0
- package/dist/bootstrap-BdyBlBMe.mjs.map +1 -0
- package/dist/{client-ip-BkLX8wXf.mjs → client-ip-BbLDp03S.mjs} +2 -2
- package/dist/{client-ip-BkLX8wXf.mjs.map → client-ip-BbLDp03S.mjs.map} +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-rXsEm1rn.mjs → config-CJiGeOca.mjs} +3 -3
- package/dist/{config-rXsEm1rn.mjs.map → config-CJiGeOca.mjs.map} +1 -1
- package/dist/{container-CsCdem73.mjs → container-CS9v9UFU.mjs} +3 -3
- package/dist/{container-CsCdem73.mjs.map → container-CS9v9UFU.mjs.map} +1 -1
- package/dist/{context-Bd02q5Sm.mjs → context-CtL9RxXp.mjs} +3 -3
- package/dist/{context-Bd02q5Sm.mjs.map → context-CtL9RxXp.mjs.map} +1 -1
- package/dist/{context-errors-BUdMBVWO.mjs → context-errors-7C5TBYn7.mjs} +2 -2
- package/dist/{context-errors-BUdMBVWO.mjs.map → context-errors-7C5TBYn7.mjs.map} +1 -1
- package/dist/{contributor-runner-CZTAFvla.mjs → contributor-runner-DBpRR5dl.mjs} +3 -3
- package/dist/{contributor-runner-CZTAFvla.mjs.map → contributor-runner-DBpRR5dl.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-B0mErNE1.mjs → core-B0JmdBvX.mjs} +3 -3
- package/dist/{core-B0mErNE1.mjs.map → core-B0JmdBvX.mjs.map} +1 -1
- package/dist/{csrf-BnetlmWY.mjs → csrf--1t4qDov.mjs} +3 -3
- package/dist/{csrf-BnetlmWY.mjs.map → csrf--1t4qDov.mjs.map} +1 -1
- package/dist/{decorate-B7-z930n.mjs → decorate-BHkdkV4l.mjs} +1 -1
- package/dist/{define-adapter-B4D_Rci1.mjs → define-adapter-BLKlUbSL.mjs} +2 -2
- package/dist/{define-adapter-B4D_Rci1.mjs.map → define-adapter-BLKlUbSL.mjs.map} +1 -1
- package/dist/{disposables-CgQqGmPL.mjs → disposables-CuY2S3bw.mjs} +2 -2
- package/dist/{disposables-CgQqGmPL.mjs.map → disposables-CuY2S3bw.mjs.map} +1 -1
- package/dist/{env-ngWmBB9d.mjs → env-BwzF9MkZ.mjs} +3 -3
- package/dist/{env-ngWmBB9d.mjs.map → env-BwzF9MkZ.mjs.map} +1 -1
- package/dist/{error-handler-Chw07m-M.d.mts → error-handler-BB9wAElm.d.mts} +2 -2
- package/dist/{error-handler-Chw07m-M.d.mts.map → error-handler-BB9wAElm.d.mts.map} +1 -1
- package/dist/{handler-CyRoAqNt.mjs → handler-zKfIgNo4.mjs} +3 -3
- package/dist/{handler-CyRoAqNt.mjs.map → handler-zKfIgNo4.mjs.map} +1 -1
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +1 -1
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.d.mts +1 -1
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/session.mjs.map +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +1 -1
- package/dist/http/runtimes/fastify.mjs +2 -2
- package/dist/http/runtimes/h3-web.mjs +1 -1
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.d.mts.map +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{jobs-Ccs6PkmC.mjs → jobs-BQ4t3TAb.mjs} +3 -3
- package/dist/{jobs-Ccs6PkmC.mjs.map → jobs-BQ4t3TAb.mjs.map} +1 -1
- package/dist/{kv-stores-BYeO77zK.d.mts → kv-stores-Co5k2PYA.d.mts} +3 -3
- package/dist/{kv-stores-BYeO77zK.d.mts.map → kv-stores-Co5k2PYA.d.mts.map} +1 -1
- package/dist/{kv-stores-9IkW7Rjj.mjs → kv-stores-DsfVy2fk.mjs} +3 -3
- package/dist/{kv-stores-9IkW7Rjj.mjs.map → kv-stores-DsfVy2fk.mjs.map} +1 -1
- package/dist/{metadata-DCWKG472.mjs → metadata-zvd6AWkE.mjs} +2 -2
- package/dist/{metadata-DCWKG472.mjs.map → metadata-zvd6AWkE.mjs.map} +1 -1
- package/dist/{module-registry-Cl12vSJd.mjs → module-registry-Daf_P2oD.mjs} +2 -2
- package/dist/{module-registry-Cl12vSJd.mjs.map → module-registry-Daf_P2oD.mjs.map} +1 -1
- package/dist/{observers-9FRE8VCB.mjs → observers-DZgs48UM.mjs} +2 -2
- package/dist/{observers-9FRE8VCB.mjs.map → observers-DZgs48UM.mjs.map} +1 -1
- package/dist/{query-DUkboS0j.mjs → query-B_k9qn0R.mjs} +2 -2
- package/dist/{query-DUkboS0j.mjs.map → query-B_k9qn0R.mjs.map} +1 -1
- package/dist/{reply-DElkObMy.mjs → reply-BDK9xt7Z.mjs} +3 -3
- package/dist/{reply-DElkObMy.mjs.map → reply-BDK9xt7Z.mjs.map} +1 -1
- package/dist/{respond-Dcj1NF_X.mjs → respond-DFRj2-7q.mjs} +3 -3
- package/dist/{respond-Dcj1NF_X.mjs.map → respond-DFRj2-7q.mjs.map} +1 -1
- package/dist/{route-flag-jd8PDm20.mjs → route-flag-BWuvJsGZ.mjs} +3 -3
- package/dist/{route-flag-jd8PDm20.mjs.map → route-flag-BWuvJsGZ.mjs.map} +1 -1
- package/dist/{route-policy-DlunIjGc.mjs → route-policy-B3vM6bSf.mjs} +2 -2
- package/dist/{route-policy-DlunIjGc.mjs.map → route-policy-B3vM6bSf.mjs.map} +1 -1
- package/dist/{router-builder-DZsCjpeV.mjs → router-builder-JvMTp50k.mjs} +3 -3
- package/dist/{router-builder-DZsCjpeV.mjs.map → router-builder-JvMTp50k.mjs.map} +1 -1
- package/dist/{router-builder-D_SkWTfE.d.mts → router-builder-zWuVIOmd.d.mts} +2 -2
- package/dist/{router-builder-D_SkWTfE.d.mts.map → router-builder-zWuVIOmd.d.mts.map} +1 -1
- package/dist/{session-4Tb6q0Ny.d.mts → session-B4hOZj90.d.mts} +13 -4
- package/dist/session-B4hOZj90.d.mts.map +1 -0
- package/dist/{upload-DL_jrBGk.d.mts → upload-CiMtdXWD.d.mts} +2 -2
- package/dist/{upload-DL_jrBGk.d.mts.map → upload-CiMtdXWD.d.mts.map} +1 -1
- package/dist/{upload-config-BiL1QXkT.mjs → upload-config-Dz2INCaa.mjs} +2 -2
- package/dist/{upload-config-BiL1QXkT.mjs.map → upload-config-Dz2INCaa.mjs.map} +1 -1
- package/dist/web.d.mts +2 -2
- package/dist/web.mjs +2 -2
- package/package.json +1 -1
- package/dist/application-1Hln746x.d.mts.map +0 -1
- package/dist/bootstrap-2hi7Zhs8.mjs +0 -12
- package/dist/bootstrap-2hi7Zhs8.mjs.map +0 -1
- package/dist/session-4Tb6q0Ny.d.mts.map +0 -1
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs v10.0.0
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) Felix Orinda
|
|
5
|
-
*
|
|
6
|
-
* This source code is licensed under the MIT license found in the
|
|
7
|
-
* LICENSE file in the root directory of this source tree.
|
|
8
|
-
*
|
|
9
|
-
* @license MIT
|
|
10
|
-
*/
|
|
11
|
-
import{o as reloadEnv}from"./env-ngWmBB9d.mjs";import{t as Container}from"./container-CsCdem73.mjs";import{createLogger}from"./core/logger.mjs";import{t as Application}from"./application-D21ZVAWG.mjs";import{r as reportError}from"./observers-9FRE8VCB.mjs";import{listCronJobs,runCronJob}from"./core/cron.mjs";import{reactive,ref}from"./core/reactivity.mjs";import"./core-B0mErNE1.mjs";import{t as defineAdapter}from"./define-adapter-B4D_Rci1.mjs";import{createRequire}from"node:module";import cluster from"node:cluster";import os from"node:os";const log$2=createLogger(`KickCronAdapter`),peerRequire=createRequire(import.meta.url),CRON_WORKER_ENV=`KICK_CRON_WORKER`;function loadCroner(){try{return peerRequire(`croner`).Cron}catch{return}}function isCronProcess(){return!cluster.isWorker||process.env.KICK_CRON_WORKER===`1`}const KickCronAdapter=defineAdapter({name:`KickCronAdapter`,defaults:{enabled:!0},build(options){let schedules=[],inFlight=/* @__PURE__ */ new Set,jobs=[];return{afterStart({container}){if(options.enabled===!1||!isCronProcess()||(jobs=listCronJobs(container),jobs.length===0))return;let Croner=loadCroner();if(!Croner){log$2.warn(`${jobs.length} @Cron job(s) found but "croner" is not installed, so none will run. Install it: pnpm add croner`);return}let run=(job,trigger)=>{let p=runCronJob(job,container,trigger).catch(()=>{});inFlight.add(p),p.finally(()=>inFlight.delete(p))};for(let job of jobs)schedules.push(new Croner(job.expression,job.timezone?{timezone:job.timezone}:{},()=>run(job,`schedule`))),job.runOnInit&&run(job,`init`);log$2.info(`Scheduled ${jobs.length} @Cron job(s): ${jobs.map(j=>j.name).join(`, `)}`)},async shutdown(){for(let schedule of schedules.splice(0))schedule.stop();await Promise.all(inFlight)},introspect(){return{protocolVersion:1,name:`KickCronAdapter`,kind:`adapter`,state:{jobs:jobs.map(j=>({name:j.name,expression:j.expression,timezone:j.timezone,description:j.description}))}}}}}}),log$1=createLogger(`Cluster`);function isClusterPrimary(){return cluster.isPrimary}function setupClusterPrimary(opts){let numWorkers=opts.workers??os.cpus().length,gracefulTimeout=opts.gracefulShutdownTimeout??5e3,restartDelay=opts.restartDelay??1e3,maxFailures=opts.maxFailures??5,restartWindow=opts.restartWindow??3e4,startupTimeouts=/* @__PURE__ */ new Map,state=reactive({failureTimestamps:[],shuttingDown:!1}),activeWorkers=ref(0);log$1.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`);let cronWorkerId,forkWorker=()=>{let takesCron=cronWorkerId===void 0,worker=cluster.fork(takesCron?{[CRON_WORKER_ENV]:`1`}:void 0);takesCron&&(cronWorkerId=worker.id)};for(let i=0;i<numWorkers;i++)forkWorker();cluster.on(`fork`,worker=>{let timeout=setTimeout(()=>{log$1.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`),worker.kill()},1e4);startupTimeouts.set(worker.id,timeout),activeWorkers.value++,log$1.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)}),cluster.on(`listening`,(worker,address)=>{let timeout=startupTimeouts.get(worker.id);timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),log$1.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)}),cluster.on(`online`,worker=>{log$1.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)}),cluster.on(`exit`,(worker,code,signal)=>{let timeout=startupTimeouts.get(worker.id);if(timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),activeWorkers.value--,worker.id===cronWorkerId&&(cronWorkerId=void 0),worker.exitedAfterDisconnect){log$1.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`);return}let now=Date.now();for(;state.failureTimestamps.length>0&&now-state.failureTimestamps[0]>=restartWindow;)state.failureTimestamps.shift();state.failureTimestamps.push(now),state.failureTimestamps.length>maxFailures?(log$1.error(`${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`),activeWorkers.value===0&&(log$1.error(`All workers have crashed, exiting primary`),process.exit(1))):(log$1.warn(`Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`),setTimeout(()=>{activeWorkers.value<numWorkers&&forkWorker()},restartDelay))}),cluster.on(`disconnect`,worker=>{log$1.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)});for(let signal of[`SIGTERM`,`SIGINT`])process.on(signal,()=>{state.shuttingDown||(state.shuttingDown=!0,log$1.info(`Primary received ${signal}, disconnecting workers...`),cluster.disconnect(()=>{log$1.info(`All workers disconnected, exiting primary`),process.exit(0)}),setTimeout(()=>{log$1.warn(`Graceful shutdown timed out, forcing exit`),process.exit(1)},gracefulTimeout).unref())})}function tryReloadEnv(){try{reloadEnv()}catch{}}const log=createLogger(`Process`);async function bootstrap(options){let isViteDev=!!globalThis.__kickjs_httpServer;if(options.cluster&&isViteDev&&log.warn(`Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).`),options.cluster&&cluster.isPrimary&&!isViteDev)return setupClusterPrimary(typeof options.cluster==`object`?options.cluster:{}),new Application(options);let g=globalThis,processHooks=options.processHooks??`auto`;if(!g.__kickBootstrapped){if(processHooks!==`manual`&&(process.on(`uncaughtException`,err=>{log.error(err,`Uncaught exception`),reportError(err,{source:`uncaught`}),process.exitCode=1}),process.on(`unhandledRejection`,reason=>{log.error(reason,`Unhandled rejection`),reportError(reason,{source:`unhandled-rejection`}),process.exitCode=1})),processHooks===`auto`&&!g.__kickjs_httpServer)for(let signal of[`SIGINT`,`SIGTERM`])process.on(signal,async()=>{log.info(`Received ${signal}, shutting down...`),g.__app&&await g.__app.shutdown(),process.exit(0)});g.__kickBootstrapped=!0}if(g.__app){log.debug(`HMR rebuild triggered`);try{await g.__app.shutdown({closeServer:!1})}catch(err){log.error(err,`HMR teardown of the previous app failed`)}tryReloadEnv(),Container.reset();let freshApp=new Application(options);return g.__app=freshApp,g.__kickjs_container=freshApp.getContainer(),await freshApp.start(),log.debug(`HMR rebuild complete`),freshApp}let app=new Application(options);return g.__app=app,g.__kickjs_container=app.getContainer(),process.env.KICK_TINKER?(await app.registerOnly(),app):(await app.start(),app)}export{KickCronAdapter as i,isClusterPrimary as n,CRON_WORKER_ENV as r,bootstrap as t};
|
|
12
|
-
//# sourceMappingURL=bootstrap-2hi7Zhs8.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap-2hi7Zhs8.mjs","names":["log","log","\"KICK_CRON_WORKER\""],"sources":["../src/http/cron-adapter.ts","../src/http/cluster.ts","../src/http/bootstrap.ts"],"sourcesContent":["/**\n * `KickCronAdapter()` — runs `@Cron` jobs on the Node server.\n *\n * Named with the `Kick` prefix (adapter name and logger too) so it never\n * collides with an app's own `CronAdapter` — which is also why it is opt-in.\n *\n * Opt-in: `@Cron` only records metadata, and apps that schedule jobs with\n * their own adapter keep doing so — adding this one to such an app would run\n * every job twice. Register it once:\n *\n * ```ts\n * bootstrap({ modules, adapters: [KickCronAdapter()] })\n * ```\n *\n * Timing comes from the optional peer `croner` (`pnpm add croner`), loaded\n * only when there is a job to schedule. Each tick resolves the job's class\n * from the container (the same singletons a request gets) and calls\n * `runCronJob`, which applies `enabled` / `overlap` and reports failures to\n * the error observers.\n *\n * Several processes: in `bootstrap({ cluster })`, only the worker the primary\n * marks with `KICK_CRON_WORKER=1` schedules, so a job runs once per cluster.\n * Several separate instances (replicas behind a load balancer) each schedule\n * every job — turn it off on all but one (`KickCronAdapter({ enabled: false })`),\n * or have the job take a lock.\n *\n * @module @forinda/kickjs/http/cron-adapter\n */\nimport cluster from 'node:cluster'\nimport { createRequire } from 'node:module'\nimport { defineAdapter } from '../core/define-adapter'\nimport { createLogger } from '../core/logger'\nimport { listCronJobs, runCronJob, type CronJob } from '../core/cron'\nimport type { Container } from '../core/container'\n\nconst log = createLogger('KickCronAdapter')\nconst peerRequire = createRequire(import.meta.url)\n\n/** Set by the cluster primary on the one worker that runs `KickCronAdapter` jobs. */\nexport const CRON_WORKER_ENV = 'KICK_CRON_WORKER'\n\nexport interface CronAdapterOptions {\n /** Off for processes that should run no jobs (replicas, one-off scripts). Default `true`. */\n enabled?: boolean\n}\n\ninterface Schedule {\n stop(): void\n}\n\ntype CronerCtor = new (pattern: string, options: { timezone?: string }, fn: () => void) => Schedule\n\nfunction loadCroner(): CronerCtor | undefined {\n try {\n return (peerRequire('croner') as { Cron: CronerCtor }).Cron\n } catch {\n return undefined\n }\n}\n\n/** Whether this process should schedule jobs — one worker per cluster. */\nfunction isCronProcess(): boolean {\n return !cluster.isWorker || process.env[CRON_WORKER_ENV] === '1'\n}\n\nexport const KickCronAdapter = defineAdapter<CronAdapterOptions>({\n name: 'KickCronAdapter',\n defaults: { enabled: true },\n build(options) {\n const schedules: Schedule[] = []\n const inFlight = new Set<Promise<unknown>>()\n let jobs: CronJob[] = []\n\n return {\n afterStart({ container }) {\n if (options.enabled === false || !isCronProcess()) return\n jobs = listCronJobs(container)\n if (jobs.length === 0) return\n\n const Croner = loadCroner()\n if (!Croner) {\n log.warn(\n `${jobs.length} @Cron job(s) found but \"croner\" is not installed, so none will run. ` +\n 'Install it: pnpm add croner',\n )\n return\n }\n\n const run = (job: CronJob, trigger: 'schedule' | 'init') => {\n // Failures are already logged and reported by runCronJob.\n const p = runCronJob(job, container as Container, trigger).catch(() => {})\n inFlight.add(p)\n void p.finally(() => inFlight.delete(p))\n }\n\n for (const job of jobs) {\n schedules.push(\n new Croner(\n job.expression,\n // No croner `name`: croner keeps names in a process-wide registry\n // and throws on a duplicate.\n job.timezone ? { timezone: job.timezone } : {},\n () => run(job, 'schedule'),\n ),\n )\n if (job.runOnInit) run(job, 'init')\n }\n log.info(`Scheduled ${jobs.length} @Cron job(s): ${jobs.map((j) => j.name).join(', ')}`)\n },\n\n /**\n * Stops the schedules, then waits for running jobs so they don't lose\n * their dependencies mid-run. Also runs on every HMR reload, so a reload\n * never leaves a second copy ticking.\n */\n async shutdown() {\n for (const schedule of schedules.splice(0)) schedule.stop()\n await Promise.all(inFlight)\n },\n\n introspect() {\n return {\n protocolVersion: 1,\n name: 'KickCronAdapter',\n kind: 'adapter',\n state: {\n jobs: jobs.map((j) => ({\n name: j.name,\n expression: j.expression,\n timezone: j.timezone,\n description: j.description,\n })),\n },\n } as never\n },\n }\n },\n})\n","import cluster from 'node:cluster'\nimport { CRON_WORKER_ENV } from './cron-adapter'\nimport os from 'node:os'\nimport { createLogger } from '../core'\nimport { reactive, ref } from '../core/reactivity'\n\nconst log = createLogger('Cluster')\n\nexport interface ClusterOptions {\n /** Number of worker processes (default: os.cpus().length) */\n workers?: number\n /** Delay before forcefully killing a worker during shutdown (ms), default: 5000 */\n gracefulShutdownTimeout?: number\n /** Delay between worker restarts (ms), default: 1000 */\n restartDelay?: number\n /** Max failures in restartWindow before refusing to restart, default: 5 */\n maxFailures?: number\n /** Time window for counting failures (ms), default: 30000 */\n restartWindow?: number\n}\n\n/** Returns true if the current process is the cluster primary */\nexport function isClusterPrimary(): boolean {\n return cluster.isPrimary\n}\n\n/**\n * Fork worker processes and manage the cluster lifecycle.\n *\n * The primary process forks `workers` child processes. Each worker\n * shares the same port via Node's built-in `cluster` module (OS\n * round-robin load balancing).\n *\n * Features:\n * - Graceful shutdown with timeout fallback to forceful kill\n * - Auto-restart dead workers (respects voluntary disconnects)\n * - Startup timeout detection (stuck workers)\n * - Crash loop prevention (backoff on rapid failures)\n * - Comprehensive lifecycle logging\n */\nexport function setupClusterPrimary(opts: ClusterOptions): void {\n const numWorkers = opts.workers ?? os.cpus().length\n const gracefulTimeout = opts.gracefulShutdownTimeout ?? 5000\n const restartDelay = opts.restartDelay ?? 1000\n const maxFailures = opts.maxFailures ?? 5\n const restartWindow = opts.restartWindow ?? 30000\n\n const startupTimeouts = new Map<number, NodeJS.Timeout>()\n const state = reactive({\n failureTimestamps: [] as number[],\n shuttingDown: false,\n })\n const activeWorkers = ref(0)\n\n log.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`)\n\n // Exactly one worker runs `KickCronAdapter` jobs, so a job fires once per\n // cluster rather than once per worker. The primary marks it with\n // KICK_CRON_WORKER=1; when it dies, the next fork inherits the role.\n let cronWorkerId: number | undefined\n const forkWorker = (): void => {\n const takesCron = cronWorkerId === undefined\n const worker = cluster.fork(takesCron ? { [CRON_WORKER_ENV]: '1' } : undefined)\n if (takesCron) cronWorkerId = worker.id\n }\n\n // Initial fork\n for (let i = 0; i < numWorkers; i++) {\n forkWorker()\n }\n\n // Detect stuck workers (no 'listening' event within timeout)\n cluster.on('fork', (worker) => {\n const timeout = setTimeout(() => {\n log.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`)\n worker.kill()\n }, 10000)\n\n startupTimeouts.set(worker.id, timeout)\n activeWorkers.value++\n log.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)\n })\n\n // Clear startup timeout when worker is ready\n cluster.on('listening', (worker, address) => {\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n log.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)\n })\n\n // Log when worker comes online\n cluster.on('online', (worker) => {\n log.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)\n })\n\n // Auto-restart dead workers (unless intentionally disconnected)\n cluster.on('exit', (worker, code, signal) => {\n // Clear startup timeout if still pending\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n\n // Don't restart if worker was intentionally disconnected\n activeWorkers.value--\n if (worker.id === cronWorkerId) cronWorkerId = undefined\n\n if (worker.exitedAfterDisconnect) {\n log.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`)\n return\n }\n\n // Track crash history globally (worker IDs change on restart)\n const now = Date.now()\n // Remove timestamps outside the window, then add the current failure\n while (\n state.failureTimestamps.length > 0 &&\n now - state.failureTimestamps[0] >= restartWindow\n ) {\n state.failureTimestamps.shift()\n }\n state.failureTimestamps.push(now)\n\n // Prevent restart loops\n if (state.failureTimestamps.length > maxFailures) {\n log.error(\n `${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`,\n )\n // Optionally exit the primary if too many workers are crashing\n if (activeWorkers.value === 0) {\n log.error('All workers have crashed, exiting primary')\n process.exit(1)\n }\n return\n }\n\n log.warn(\n `Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`,\n )\n\n setTimeout(() => {\n // Only restart if we still need more workers\n if (activeWorkers.value < numWorkers) {\n forkWorker()\n }\n }, restartDelay)\n })\n\n // Log when worker disconnects (can happen before exit)\n cluster.on('disconnect', (worker) => {\n log.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)\n })\n\n // Forward shutdown signals to workers (disconnect so exitedAfterDisconnect is set)\n for (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.on(signal, () => {\n if (state.shuttingDown) return\n state.shuttingDown = true\n log.info(`Primary received ${signal}, disconnecting workers...`)\n cluster.disconnect(() => {\n log.info('All workers disconnected, exiting primary')\n process.exit(0)\n })\n // Force kill after graceful timeout\n setTimeout(() => {\n log.warn('Graceful shutdown timed out, forcing exit')\n process.exit(1)\n }, gracefulTimeout).unref()\n })\n }\n}\n","import cluster from 'node:cluster'\nimport { reportError } from '../core/observers'\nimport { createLogger, Container } from '../core'\nimport { reloadEnv } from '../config/env'\nimport { Application, type ApplicationOptions } from './application'\nimport { setupClusterPrimary, type ClusterOptions } from './cluster'\n\n/** Reload env from .env file (HMR rebuild) — config now lives in-tree. */\nfunction tryReloadEnv(): void {\n try {\n reloadEnv()\n } catch {\n // Best-effort: never crash bootstrap because env reload failed.\n }\n}\n\nconst log = createLogger('Process')\n\n/**\n * Bootstrap a KickJS application.\n *\n * Creates the Express app, registers modules and adapters, and starts\n * the HTTP server. In dev mode (`kick dev`), the Vite plugin manages\n * the server and HMR — source changes are picked up instantly without\n * losing database connections or WebSocket state.\n *\n * @returns The {@link Application} instance.\n *\n * @example\n * ```ts\n * // src/index.ts\n * import 'reflect-metadata'\n * import { bootstrap } from '@forinda/kickjs'\n * import { modules } from './modules'\n *\n * export const app = await bootstrap({ modules })\n * ```\n */\nexport async function bootstrap(options: ApplicationOptions): Promise<Application> {\n // ── Cluster mode ─────────────────────────────────────────────────────\n // In cluster mode the primary process forks workers and never runs the\n // Express application itself. Only workers proceed past this block.\n // Cluster is disabled in Vite dev mode — Vite owns the HTTP server and\n // forked workers would each create their own Vite instance on separate ports.\n const isViteDev = !!(globalThis as any).__kickjs_httpServer\n if (options.cluster && isViteDev) {\n log.warn(\n 'Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).',\n )\n }\n if (options.cluster && cluster.isPrimary && !isViteDev) {\n const clusterOpts: ClusterOptions = typeof options.cluster === 'object' ? options.cluster : {}\n setupClusterPrimary(clusterOpts)\n\n // Return a placeholder — the primary never serves HTTP traffic.\n // The Application is created only so callers get a valid reference.\n return new Application(options)\n }\n\n // Internal: globalThis stores the app instance and Vite's HTTP server\n // reference across HMR rebuilds. When __kickjs_httpServer is set, the\n // Vite plugin owns the port and bootstrap() skips server creation.\n // On subsequent calls (HMR), the Express handler is swapped without\n // restarting the server — preserving DB connections and port bindings.\n const g = globalThis as any\n\n // ── Global error handlers + signal hooks ─────────────────────────────\n // `processHooks` controls whether KickJS registers its own\n // process-level handlers. Defaults to `'auto'` — register everything.\n // Adopters that bring their own observability stack (OpenTelemetry SDK,\n // Sentry, custom shutdown choreographer) often register their own\n // SIGINT/SIGTERM handlers and need to opt out of ours so the two don't\n // race to call `process.exit(0)`. Three modes:\n // - 'auto' (default): register uncaughtException/unhandledRejection\n // loggers + SIGINT/SIGTERM → `app.shutdown()` → `process.exit(0)`.\n // - 'errors-only': register the error loggers; skip signal handlers.\n // Use this when you have your own shutdown choreographer that calls\n // `app.shutdown()` itself.\n // - 'manual': skip everything. Adopter is responsible for both error\n // logging and signal-driven shutdown.\n const processHooks = options.processHooks ?? 'auto'\n\n if (!g.__kickBootstrapped) {\n if (processHooks !== 'manual') {\n // A listener here replaces Node's own handling, which exits with code\n // 1. Keep the failure in the exit code: a boot that throws — a top-level\n // `await bootstrap()` rejecting — must not end the process with 0, or a\n // deploy reads the crash as success.\n process.on('uncaughtException', (err) => {\n log.error(err, 'Uncaught exception')\n reportError(err, { source: 'uncaught' })\n process.exitCode = 1\n })\n\n process.on('unhandledRejection', (reason) => {\n log.error(reason as any, 'Unhandled rejection')\n reportError(reason, { source: 'unhandled-rejection' })\n process.exitCode = 1\n })\n }\n\n if (processHooks === 'auto' && !g.__kickjs_httpServer) {\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, async () => {\n log.info(`Received ${signal}, shutting down...`)\n if (g.__app) await g.__app.shutdown()\n process.exit(0)\n })\n }\n }\n\n g.__kickBootstrapped = true\n }\n\n // ── HMR rebuild ──────────────────────────────────────────────────────\n if (g.__app) {\n log.debug('HMR rebuild triggered')\n\n // Tear the PREVIOUS app down before replacing it. Without this the old\n // Application was dropped on the floor still running: its adapters kept\n // their sockets and consumers open, so every save added another set.\n //\n // Observed in the wild as one process holding several consumer-group\n // members — on a single-partition topic only one can hold the assignment,\n // and it was a leaked consumer no longer wired to anything, so jobs\n // stopped being processed. A second socket.io server on the same HTTP\n // server crashed `handleUpgrade()` the same way.\n //\n // `closeServer: false` is load-bearing: the dev HTTP server is shared\n // across rebuilds, so a full shutdown would close the listening socket and\n // kill HMR on the first save.\n //\n // Failures are logged, not thrown — one broken adapter must not leave the\n // dev server wedged with no app at all.\n try {\n await g.__app.shutdown({ closeServer: false })\n } catch (err) {\n log.error(err as Error, 'HMR teardown of the previous app failed')\n }\n\n tryReloadEnv()\n Container.reset()\n\n const freshApp = new Application(options)\n g.__app = freshApp\n g.__kickjs_container = freshApp.getContainer()\n\n await freshApp.start()\n log.debug('HMR rebuild complete')\n return freshApp\n }\n\n // ── First boot ───────────────────────────────────────────────────────\n const app = new Application(options)\n g.__app = app\n g.__kickjs_container = app.getContainer()\n\n if (process.env.KICK_TINKER) {\n await app.registerOnly()\n return app\n }\n\n await app.start()\n\n return app\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-4Tb6q0Ny.d.mts","names":[],"sources":["../src/http/middleware/session.ts"],"mappings":";;;;;;;;;;;;;UAgBiB;GACd;;UAGc;EACf,IAAI,cAAc,QAAQ;EAC1B,IAAI,aAAa,MAAM,aAAa,iBAAiB;EACrD,QAAQ,cAAc;EACtB,OAAO,aAAa,iBAAiB;;UAGtB;EACf;EACA,MAAM;EACN,cAAc;EACd,WAAW;EACX,QAAQ;;UAGO;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;IACE;IACA;IACA;IACA;IACA;;;EAGF,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiHM,QAAQ,SAAS,kBAAc,KAoB1B,SAAO,KAAO,UAAQ,MAAQ,iBAAY"}
|