@forinda/kickjs 6.3.1 → 6.4.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-stSpsUHs.d.mts → adapter-BjxEv8oW.d.mts} +7 -1
- package/dist/{adapter-stSpsUHs.d.mts.map → adapter-BjxEv8oW.d.mts.map} +1 -1
- package/dist/{application-C_KlrNoH.d.mts → application-Bo3jSfmL.d.mts} +2 -2
- package/dist/{application-C_KlrNoH.d.mts.map → application-Bo3jSfmL.d.mts.map} +1 -1
- package/dist/application-TiHVcGRL.mjs +12 -0
- package/dist/application-TiHVcGRL.mjs.map +1 -0
- package/dist/{bootstrap-BgffgFNX.d.mts → bootstrap-DUUxZXvF.d.mts} +2 -2
- package/dist/{bootstrap-BgffgFNX.d.mts.map → bootstrap-DUUxZXvF.d.mts.map} +1 -1
- package/dist/{bootstrap-BJB_prf2.mjs → bootstrap-DW2yzKh6.mjs} +3 -3
- package/dist/{bootstrap-BJB_prf2.mjs.map → bootstrap-DW2yzKh6.mjs.map} +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-CZB3bZ5C.mjs → config-BTDTXaeZ.mjs} +3 -3
- package/dist/{config-CZB3bZ5C.mjs.map → config-BTDTXaeZ.mjs.map} +1 -1
- package/dist/container-Da9BLMHy.mjs +117 -0
- package/dist/container-Da9BLMHy.mjs.map +1 -0
- package/dist/{context-BADwflt3.mjs → context-Cs0939u1.mjs} +3 -3
- package/dist/{context-BADwflt3.mjs.map → context-Cs0939u1.mjs.map} +1 -1
- package/dist/{contributor-runner-cYobgGDc.mjs → contributor-runner-B3Z3Q7J5.mjs} +2 -2
- package/dist/{contributor-runner-cYobgGDc.mjs.map → contributor-runner-B3Z3Q7J5.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-BnRYGmHr.mjs → core-CQ6u5kOq.mjs} +3 -3
- package/dist/{core-BnRYGmHr.mjs.map → core-CQ6u5kOq.mjs.map} +1 -1
- package/dist/{csrf-BXorNCE1.mjs → csrf-Dlo-P8Il.mjs} +2 -2
- package/dist/{csrf-BXorNCE1.mjs.map → csrf-Dlo-P8Il.mjs.map} +1 -1
- package/dist/{disposables-Dt1t6RE0.mjs → disposables-rPSyh69r.mjs} +2 -2
- package/dist/{disposables-Dt1t6RE0.mjs.map → disposables-rPSyh69r.mjs.map} +1 -1
- package/dist/{env-C_gXlHfU.mjs → env-Ce-aYRxQ.mjs} +3 -3
- package/dist/{env-C_gXlHfU.mjs.map → env-Ce-aYRxQ.mjs.map} +1 -1
- package/dist/{handler-Cm6IG_Ld.mjs → handler-D8Zyie-B.mjs} +3 -3
- package/dist/{handler-Cm6IG_Ld.mjs.map → handler-D8Zyie-B.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.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.mjs +2 -2
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- 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 +2 -2
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/router-builder.mjs.map +1 -1
- package/dist/http/runtimes/fastify.d.mts +1 -1
- package/dist/http/runtimes/fastify.mjs +2 -2
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/kv-stores-CAYc5VKm.mjs +12 -0
- package/dist/kv-stores-CAYc5VKm.mjs.map +1 -0
- package/dist/kv-stores-gQrfH0KQ.d.mts +188 -0
- package/dist/kv-stores-gQrfH0KQ.d.mts.map +1 -0
- package/dist/{metadata-JG-zrMEb.mjs → metadata-BhKXlN2F.mjs} +2 -2
- package/dist/{metadata-JG-zrMEb.mjs.map → metadata-BhKXlN2F.mjs.map} +1 -1
- package/dist/{module-registry-Bp2f2NWM.mjs → module-registry-CvP9mee-.mjs} +2 -2
- package/dist/{module-registry-Bp2f2NWM.mjs.map → module-registry-CvP9mee-.mjs.map} +1 -1
- package/dist/{query-C7YPGlW6.mjs → query-Cs_Llg77.mjs} +2 -2
- package/dist/{query-C7YPGlW6.mjs.map → query-Cs_Llg77.mjs.map} +1 -1
- package/dist/{reply-ym0sPcEJ.mjs → reply-edT8RGBB.mjs} +3 -3
- package/dist/{reply-ym0sPcEJ.mjs.map → reply-edT8RGBB.mjs.map} +1 -1
- package/dist/{rolldown-runtime-BsVplZDg.mjs → rolldown-runtime-Bzotz84Y.mjs} +1 -1
- package/dist/{router-builder-BK7yPm89.d.mts → router-builder-CiS2ASTJ.d.mts} +14 -3
- package/dist/router-builder-CiS2ASTJ.d.mts.map +1 -0
- package/dist/{spa-BwO1qOuA.d.mts → spa-Dp9onKJt.d.mts} +2 -2
- package/dist/{spa-BwO1qOuA.d.mts.map → spa-Dp9onKJt.d.mts.map} +1 -1
- package/dist/{upload-config-BHIV2SGy.mjs → upload-config-C3MSF7RF.mjs} +2 -2
- package/dist/{upload-config-BHIV2SGy.mjs.map → upload-config-C3MSF7RF.mjs.map} +1 -1
- package/dist/{views-w0EPrhui.d.mts → views-DjgUXRdu.d.mts} +3 -3
- package/dist/{views-w0EPrhui.d.mts.map → views-DjgUXRdu.d.mts.map} +1 -1
- package/dist/web.d.mts +10 -3
- package/dist/web.d.mts.map +1 -1
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/application-ClWQpWUz.mjs +0 -12
- package/dist/application-ClWQpWUz.mjs.map +0 -1
- package/dist/container-BkAX8ftP.mjs +0 -108
- package/dist/container-BkAX8ftP.mjs.map +0 -1
- package/dist/reply-2CvBP_Sl.d.mts +0 -63
- package/dist/reply-2CvBP_Sl.d.mts.map +0 -1
- package/dist/router-builder-BK7yPm89.d.mts.map +0 -1
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs v6.3.1
|
|
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{METADATA}from"./core/interfaces.mjs";import{n as tokenName,o as moduleRouteMissingControllerError,t as Container}from"./container-BkAX8ftP.mjs";import{Logger,createLogger}from"./core/logger.mjs";import{t as getClassMeta}from"./metadata-JG-zrMEb.mjs";import{g as mountSort}from"./core-BnRYGmHr.mjs";import{t as MutableModuleRegistry}from"./module-registry-Bp2f2NWM.mjs";import{i as runDisposables,n as drainDisposables,r as registerDisposable,t as disposeAll}from"./disposables-Dt1t6RE0.mjs";import{normalizePath}from"./core/path.mjs";import{requestId}from"./http/middleware/request-id.mjs";import{errorHandler,notFoundHandler}from"./http/middleware/error-handler.mjs";import{i as requestStore,t as RequestContext}from"./context-BADwflt3.mjs";import{o as isRequestScopeMiddleware,s as requestScopeMiddleware,t as applyHandlerResult}from"./reply-ym0sPcEJ.mjs";import{_setExternalContributorSources,buildRouteTable}from"./http/router-builder.mjs";import{validate}from"./http/middleware/validate.mjs";import{buildUploadMiddleware}from"./http/middleware/upload.mjs";import http from"node:http";import express,{Router}from"express";function materializeRouter(entries){let router=Router();for(let entry of entries){let method=entry.method.toLowerCase(),handlers=[];entry.meta.validation&&handlers.push(validate(entry.meta.validation)),entry.meta.upload&&handlers.push(buildUploadMiddleware(entry.meta.upload));for(let mw of entry.middlewares)handlers.push((req,res,next)=>{let ctx=new RequestContext(req,res,next);Promise.resolve(mw(ctx,next)).catch(next)});if(entry.contributorRunner){let run=entry.contributorRunner;handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{await run(ctx),next()}catch(err){next(err)}})}handlers.push(async(req,res,next)=>{let ctx=new RequestContext(req,res,next);try{let result=await entry.handler(ctx);res.headersSent||applyHandlerResult(ctx,result)}catch(err){next(err)}}),router[method](entry.path,...handlers)}return router}function buildRoutes(controllerClass,options={}){return materializeRouter(buildRouteTable(controllerClass,options))}function expressRuntime(){return{name:`express`,createApp(options={}){let app=express();return app.disable(`x-powered-by`),app.set(`trust proxy`,options.trustProxy??!1),app},nodeHandler(app){return(req,res,next)=>{next?app(req,res,next):app(req,res)}},mountRoutes(app,table){for(let{mountPath,routes}of table)app.use(mountPath,materializeRouter(routes))},useConnect(app,mw,opts={}){opts.path===void 0?app.use(mw):app.use(opts.path,mw)},serveStatic(app,prefix,dir){app.use(prefix,express.static(dir))},setNotFound(app,mw){app.use(mw)},setErrorHandler(app,mw){app.use(mw)},capabilities:{render:!0,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!1}}}const log=createLogger(`Application`);var Application=class{options;app;runtime;container;httpServer=null;adapters;plugins;_moduleContributors=[];_inFlightRequests=0;_draining=!1;_shutdownInitiated=!1;_drainResolvers=[];constructor(options){this.options=options,this.runtime=options.runtime??expressRuntime(),this.app=this.runtime.createApp({trustProxy:options.trustProxy}),this.container=Container.getInstance(),this.plugins=mountSort(options.plugins??[],`plugin`);let allAdapters=[...this.plugins.flatMap(p=>p.adapters?.()??[]),...options.adapters??[]],anonAdapterCount=0,namedAdapters=allAdapters.map(adapter=>{if(!adapter.name){let ctorName=adapter.constructor?.name,fallback=ctorName&&ctorName!==`Object`?ctorName:`AnonymousAdapter#${anonAdapterCount++}`;return Object.assign(adapter,{name:fallback})}return adapter});this.adapters=mountSort(namedAdapters,`adapter`),Container._requestStoreProvider=()=>requestStore.getStore()??null}get isDraining(){return this._draining}get inFlightRequests(){return this._inFlightRequests}getContainer(){return this.container}handle(req,res,next){let handler=this.runtime.nodeHandler(this.app);next?handler(req,res,next):handler(req,res)}adapterCtx(server){let env=process.env.NODE_ENV??`development`;return{http:this.adapterHttp(),app:this.app,container:this.container,server,env,isProduction:env===`production`}}adapterHttp(){return{route:(method,path,handler)=>{let entry={method,path,middlewares:[],contributorRunner:null,handler,meta:{}};this.runtime.mountRoutes(this.app,[{mountPath:`/`,routes:[entry]}])},mount:(prefix,routes)=>{this.runtime.mountRoutes(this.app,[{mountPath:prefix,routes}])},serveStatic:(prefix,dir)=>{this.runtime.serveStatic(this.app,prefix,dir)},use:(mw,opts)=>{this.runtime.useConnect(this.app,mw,opts)}}}async callHook(hook,ctx){if(hook)try{let result=hook(ctx);result&&typeof result.then==`function`&&await result}catch(err){log.error(err,`Adapter hook failed`)}}async setup(){log.debug(`Bootstrapping application...`),this._moduleContributors=[];let adapterMw=this.collectAdapterMiddleware();this.app.__kickApp=this;let ctx=this.adapterCtx();for(let adapter of this.adapters)await this.callHook(adapter.beforeMount?.bind(adapter),ctx);this.runtime.useConnect(this.app,this.requestTrackingMiddleware()),this.mountHealthEndpoints(),this.shouldAutoMountRequestScope()&&this.runtime.useConnect(this.app,requestScopeMiddleware()),this.mountMiddlewareList(adapterMw.beforeGlobal);for(let plugin of this.plugins)plugin.register?.(this.container);for(let plugin of this.plugins)try{let mw=plugin.middleware?.()??[];for(let handler of mw)this.runtime.useConnect(this.app,handler)}catch(err){log.error(err,`Plugin middleware failed: ${plugin.name??`unknown`}`)}if(this.options.security?.helmet!==!1)try{let{helmet:helmetFn}=await import(`./http/middleware/helmet.mjs`);this.runtime.useConnect(this.app,helmetFn())}catch{}let userMiddlewares=this.options.middlewares??this.options.middleware;if(userMiddlewares)for(let entry of userMiddlewares)this.mountMiddlewareEntry(entry);else this.runtime.useConnect(this.app,requestId()),this.runtime.capabilities.nativeBodyParsing||this.runtime.useConnect(this.app,express.json({limit:this.options.jsonLimit??`1mb`}));this.mountMiddlewareList(adapterMw.afterGlobal);let moduleRegistry=new MutableModuleRegistry;for(let plugin of this.plugins){for(let m of plugin.modules?.()??[])moduleRegistry.mount(m);plugin.setup?.(moduleRegistry)}for(let m of this.options.modules)moduleRegistry.mount(m);this.options.setup?.(moduleRegistry);let modules=moduleRegistry.entries.map(entry=>{let mod=typeof entry==`function`?new entry:entry;return mod.register?.(this.container),mod});this.container.bootstrap(),this.container.has(Logger)||this.container.registerInstance(Logger,new Logger),this.mountMiddlewareList(adapterMw.beforeRoutes);let apiPrefix=this.options.apiPrefix??`/api`,defaultVersion=this.options.defaultVersion??1,shouldLogRoutes=this.options.logRouteTable??this.options.logRoutesTable??!1,mountedRoutes=[],adapterSources=[];for(let adapter of this.adapters){let adapterContribs=adapter.contributors?.()??[],adapterLabel=adapter.name??adapter.constructor.name??`adapter`;for(let registration of adapterContribs)adapterSources.push({source:`adapter`,registration,label:adapterLabel})}for(let plugin of this.plugins){let pluginContribs=plugin.contributors?.()??[],pluginLabel=plugin.name??plugin.constructor?.name??`plugin`;for(let registration of pluginContribs)adapterSources.push({source:`adapter`,registration,label:pluginLabel})}let globalSources=(this.options.contributors??[]).map(registration=>({source:`global`,registration,label:`bootstrap`}));this._moduleContributors=[];for(let mod of modules){let declaredName=mod.name,ctorName=mod.constructor?.name,moduleLabel=typeof declaredName==`string`&&declaredName.length>0?declaredName:ctorName&&ctorName!==`Object`?ctorName:`module`,moduleSources=(mod.contributors?.()??[]).map(registration=>({source:`module`,registration,label:moduleLabel}));for(let src of moduleSources)this._moduleContributors.push({registration:src.registration,label:moduleLabel});_setExternalContributorSources([...moduleSources,...adapterSources,...globalSources]);try{let result=mod.routes();if(!result)continue;let routeSets=Array.isArray(result)?result:[result];for(let route of routeSets){let mountPath=`${apiPrefix}/v${route.version??defaultVersion}${normalizePath(route.path)}`;if(route.router)this.runtime.useConnect(this.app,route.router,{path:mountPath});else if(route.controller)this.runtime.mountRoutes(this.app,[{mountPath,routes:buildRouteTable(route.controller)}]);else throw moduleRouteMissingControllerError(mountPath);if(route.controller){for(let adapter of this.adapters)try{adapter.onRouteMount?.(route.controller,mountPath)}catch(err){log.error(err,`adapter.onRouteMount() failed for ${mountPath}`)}shouldLogRoutes&&mountedRoutes.push({controller:route.controller,mountPath})}}}finally{_setExternalContributorSources([])}}if(shouldLogRoutes&&mountedRoutes.length>0){let methodOrder=[`GET`,`POST`,`PUT`,`PATCH`,`DELETE`,`HEAD`,`OPTIONS`],methodRank=m=>{let i=methodOrder.indexOf(m);return i===-1?99:i},totalRoutes=0;log.info(`Routes:`);for(let{controller,mountPath}of mountedRoutes){let defs=getClassMeta(METADATA.ROUTES,controller,[]);if(defs.length===0)continue;let counts={};for(let def of defs){let m=def.method.toUpperCase();counts[m]=(counts[m]||0)+1}totalRoutes+=defs.length;let methods=Object.entries(counts).toSorted(([a],[b])=>methodRank(a)-methodRank(b)).map(([m,n])=>`${n} ${m}`).join(`, `),name=controller.name||`Controller`;log.info(` ${name.padEnd(30)} ${mountPath.padEnd(25)} ${defs.length} routes (${methods})`)}log.info(` Total: ${totalRoutes} routes`)}this.mountMiddlewareList(adapterMw.afterRoutes);for(let adapter of this.adapters)await this.callHook(adapter.beforeStart?.bind(adapter),ctx);this.runtime.setNotFound(this.app,this.options.onNotFound??notFoundHandler()),this.runtime.setErrorHandler(this.app,this.options.onError??errorHandler())}async registerOnly(){await this.setup()}async start(){await this.setup();let g=globalThis;if(g.__kickjs_httpServer){this.httpServer=g.__kickjs_httpServer,log.debug(`Attached to Vite dev server`),g.__kickjs_app_shutdown=()=>this.shutdown();for(let adapter of this.adapters){let ctx=this.adapterCtx(this.httpServer);await this.callHook(adapter.afterStart?.bind(adapter),ctx)}for(let plugin of this.plugins)await plugin.onReady?.(this.container);return}let port=this.options.port??parseInt(process.env.PORT||`3000`,10);this.httpServer=http.createServer(this.runtime.nodeHandler(this.app)),this.httpServer.on(`error`,err=>{throw err.code===`EADDRINUSE`&&(log.error(`Port ${port} is already in use. Kill the existing process or use a different port:\n PORT=${port+1} kick dev\n lsof -i :${port} # find what's using it\n kill <PID> # stop it`),process.exit(1)),err}),await new Promise((resolve,reject)=>{this.httpServer.listen(port,async()=>{try{log.info(`Server running on http://localhost:${port}`);for(let adapter of this.adapters){let afterCtx=this.adapterCtx(this.httpServer);await this.callHook(adapter.afterStart?.bind(adapter),afterCtx)}for(let plugin of this.plugins)await plugin.onReady?.(this.container);resolve()}catch(err){reject(err)}})})}async rebuild(){let prevApp=this.app,prevContainer=this.container,staleDisposables=drainDisposables();try{Container.reset(),this.container=Container.getInstance(),this.app=this.runtime.createApp({trustProxy:this.options.trustProxy}),await this.setup()}catch(err){log.error(err,`HMR rebuild failed, keeping previous app`),await runDisposables(drainDisposables());for(let d of staleDisposables)registerDisposable(d);this.app=prevApp,this.container=prevContainer;return}await runDisposables(staleDisposables),this.httpServer&&(this.httpServer.removeAllListeners(`request`),this.httpServer.on(`request`,this.runtime.nodeHandler(this.app)),log.debug(`HMR: app rebuilt and swapped`))}async shutdown(){if(this._shutdownInitiated){log.debug(`Shutdown already in progress, skipping duplicate call`);return}this._shutdownInitiated=!0,this._draining=!0,log.debug(`Shutting down — draining in-flight requests...`);let timeoutMs=this.options.shutdownTimeout??3e4,timer,forceExitPromise=timeoutMs>0?new Promise(resolve=>{timer=setTimeout(()=>resolve(`timeout`),timeoutMs),timer.unref()}):new Promise(()=>{});try{if(this.httpServer&&this.httpServer.close(()=>{}),this._inFlightRequests>0){log.debug(`Waiting for ${this._inFlightRequests} in-flight request(s) to complete...`);let drainPromise=new Promise(resolve=>{this._drainResolvers.push(()=>resolve(`drained`))});await Promise.race([drainPromise,forceExitPromise])===`timeout`?log.warn(`Shutdown timeout (${timeoutMs}ms) reached with ${this._inFlightRequests} request(s) still in-flight, forcing shutdown`):log.debug(`All in-flight requests completed`)}let results=await Promise.allSettled([...this.plugins.map(plugin=>Promise.resolve(plugin.shutdown?.())),...this.adapters.map(adapter=>Promise.resolve(adapter.shutdown?.()))]);for(let result of results)result.status===`rejected`&&log.error({err:result.reason},`Adapter shutdown failed`);await disposeAll(),this.container.flushChanges()}finally{timer&&clearTimeout(timer)}}getRuntimeApp(){return this.app}getActiveRuntime(){return{name:this.runtime.name,capabilities:this.runtime.capabilities}}getExpressApp(){return this.app}getAdapters(){return this.adapters}getPlugins(){return this.plugins}getContributors(){let out=[],ingest=(list,source,label)=>{if(list)for(let reg of list){let r=reg;if(r.key===void 0||r.key===null)continue;let key=typeof r.key==`string`?r.key:tokenName(r.key);out.push({key,source,label,dependsOn:Array.isArray(r.dependsOn)?r.dependsOn:[]})}};for(let adapter of this.adapters)ingest(adapter.contributors?.(),`adapter`,adapter.name??`adapter`);for(let plugin of this.plugins)ingest(plugin.contributors?.(),`plugin`,plugin.name??`plugin`);for(let{registration,label}of this._moduleContributors)ingest([registration],`module`,label);return ingest(this.options.contributors,`global`,`bootstrap`),out}getHttpServer(){return this.httpServer}shouldAutoMountRequestScope(){if(this.options.contextStore===`manual`)return!1;let userEntries=this.options.middlewares??this.options.middleware??[];for(let entry of userEntries)if(isRequestScopeMiddleware(typeof entry==`function`?entry:entry.handler))return!1;return!0}collectAdapterMiddleware(){let result={beforeGlobal:[],afterGlobal:[],beforeRoutes:[],afterRoutes:[]};for(let adapter of this.adapters){let entries=adapter.middleware?.()??[];for(let entry of entries)result[entry.phase??`afterGlobal`].push(entry)}return result}mountMiddlewareList(entries){for(let entry of entries){if(entry.path===void 0){this.runtime.useConnect(this.app,entry.handler);continue}this.runtime.useConnect(this.app,entry.handler,{path:entry.path})}}mountMiddlewareEntry(entry){typeof entry==`function`?this.runtime.useConnect(this.app,entry):this.runtime.useConnect(this.app,entry.handler,{path:entry.path})}requestTrackingMiddleware(){return(_req,res,next)=>{this._inFlightRequests++;let onFinish=()=>{if(res.removeListener(`finish`,onFinish),res.removeListener(`close`,onFinish),this._inFlightRequests--,this._draining&&this._inFlightRequests===0){for(let resolve of this._drainResolvers)resolve();this._drainResolvers=[]}};res.on(`finish`,onFinish),res.on(`close`,onFinish),next()}}mountHealthEndpoints(){let http=this.adapterHttp();http.route(`GET`,`/health/live`,ctx=>{this._draining?ctx.res.status(503).json({status:`draining`,uptime:process.uptime()}):ctx.json({status:`ok`,uptime:process.uptime()})}),http.route(`GET`,`/health/ready`,async ctx=>{if(this._draining){ctx.res.status(503).json({status:`draining`,checks:[]});return}let adaptersWithHealth=this.adapters.filter(a=>a.onHealthCheck),results=(await Promise.allSettled(adaptersWithHealth.map(a=>a.onHealthCheck()))).map((c,i)=>c.status===`fulfilled`?c.value:{name:adaptersWithHealth[i].name??`unknown`,status:`down`}),healthy=results.every(r=>r.status===`up`);ctx.res.status(healthy?200:503).json({status:healthy?`ready`:`degraded`,checks:results})})}};export{materializeRouter as i,buildRoutes as n,expressRuntime as r,Application as t};
|
|
12
|
-
//# sourceMappingURL=application-ClWQpWUz.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"application-ClWQpWUz.mjs","names":[],"sources":["../src/http/runtimes/express.ts","../src/http/application.ts"],"sourcesContent":["// The default Express runtime — the M1 reference implementation of HttpRuntime.\n//\n// Its route materializer rebuilds the exact `(req, res, next)` handler chain\n// the old `buildRoutes()` produced, so swapping decorators → RouteTable →\n// runtime is behavior-neutral under Express. `buildRoutes()` (the public,\n// Router-returning API) now lives here as a thin shim over `buildRouteTable()`\n// + this materializer.\n\nimport express, {\n Router,\n type Express,\n type Request,\n type Response,\n type NextFunction,\n type RequestHandler,\n} from 'express'\n\nimport { buildRouteTable, type BuildRoutesOptions } from '../router-builder'\nimport { RequestContext } from '../context'\nimport { applyHandlerResult } from '../reply'\nimport { validate } from '../middleware/validate'\nimport { buildUploadMiddleware } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n UseConnectOptions,\n} from '../runtime'\n\n/**\n * Materialize a controller's {@link RouteEntry}[] onto a fresh `express.Router`.\n * Reproduces the legacy handler ordering precisely: validation → upload →\n * `(ctx, next)` middleware → contributor runner → terminal handler. Each step\n * constructs its own `RequestContext` over the same `req`/`res` — request-scoped\n * state lives in the AsyncLocalStorage store, not on the ctx instance, so the\n * per-step instances share state exactly as before.\n */\nexport function materializeRouter(entries: RouteEntry[]): Router {\n const router = Router()\n\n for (const entry of entries) {\n const method = entry.method.toLowerCase() as 'get' | 'post' | 'put' | 'delete' | 'patch'\n const handlers: RequestHandler[] = []\n\n // Validation middleware (shared with the standalone validate() export).\n if (entry.meta.validation) {\n handlers.push(validate(entry.meta.validation))\n }\n\n // @FileUpload — auto-attach upload middleware from metadata.\n if (entry.meta.upload) {\n handlers.push(buildUploadMiddleware(entry.meta.upload))\n }\n\n // Class + method middleware — wrapped as Express middleware with error catching.\n for (const mw of entry.middlewares) {\n handlers.push((req: Request, res: Response, next: NextFunction) => {\n const ctx = new RequestContext(req, res, next)\n Promise.resolve(mw(ctx, next)).catch(next)\n })\n }\n\n // Context Contributor pipeline (#107) — runs, then advances the chain.\n if (entry.contributorRunner) {\n const run = entry.contributorRunner\n handlers.push(async (req: Request, res: Response, next: NextFunction) => {\n const ctx = new RequestContext(req, res, next)\n try {\n await run(ctx)\n next()\n } catch (err) {\n next(err)\n }\n })\n }\n\n // Terminal handler.\n handlers.push(async (req: Request, res: Response, next: NextFunction) => {\n const ctx = new RequestContext(req, res, next)\n try {\n const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send the returned payload\n // when the handler wrote nothing. `undefined` keeps prior behavior.\n if (!res.headersSent) applyHandlerResult(ctx, result)\n } catch (err) {\n next(err)\n }\n })\n ;(router as any)[method](entry.path, ...handlers)\n }\n\n return router\n}\n\n/**\n * Build an Express Router from a controller class decorated with @Get, @Post,\n * etc. The public, back-compatible entry point: a thin shim over\n * {@link buildRouteTable} + {@link materializeRouter}. Returns an\n * `express.Router` exactly as before — modules pass it to `app.use(path, router)`.\n *\n * @see buildRouteTable for the engine-neutral route-table form.\n */\nexport function buildRoutes(controllerClass: any, options: BuildRoutesOptions = {}): Router {\n return materializeRouter(buildRouteTable(controllerClass, options))\n}\n\n/**\n * The default HTTP runtime. Express stays a peer dependency of `@forinda/kickjs`;\n * apps that pass no `runtime` get this and behave exactly as before.\n */\nexport function expressRuntime(): HttpRuntime<Express> {\n return {\n name: 'express',\n\n createApp(options: RuntimeAppOptions = {}): Express {\n const app = express()\n // Engine hardening + config the Application used to apply directly.\n app.disable('x-powered-by')\n app.set('trust proxy', options.trustProxy ?? false)\n return app\n },\n\n nodeHandler(app) {\n return (req, res, next) => {\n if (next) {\n ;(app as any)(req, res, next)\n } else {\n ;(app as any)(req, res)\n }\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n app.use(mountPath, materializeRouter(routes))\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts: UseConnectOptions = {}) {\n if (opts.path !== undefined) {\n ;(app as any).use(opts.path, mw)\n } else {\n ;(app as any).use(mw)\n }\n },\n\n serveStatic(app, prefix, dir) {\n app.use(prefix, express.static(dir))\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n ;(app as any).use(mw)\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as any).use(mw)\n },\n\n capabilities: {\n render: true,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: false,\n },\n }\n}\n","import http from 'node:http'\nimport express, { type Express, type RequestHandler } from 'express'\nimport {\n Container,\n createLogger,\n Logger,\n normalizePath,\n tokenName,\n METADATA,\n type AppModule,\n type AppModuleClass,\n type AppModuleEntry,\n type AppAdapter,\n type AdapterContext,\n type AdapterMiddleware,\n type ContributorRegistrations,\n type KickPlugin,\n type ModuleRegistry,\n type RouteDefinition,\n type SourcedRegistration,\n mountSort,\n MutableModuleRegistry,\n} from '../core'\nimport { moduleRouteMissingControllerError } from '../core/kick-errors'\nimport {\n disposeAll,\n drainDisposables,\n registerDisposable,\n runDisposables,\n} from '../core/disposables'\nimport { getClassMeta } from '../core/metadata'\nimport { requestId } from './middleware/request-id'\nimport { notFoundHandler, errorHandler } from './middleware/error-handler'\nimport { requestScopeMiddleware, isRequestScopeMiddleware } from './middleware/request-scope'\nimport { _setExternalContributorSources, buildRouteTable } from './router-builder'\nimport { expressRuntime } from './runtimes/express'\nimport type {\n ActiveRuntime,\n AdapterHttp,\n HttpRuntime,\n RouteEntry,\n RuntimeCapabilities,\n} from './runtime'\nimport { requestStore } from './request-store'\n\nconst log = createLogger('Application')\n\n/**\n * A middleware entry in the declarative pipeline.\n * Can be a bare handler or an object with path scoping.\n */\nexport type MiddlewareEntry = RequestHandler | { path: string; handler: RequestHandler }\n\nexport interface ApplicationOptions {\n /**\n * Feature modules to load. Accepts both class form\n * (`SomeModule extends AppModule`, instantiated via `new`) and\n * instance form (output of {@link defineModule}'s factory call,\n * e.g. `TasksModule({ scope: 'admin' })`) — the loader discriminates\n * and either `new`-s the class or uses the instance directly.\n *\n * Static path — for conditional / dynamic registration, use the\n * {@link ApplicationOptions.setup} callback instead.\n */\n modules: AppModuleEntry[]\n\n /**\n * Imperative module registration callback. Receives a\n * {@link ModuleRegistry} you call `.mount(module)` on to register\n * modules conditionally — based on env flags, runtime config, a\n * tenant list, etc. Runs after every plugin's `setup()` hook and\n * after the static `modules: [...]` array is collected.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules: [HelloModule()],\n * setup(registry) {\n * if (env.ENABLE_ADMIN) registry.mount(AdminModule())\n * },\n * })\n * ```\n */\n setup?(registry: ModuleRegistry): void\n /** Adapters that hook into the lifecycle (DB, Redis, Swagger, etc.) */\n adapters?: AppAdapter[]\n /** Server port (falls back to PORT env var, then 3000) */\n port?: number\n /** Global API prefix (default: '/api') */\n apiPrefix?: string\n /** Default API version (default: 1) — routes become /{prefix}/v{version}/{path} */\n defaultVersion?: number\n\n /**\n * Global middleware pipeline. Declared in order.\n * Replaces the hardcoded middleware stack — you control exactly what runs.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * middlewares: [\n * helmet(),\n * cors(),\n * compression(),\n * morgan('dev'),\n * express.json({ limit: '1mb' }),\n * ],\n * })\n * ```\n *\n * If omitted, a sensible default is applied:\n * requestId(), express.json({ limit: '100kb' })\n */\n middlewares?: MiddlewareEntry[]\n /**\n * @deprecated Use {@link ApplicationOptions.middlewares} (plural).\n * Kept as an alias for back-compat; `middlewares` wins when both are\n * set.\n */\n middleware?: MiddlewareEntry[]\n\n /** Plugins that bundle modules, adapters, middleware, and DI bindings */\n plugins?: KickPlugin[]\n\n /**\n * Global Context Contributors (#107) that apply to every route in the\n * application. Merge into the per-route pipeline at the `'global'`\n * precedence level — they lose to module, adapter, class, and method\n * contributors with the same key, providing app-wide defaults that any\n * narrower scope can override.\n *\n * @example\n * ```ts\n * const StartedAt = defineContextDecorator({\n * key: 'requestStartedAt',\n * resolve: () => Date.now(),\n * })\n *\n * bootstrap({\n * modules,\n * contributors: [StartedAt.registration],\n * })\n * ```\n */\n contributors?: ContributorRegistrations\n\n /**\n * Backing store strategy for {@link RequestContext} `set/get` and the\n * Context Contributor pipeline (#107).\n *\n * - `'auto'` (default) — Application mounts {@link requestScopeMiddleware}\n * automatically before any user middleware. If the user-supplied\n * `middleware` list already includes `requestScopeMiddleware()`,\n * detection skips the auto-mount so adopters can control its position.\n * - `'manual'` — Application never mounts the wrapper. The Context\n * Contributor pipeline still runs on every route — the runner is\n * inserted by `router-builder` regardless of ALS state. What\n * degrades without an ALS frame is the *backing store*:\n * REQUEST-scoped DI throws (no `requestStore.getStore()` to read\n * from) and `RequestContext.set/get` throws. Use `'manual'` only when you\n * genuinely intend to wrap requests in your own ALS frame (rare —\n * multi-tenant adapters used to do this; post-Phase 3 they share\n * the framework's frame instead).\n */\n contextStore?: 'auto' | 'manual'\n\n /**\n * Controls whether KickJS registers process-level handlers at bootstrap.\n *\n * - `'auto'` (default) — register `uncaughtException` /\n * `unhandledRejection` loggers AND `SIGINT` / `SIGTERM` →\n * `app.shutdown()` → `process.exit(0)`.\n * - `'errors-only'` — register the error loggers; skip the signal\n * handlers. Use this when you have your own shutdown choreographer\n * (OpenTelemetry SDK, Sentry, custom orchestrator) that calls\n * `app.shutdown()` itself and you don't want two listeners racing\n * to call `process.exit`.\n * - `'manual'` — skip everything. Adopter is responsible for both\n * error logging and signal-driven shutdown.\n *\n * Background: many observability SDKs install their own SIGTERM\n * handler to flush spans/metrics before the process exits. Two\n * separate handlers calling `process.exit(0)` race each other and\n * can truncate the in-flight flush. Setting this to `'errors-only'`\n * or `'manual'` lets the SDK own the lifecycle while still letting\n * KickJS surface uncaught exceptions / rejections via the logger.\n *\n * The dev mode auto-detect (`__kickjs_httpServer` set by the Vite\n * plugin) suppresses signal registration regardless of this option,\n * since Vite owns the lifecycle in development.\n */\n processHooks?: 'auto' | 'errors-only' | 'manual'\n\n /**\n * The HTTP engine driver. Defaults to {@link expressRuntime} — Express stays\n * the zero-config default, so existing apps are unaffected.\n *\n * Any `HttpRuntime` is accepted — pass `fastifyRuntime()` (from\n * `@forinda/kickjs/fastify`) or a custom engine driver. Defaults to\n * {@link expressRuntime}. The engine-native escape hatches\n * (`getRuntimeApp()`, `AdapterContext.app`) follow the active runtime via the\n * registry (`ActiveRuntime`, spec §4.3b) — Express by default, or the augmented\n * engine once the `kick/runtime` typegen output is present.\n */\n runtime?: HttpRuntime\n\n /** Express `trust proxy` setting */\n trustProxy?: boolean | number | string | ((ip: string, hopIndex: number) => boolean)\n /** Maximum JSON body size (only used when middleware is not provided) */\n jsonLimit?: string | number\n /**\n * Print the route table on startup. Default: `false`. Set to `true`\n * to log a per-controller summary (method counts + mount paths) once\n * the app has mounted. Emitted at `info` level, so it is still subject\n * to `LOG_LEVEL` filtering (visible at the default `info`; hidden at\n * `warn`/`error`/`silent`).\n */\n logRouteTable?: boolean\n /**\n * @deprecated Use {@link ApplicationOptions.logRouteTable}. Kept as an\n * alias for back-compat; `logRouteTable` wins when both are set.\n */\n logRoutesTable?: boolean\n /**\n * Maximum time (ms) to wait for graceful shutdown before forcing exit.\n * Default: 30000 (30 seconds). Set to 0 to disable forced exit.\n */\n shutdownTimeout?: number\n\n /**\n * Enable cluster mode for multi-core utilization.\n *\n * When enabled, the primary process forks worker processes that share the\n * same port via Node's built-in `cluster` module (OS load balancing).\n *\n * - `true` — use all available CPU cores\n * - `{ workers: N }` — use exactly N workers\n *\n * Workers are auto-restarted on crash. SIGTERM/SIGINT on the primary is\n * forwarded to all workers.\n *\n * @example\n * ```ts\n * bootstrap({ modules, cluster: true })\n * bootstrap({ modules, cluster: { workers: 4 } })\n * ```\n */\n cluster?: boolean | { workers?: number }\n\n /**\n * Custom 404 handler for unmatched routes. Receives the raw Express\n * `(req, res, next)` args. When omitted, the built-in handler returns\n * `{ message: 'Not Found' }` with status 404.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * onNotFound: (req, res) => {\n * res.status(404).json({ error: 'Route not found', path: req.originalUrl })\n * },\n * })\n * ```\n */\n onNotFound?: (req: any, res: any, next: any) => void\n\n /**\n * Custom global error handler. Receives `(err, req, res, next)` — the\n * standard Express error-handling signature. When omitted, the built-in\n * handler formats ZodError, HttpException, and unexpected errors.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * onError: (err, req, res, next) => {\n * logger.error(err)\n * res.status(err.status ?? 500).json({ error: err.message })\n * },\n * })\n * ```\n */\n onError?: (err: any, req: any, res: any, next: any) => void\n\n /**\n * Security defaults applied automatically unless opted out.\n *\n * When not specified, secure defaults are applied:\n * - Helmet security headers are auto-injected\n * - JSON body limit defaults to 1MB\n *\n * @example\n * ```ts\n * bootstrap({\n * modules,\n * security: {\n * helmet: false, // disable auto-helmet\n * },\n * })\n * ```\n */\n security?: {\n /** Auto-inject helmet security headers. Default: true */\n helmet?: boolean\n }\n}\n\n/**\n * The main application class. Wires together Express, the DI container,\n * feature modules, adapters, and the middleware pipeline.\n */\nexport class Application {\n private app: Express\n /** The HTTP engine driver. Defaults to {@link expressRuntime}. */\n private readonly runtime: HttpRuntime\n private container: Container\n private httpServer: http.Server | null = null\n private readonly adapters: AppAdapter[]\n\n private readonly plugins: KickPlugin[]\n\n /**\n * Snapshot of every module-level Context Contributor registration\n * captured during `setup()`. The registrations themselves are\n * frozen by `defineContextDecorator`, so storing references is\n * safe — they outlive the module instances they came from.\n *\n * Used by {@link getContributors} so DevTools and adopter tooling\n * can render the module-level contributors alongside the adapter /\n * plugin / global ones. Populated once per `setup()` call; cleared\n * at the start of each invocation so a re-setup (test harnesses,\n * dev-server restarts) doesn't accumulate stale entries.\n */\n private _moduleContributors: Array<{\n registration: { key: string; dependsOn: readonly string[] }\n label: string\n }> = []\n\n /** Number of HTTP requests currently being processed */\n private _inFlightRequests = 0\n /** Whether the application is draining (shutting down gracefully) */\n private _draining = false\n /** Whether shutdown has already been initiated (prevents double-shutdown) */\n private _shutdownInitiated = false\n /** Resolvers waiting for in-flight requests to reach zero */\n private _drainResolvers: Array<() => void> = []\n\n constructor(private readonly options: ApplicationOptions) {\n this.runtime = options.runtime ?? expressRuntime()\n // `this.app` is the engine-native app; typed Express here (the default) for\n // the legacy escape hatches. Under another runtime it holds that engine's\n // app — `getRuntimeApp()` / `AdapterContext.app` expose it via ActiveRuntime.\n this.app = this.runtime.createApp({ trustProxy: options.trustProxy }) as Express\n this.container = Container.getInstance()\n\n // Sort plugins by `dependsOn` declarations BEFORE reading their adapters/etc.\n // Plugins without `dependsOn` keep their declaration order — this is a\n // pure refinement; no behaviour change for apps that don't use the field.\n this.plugins = mountSort(options.plugins ?? [], 'plugin')\n\n // Build adapter list from plugin adapters + user adapters, synthesize\n // a stable name for any anonymous adapter (so duplicate-name detection\n // and `dependsOn` resolution have something to key on), then sort by\n // adapter `dependsOn`. Plugin-shipped adapters keep their plugin's\n // relative order unless their own `dependsOn` says otherwise.\n const allAdapters = [\n ...this.plugins.flatMap((p) => p.adapters?.() ?? []),\n ...(options.adapters ?? []),\n ]\n let anonAdapterCount = 0\n const namedAdapters: Array<AppAdapter & { name: string }> = allAdapters.map((adapter) => {\n if (!adapter.name) {\n // `constructor.name === 'Object'` for plain object-literal adapters —\n // useless as an identity since two anonymous literals would collide.\n // Only borrow the constructor name when it's an actual class.\n const ctorName = adapter.constructor?.name\n const fallback =\n ctorName && ctorName !== 'Object' ? ctorName : `AnonymousAdapter#${anonAdapterCount++}`\n return Object.assign(adapter, { name: fallback })\n }\n return adapter as AppAdapter & { name: string }\n })\n this.adapters = mountSort(namedAdapters, 'adapter')\n // Wire the request store provider so Container can resolve REQUEST-scoped deps\n Container._requestStoreProvider = () => requestStore.getStore() ?? null\n }\n\n /** Whether the application is currently draining in-flight requests */\n get isDraining(): boolean {\n return this._draining\n }\n\n /** Number of HTTP requests currently being processed */\n get inFlightRequests(): number {\n return this._inFlightRequests\n }\n\n /** Get the DI container instance */\n getContainer(): Container {\n return this.container\n }\n\n /**\n * Express request handler — delegates to the internal Express app.\n *\n * Used by the Vite dev-server plugin to route requests through KickJS:\n * ```ts\n * const mod = await ssrLoadModule('virtual:kickjs/app')\n * mod.app.handle(req, res, next)\n * ```\n *\n * Also works as a standard Node.js request handler for production:\n * ```ts\n * http.createServer(app.handle.bind(app))\n * ```\n */\n handle(req: http.IncomingMessage, res: http.ServerResponse, next?: (err?: any) => void): void {\n const handler = this.runtime.nodeHandler(this.app)\n if (next) {\n handler(req, res, next)\n } else {\n handler(req, res)\n }\n }\n\n /**\n * Full setup pipeline:\n * 1. Adapter beforeMount hooks (early routes — docs, health)\n * 2. Adapter middleware (phase: beforeGlobal)\n * 3. Global middleware (user-declared or defaults)\n * 4. Adapter middleware (phase: afterGlobal)\n * 5. Module registration + DI bootstrap\n * 6. Adapter middleware (phase: beforeRoutes)\n * 7. Module route mounting\n * 8. Adapter middleware (phase: afterRoutes)\n * 9. Error handlers (notFound + global)\n * 10. Adapter beforeStart hooks\n */\n /** Build the adapter context object (shared across all hooks) */\n private adapterCtx(server?: any): AdapterContext {\n const env = process.env.NODE_ENV ?? 'development'\n return {\n http: this.adapterHttp(),\n app: this.app,\n container: this.container,\n server,\n env,\n isProduction: env === 'production',\n }\n }\n\n /**\n * The engine-agnostic HTTP surface handed to adapters as `ctx.http`. Each\n * call routes through the active runtime over the current `this.app`, so an\n * adapter written against this works under any runtime.\n */\n private adapterHttp(): AdapterHttp {\n return {\n route: (method, path, handler) => {\n const entry: RouteEntry = {\n method,\n path,\n middlewares: [],\n contributorRunner: null,\n handler,\n meta: {},\n }\n this.runtime.mountRoutes(this.app, [{ mountPath: '/', routes: [entry] }])\n },\n mount: (prefix, routes) => {\n this.runtime.mountRoutes(this.app, [{ mountPath: prefix, routes }])\n },\n serveStatic: (prefix, dir) => {\n this.runtime.serveStatic(this.app, prefix, dir)\n },\n use: (mw, opts) => {\n this.runtime.useConnect(this.app, mw, opts)\n },\n }\n }\n\n /** Call an adapter hook, awaiting async hooks and catching errors */\n private async callHook(\n hook: ((ctx: AdapterContext) => void | Promise<void>) | undefined,\n ctx: AdapterContext,\n ): Promise<void> {\n if (!hook) return\n try {\n const result = hook(ctx)\n if (result && typeof (result as Promise<void>).then === 'function') {\n await result\n }\n } catch (err) {\n log.error(err, 'Adapter hook failed')\n }\n }\n\n async setup(): Promise<void> {\n log.debug('Bootstrapping application...')\n\n // Reset module-contributor snapshot at the very top of each\n // setup pass — before anything that could throw — so a partial\n // failure doesn't leave stale entries that getContributors()\n // would surface on the next call. See _moduleContributors field\n // declaration for the use case.\n this._moduleContributors = []\n\n // Collect adapter middleware by phase\n const adapterMw = this.collectAdapterMiddleware()\n\n // Expose the Application instance on the Express app for adapter discovery\n ;(this.app as any).__kickApp = this\n\n const ctx = this.adapterCtx()\n\n // ── 1. Adapter beforeMount hooks ──────────────────────────────────\n for (const adapter of this.adapters) {\n await this.callHook(adapter.beforeMount?.bind(adapter), ctx)\n }\n\n // ── 2. Hardened defaults ──────────────────────────────────────────\n // x-powered-by disable + trust proxy now live in `runtime.createApp()`.\n\n // ── 2a. In-flight request tracking ──────────────────────────────\n this.runtime.useConnect(this.app, this.requestTrackingMiddleware())\n\n // ── 2b. Health check endpoints (before middleware, at root) ──────\n this.mountHealthEndpoints()\n\n // ── 2c. Request scope (AsyncLocalStorage) ────────────────────────\n // Auto-mounted unless the user opted out (`contextStore: 'manual'`)\n // or already included one in their middleware list.\n if (this.shouldAutoMountRequestScope()) {\n this.runtime.useConnect(this.app, requestScopeMiddleware())\n }\n\n // ── 3. Adapter middleware: beforeGlobal ───────────────────────────\n this.mountMiddlewareList(adapterMw.beforeGlobal)\n\n // ── 3b. Plugin registration ──────────────────────────────────────\n for (const plugin of this.plugins) {\n plugin.register?.(this.container)\n }\n\n // ── 3c. Plugin middleware ─────────────────────────────────────────\n for (const plugin of this.plugins) {\n try {\n const mw = plugin.middleware?.() ?? []\n for (const handler of mw) {\n this.runtime.useConnect(this.app, handler)\n }\n } catch (err) {\n log.error(err, `Plugin middleware failed: ${(plugin as any).name ?? 'unknown'}`)\n }\n }\n\n // ── 4. Global middleware ─────────────────────────────────────────\n // Auto-inject helmet unless opted out\n const autoHelmet = this.options.security?.helmet !== false\n if (autoHelmet) {\n try {\n const { helmet: helmetFn } = await import('./middleware/helmet')\n this.runtime.useConnect(this.app, helmetFn())\n } catch {\n // helmet middleware not available — skip silently\n }\n }\n\n const userMiddlewares = this.options.middlewares ?? this.options.middleware\n if (userMiddlewares) {\n // User-declared pipeline — full control\n for (const entry of userMiddlewares) {\n this.mountMiddlewareEntry(entry)\n }\n } else {\n // Sensible defaults when no middleware declared\n this.runtime.useConnect(this.app, requestId())\n // Skip express.json on engines that parse bodies natively (Fastify, h3) —\n // otherwise the body stream is read twice and the engine's parser hangs.\n if (!this.runtime.capabilities.nativeBodyParsing) {\n this.runtime.useConnect(this.app, express.json({ limit: this.options.jsonLimit ?? '1mb' }))\n }\n }\n\n // ── 5. Adapter middleware: afterGlobal ────────────────────────────\n this.mountMiddlewareList(adapterMw.afterGlobal)\n\n // ── 6. Module registration + DI bootstrap ────────────────────────\n // Module collection — the static `modules: [...]` array AND the\n // imperative `setup(registry)` callback feed into the same\n // ordered list. Plugins go first so plugin modules / setup\n // calls run before user code (existing precedence).\n //\n // Order within the registry:\n // 1. plugin static modules (`plugin.modules?()`)\n // 2. plugin setup() calls (in plugin dependsOn-sorted order)\n // 3. user static modules (`options.modules`)\n // 4. user setup() callback\n const moduleRegistry = new MutableModuleRegistry()\n for (const plugin of this.plugins) {\n for (const m of plugin.modules?.() ?? []) moduleRegistry.mount(m)\n plugin.setup?.(moduleRegistry)\n }\n for (const m of this.options.modules) moduleRegistry.mount(m)\n this.options.setup?.(moduleRegistry)\n const allModuleEntries: AppModuleEntry[] = moduleRegistry.entries\n const modules = allModuleEntries.map((entry) => {\n // Discriminate class vs instance: classes are functions whose\n // `prototype` carries the AppModule shape; defineModule output is\n // a plain object with `routes`, `register`, `contributors`. Calling\n // `new` on a plain object throws, so we branch up-front rather\n // than try/catch.\n const mod: AppModule = typeof entry === 'function' ? new (entry as AppModuleClass)() : entry\n // `register()` is optional — modules whose classes are entirely\n // decorator-managed (@Service, @Controller, @Repository) don't need it.\n mod.register?.(this.container)\n return mod\n })\n this.container.bootstrap()\n\n // Register Logger as an injectable singleton so @Inject(Logger)\n // works in constructors. Without this, Logger is just a plain\n // class with static methods and no DI binding.\n if (!this.container.has(Logger)) {\n this.container.registerInstance(Logger, new Logger())\n }\n\n // ── 7. Adapter middleware: beforeRoutes ───────────────────────────\n this.mountMiddlewareList(adapterMw.beforeRoutes)\n\n // ── 8. Mount module routes with versioning ───────────────────────\n const apiPrefix = this.options.apiPrefix ?? '/api'\n const defaultVersion = this.options.defaultVersion ?? 1\n // Opt-in, default off. `logRouteTable` is the current name;\n // `logRoutesTable` is the deprecated alias.\n const shouldLogRoutes = this.options.logRouteTable ?? this.options.logRoutesTable ?? false\n\n // Collect route metadata during mounting (avoids calling mod.routes() twice)\n const mountedRoutes: Array<{ controller: any; mountPath: string }> = []\n\n // Context Contributors (#107) — collect adapter + plugin + global once;\n // per-module sources are computed inside the loop so module isolation is\n // preserved.\n //\n // Plugin contributors merge at the same `'adapter'` precedence as adapter\n // contributors. Plugins are conceptually \"bundles of adapters + extras\",\n // so a plugin that ships a typed contributor without standing up an\n // accompanying adapter behaves identically to one that does.\n const adapterSources: SourcedRegistration[] = []\n for (const adapter of this.adapters) {\n const adapterContribs = adapter.contributors?.() ?? []\n const adapterLabel = adapter.name ?? adapter.constructor.name ?? 'adapter'\n for (const registration of adapterContribs) {\n adapterSources.push({ source: 'adapter', registration, label: adapterLabel })\n }\n }\n for (const plugin of this.plugins) {\n const pluginContribs = plugin.contributors?.() ?? []\n const pluginLabel = plugin.name ?? plugin.constructor?.name ?? 'plugin'\n for (const registration of pluginContribs) {\n adapterSources.push({ source: 'adapter', registration, label: pluginLabel })\n }\n }\n const globalSources: SourcedRegistration[] = (this.options.contributors ?? []).map(\n (registration): SourcedRegistration => ({\n source: 'global',\n registration,\n label: 'bootstrap',\n }),\n )\n\n // Reset module-contributor snapshot at the start of each setup\n // pass so re-running `setup()` (test harnesses, dev-server\n // restarts) doesn't accumulate stale entries.\n this._moduleContributors = []\n\n for (const mod of modules) {\n // Prefer the declared `name` field (set by `defineModule({ name: 'X', ... })`)\n // over `constructor.name`. Factory-built modules are plain objects whose\n // constructor is `Object` — falling straight to `constructor.name` would\n // degrade every label in the DevTools Contributors table to \"Object\".\n const declaredName = (mod as { name?: unknown }).name\n const ctorName = mod.constructor?.name\n const moduleLabel =\n typeof declaredName === 'string' && declaredName.length > 0\n ? declaredName\n : ctorName && ctorName !== 'Object'\n ? ctorName\n : 'module'\n const moduleSources: SourcedRegistration[] = (mod.contributors?.() ?? []).map(\n (registration): SourcedRegistration => ({\n source: 'module',\n registration,\n label: moduleLabel,\n }),\n )\n // Retain registrations for `getContributors()` — these would\n // otherwise be unreachable post-bootstrap (module instances\n // are not kept on the Application).\n for (const src of moduleSources) {\n this._moduleContributors.push({\n registration: src.registration as { key: string; dependsOn: readonly string[] },\n label: moduleLabel,\n })\n }\n\n // Thread per-module + adapter + global sources to buildRoutes via the\n // module-scoped slot. Module setup is sequential, so the slot is\n // race-free. The slot MUST stay populated across both `mod.routes()`\n // AND any framework-driven `buildRoutes(controller)` calls below —\n // when a module returns `{ path, controller }` (auto-derive shape),\n // buildRoutes runs here, not inside routes(). Clearing the slot in\n // the routes()-finally would drop module/adapter/global contributors\n // on the floor and surface as `MissingContributorError` for any\n // class/method-level dependsOn referencing a module-level key.\n _setExternalContributorSources([...moduleSources, ...adapterSources, ...globalSources])\n try {\n const result = mod.routes()\n if (!result) continue // Non-HTTP modules (queues, cron) may return null\n\n const routeSets = Array.isArray(result) ? result : [result]\n\n for (const route of routeSets) {\n const version = route.version ?? defaultVersion\n const mountPath = `${apiPrefix}/v${version}${normalizePath(route.path)}`\n // Common-case `{ path, controller }`: build the engine-neutral route\n // table and let the runtime materialize it natively (Express Router /\n // Fastify routes / …). Adopters who hand-build a `router` (composing\n // multiple controllers, third-party routers) pass it explicitly — that\n // is an Express-specific connect handler, mounted via `useConnect`.\n if (route.router) {\n this.runtime.useConnect(this.app, route.router, { path: mountPath })\n } else if (route.controller) {\n this.runtime.mountRoutes(this.app, [\n { mountPath, routes: buildRouteTable(route.controller) },\n ])\n } else {\n throw moduleRouteMissingControllerError(mountPath)\n }\n\n // Notify adapters (e.g. SwaggerAdapter for OpenAPI spec generation)\n if (route.controller) {\n for (const adapter of this.adapters) {\n try {\n adapter.onRouteMount?.(route.controller, mountPath)\n } catch (err) {\n log.error(err, `adapter.onRouteMount() failed for ${mountPath}`)\n }\n }\n if (shouldLogRoutes) {\n mountedRoutes.push({ controller: route.controller, mountPath })\n }\n }\n }\n } finally {\n _setExternalContributorSources([])\n }\n }\n\n // ── 8b. Log route summary ─────────────────────────────────────────\n if (shouldLogRoutes && mountedRoutes.length > 0) {\n const methodOrder = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']\n const methodRank = (m: string) => {\n const i = methodOrder.indexOf(m)\n return i === -1 ? 99 : i\n }\n\n let totalRoutes = 0\n log.info('Routes:')\n\n for (const { controller, mountPath } of mountedRoutes) {\n const defs: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controller,\n [],\n )\n if (defs.length === 0) continue\n\n const counts: Record<string, number> = {}\n for (const def of defs) {\n const m = def.method.toUpperCase()\n counts[m] = (counts[m] || 0) + 1\n }\n totalRoutes += defs.length\n\n const methods = Object.entries(counts)\n .toSorted(([a], [b]) => methodRank(a) - methodRank(b))\n .map(([m, n]) => `${n} ${m}`)\n .join(', ')\n const name = controller.name || 'Controller'\n log.info(` ${name.padEnd(30)} ${mountPath.padEnd(25)} ${defs.length} routes (${methods})`)\n }\n\n log.info(` Total: ${totalRoutes} routes`)\n }\n\n // ── 9. Adapter middleware: afterRoutes ────────────────────────────\n this.mountMiddlewareList(adapterMw.afterRoutes)\n\n // ── 10. Adapter beforeStart hooks ────────────────────────────────\n // Runs BEFORE error handlers so adapters can still mount Express\n // routes (e.g. `McpAdapter` registers `/_mcp/messages` here). If\n // error handlers were registered first, the notFoundHandler — a\n // catch-all that never calls next() — would pre-empt anything an\n // adapter adds afterwards and adapter-mounted endpoints would 404.\n for (const adapter of this.adapters) {\n await this.callHook(adapter.beforeStart?.bind(adapter), ctx)\n }\n\n // ── 11. Error handlers ───────────────────────────────────────────\n // Last in the chain so any adapter-mounted route (step 10) gets\n // a chance to match before the catch-all 404 fires.\n this.runtime.setNotFound(this.app, this.options.onNotFound ?? notFoundHandler())\n this.runtime.setErrorHandler(this.app, this.options.onError ?? errorHandler())\n }\n\n /** Register modules and DI without starting the HTTP server (used by kick tinker) */\n async registerOnly(): Promise<void> {\n await this.setup()\n }\n\n /**\n * Start the HTTP server.\n *\n * In **dev mode** (Vite plugin active): reuses `globalThis.__kickjs_httpServer`\n * created by Vite. Adapters (WsAdapter, Socket.IO, etc.) receive the real\n * `http.Server` through `afterStart({ server })` — zero adapter changes needed.\n *\n * In **production**: creates its own `http.Server` and binds to the port.\n */\n async start(): Promise<void> {\n await this.setup()\n\n const g = globalThis as any\n\n if (g.__kickjs_httpServer) {\n // ── DEV MODE: Vite owns the http.Server ──────────────────────\n // Don't create a new server or listen — Vite is already listening.\n // Just wire up adapters with the Vite-created server.\n this.httpServer = g.__kickjs_httpServer\n log.debug('Attached to Vite dev server')\n\n // Dev mode suppresses our own SIGINT/SIGTERM handlers (Vite owns\n // the process lifecycle), so a Ctrl+C would otherwise skip graceful\n // shutdown entirely — `adapter.shutdown()`, request draining, and\n // any shutdown logs never run. Expose `shutdown()` on globalThis so\n // the CLI dev server (`kick dev`) can drive it before tearing down\n // Vite. Same process, so the CLI's signal handler can reach this.\n g.__kickjs_app_shutdown = () => this.shutdown()\n\n for (const adapter of this.adapters) {\n const ctx = this.adapterCtx(this.httpServer!)\n await this.callHook(adapter.afterStart?.bind(adapter), ctx)\n }\n\n for (const plugin of this.plugins) {\n await plugin.onReady?.(this.container)\n }\n\n return\n }\n\n // ── PRODUCTION: Create and own the http.Server ─────────────────\n const port = this.options.port ?? parseInt(process.env.PORT || '3000', 10)\n this.httpServer = http.createServer(this.runtime.nodeHandler(this.app))\n\n this.httpServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n log.error(\n `Port ${port} is already in use. Kill the existing process or use a different port:\\n` +\n ` PORT=${port + 1} kick dev\\n` +\n ` lsof -i :${port} # find what's using it\\n` +\n ` kill <PID> # stop it`,\n )\n process.exit(1)\n }\n throw err\n })\n\n // Wrap listen in a Promise so afterStart/onReady errors propagate\n await new Promise<void>((resolve, reject) => {\n this.httpServer!.listen(port, async () => {\n try {\n log.info(`Server running on http://localhost:${port}`)\n\n for (const adapter of this.adapters) {\n const afterCtx = this.adapterCtx(this.httpServer!)\n await this.callHook(adapter.afterStart?.bind(adapter), afterCtx)\n }\n\n // Plugin onReady hooks\n for (const plugin of this.plugins) {\n await plugin.onReady?.(this.container)\n }\n\n resolve()\n } catch (err) {\n reject(err)\n }\n })\n })\n }\n\n /** HMR rebuild: swap Express handler without restarting the server */\n async rebuild(): Promise<void> {\n // Build the new app fully before swapping — if setup() throws,\n // the old app keeps running so the server stays responsive.\n const prevApp = this.app\n const prevContainer = this.container\n // Snapshot the outgoing app's disposables (session/rate-limit store\n // intervals) BEFORE setup() registers the incoming app's. On success the\n // stale set is disposed — without this, every HMR swap leaked one live\n // interval + Map per store. On failure it's restored, and whatever the\n // failed setup() half-registered is disposed instead.\n const staleDisposables = drainDisposables()\n\n try {\n Container.reset()\n this.container = Container.getInstance()\n this.app = this.runtime.createApp({ trustProxy: this.options.trustProxy }) as Express\n await this.setup()\n } catch (err) {\n log.error(err, 'HMR rebuild failed, keeping previous app')\n // Restore previous state so the server stays responsive\n await runDisposables(drainDisposables())\n for (const d of staleDisposables) registerDisposable(d)\n this.app = prevApp\n this.container = prevContainer\n return\n }\n\n await runDisposables(staleDisposables)\n\n if (this.httpServer) {\n this.httpServer.removeAllListeners('request')\n this.httpServer.on('request', this.runtime.nodeHandler(this.app))\n log.debug('HMR: app rebuilt and swapped')\n }\n }\n\n /**\n * Graceful shutdown with request draining.\n *\n * 1. Stops accepting new connections (server.close())\n * 2. Waits for in-flight requests to complete (up to shutdownTimeout)\n * 3. Calls adapter.shutdown() for all registered adapters\n * 4. Force-closes after timeout\n *\n * Safe to call multiple times — subsequent calls return the same promise.\n */\n async shutdown(): Promise<void> {\n // Prevent double-shutdown — return immediately if already initiated\n if (this._shutdownInitiated) {\n log.debug('Shutdown already in progress, skipping duplicate call')\n return\n }\n this._shutdownInitiated = true\n this._draining = true\n\n log.debug('Shutting down — draining in-flight requests...')\n\n const timeoutMs = this.options.shutdownTimeout ?? 30_000\n let timer: ReturnType<typeof setTimeout> | undefined\n\n // Start a force-exit timer if timeout is configured\n const forceExitPromise =\n timeoutMs > 0\n ? new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), timeoutMs)\n timer.unref()\n })\n : new Promise<never>(() => {}) // never resolves — no forced exit\n\n try {\n // Step 1: Stop accepting new connections.\n // server.close() prevents new connections. Its callback fires only when\n // ALL existing connections are fully closed, so we do NOT await it here —\n // we track request draining separately via the tracking middleware.\n if (this.httpServer) {\n this.httpServer.close(() => {})\n }\n\n // Step 2: Wait for in-flight requests to drain (or timeout)\n if (this._inFlightRequests > 0) {\n log.debug(`Waiting for ${this._inFlightRequests} in-flight request(s) to complete...`)\n const drainPromise = new Promise<'drained'>((resolve) => {\n this._drainResolvers.push(() => resolve('drained'))\n })\n\n const result = await Promise.race([drainPromise, forceExitPromise])\n if (result === 'timeout') {\n log.warn(\n `Shutdown timeout (${timeoutMs}ms) reached with ${this._inFlightRequests} request(s) still in-flight, forcing shutdown`,\n )\n } else {\n log.debug('All in-flight requests completed')\n }\n }\n\n // Step 3: Run all plugin + adapter shutdowns concurrently\n const results = await Promise.allSettled([\n ...this.plugins.map((plugin) => Promise.resolve(plugin.shutdown?.())),\n ...this.adapters.map((adapter) => Promise.resolve(adapter.shutdown?.())),\n ])\n for (const result of results) {\n if (result.status === 'rejected') {\n log.error({ err: result.reason }, 'Adapter shutdown failed')\n }\n }\n\n // Step 4: Release framework-owned resources started outside the\n // adapter lifecycle — in-memory session/rate-limit store intervals —\n // and flush any pending container change batch so its debounce timer\n // doesn't outlive the app.\n await disposeAll()\n this.container.flushChanges()\n } finally {\n if (timer) clearTimeout(timer)\n }\n }\n\n /**\n * The engine-native app instance, typed from the active runtime\n * ({@link ActiveRuntime} — Express by default). Prefer this over\n * {@link getExpressApp}; under a non-Express runtime the type follows the\n * augmented registry.\n */\n getRuntimeApp(): ActiveRuntime['app'] {\n return this.app\n }\n\n /**\n * The active HTTP runtime's identity + capabilities — `name` is `'express'`\n * (default), `'fastify'`, or `'h3'`; `capabilities` reports what the engine\n * supports (render / uploads / connectMiddleware / nativeBodyParsing).\n * Surfaced by DevTools and the VS Code extension so tooling can show which\n * engine the app is running on. Cheap, side-effect free.\n */\n getActiveRuntime(): { name: string; capabilities: RuntimeCapabilities } {\n return { name: this.runtime.name, capabilities: this.runtime.capabilities }\n }\n\n /** @deprecated Use {@link getRuntimeApp}. Returns the engine-native app. */\n getExpressApp(): Express {\n return this.app\n }\n\n /** Get registered adapters — used by DevToolsAdapter for peer discovery */\n getAdapters(): AppAdapter[] {\n return this.adapters\n }\n\n /**\n * Get registered plugins — used by DevToolsAdapter for topology\n * introspection (architecture.md §23). Read-only; returns the\n * already-sorted plugin list (post-`dependsOn` topo-sort).\n */\n getPlugins(): readonly KickPlugin[] {\n return this.plugins\n }\n\n /**\n * Snapshot of every Context Contributor reachable from this app —\n * walks **four** registration sites: adapters, plugins, modules,\n * and the bootstrap `contributors` option. The devtools Topology\n * tab consumes this for the Contributors panel; tests + adopters\n * can call it for diagnostics.\n *\n * Per-route (method/class-decorator) registrations are NOT included\n * here — they live on the route registry rather than on any of\n * the four collection sites this method walks. A future RPC will\n * surface them separately.\n *\n * Result shape stays minimal — `{ key, source, label, dependsOn }`\n * — so we don't leak `resolve` closures or other internal fields.\n * `source` matches the five-level precedence union from\n * `ContributorSource` (minus `'method'` / `'class'` which require\n * the per-route walk noted above).\n */\n getContributors(): ReadonlyArray<{\n key: string\n source: 'module' | 'adapter' | 'plugin' | 'global'\n label: string\n dependsOn: readonly string[]\n }> {\n const out: Array<{\n key: string\n source: 'module' | 'adapter' | 'plugin' | 'global'\n label: string\n dependsOn: readonly string[]\n }> = []\n const ingest = (\n list: ReadonlyArray<unknown> | null | undefined,\n source: 'module' | 'adapter' | 'plugin' | 'global',\n label: string,\n ): void => {\n if (!list) return\n for (const reg of list) {\n const r = reg as { key?: unknown; dependsOn?: unknown }\n if (r.key === undefined || r.key === null) continue\n // `key` is typically a string but createToken<T>() yields a\n // branded token object — normalise via tokenName so both\n // surfaces flow through.\n const key = typeof r.key === 'string' ? r.key : tokenName(r.key)\n out.push({\n key,\n source,\n label,\n dependsOn: Array.isArray(r.dependsOn) ? (r.dependsOn as string[]) : [],\n })\n }\n }\n for (const adapter of this.adapters) {\n ingest(adapter.contributors?.(), 'adapter', adapter.name ?? 'adapter')\n }\n for (const plugin of this.plugins) {\n ingest(plugin.contributors?.(), 'plugin', plugin.name ?? 'plugin')\n }\n // Module-level registrations — captured during `setup()` because\n // module instances are not retained on the Application. Walk the\n // snapshot rather than calling `mod.contributors()` here.\n for (const { registration, label } of this._moduleContributors) {\n ingest([registration], 'module', label)\n }\n ingest(this.options.contributors, 'global', 'bootstrap')\n return out\n }\n\n getHttpServer(): http.Server | null {\n return this.httpServer\n }\n\n // ── Internal helpers ────────────────────────────────────────────────\n\n /**\n * Decide whether {@link Application} should auto-mount the\n * {@link requestScopeMiddleware} ALS wrapper.\n *\n * Returns `false` when:\n * - `contextStore: 'manual'` was set (caller manages ALS frames), or\n * - the user-supplied `middleware` list already includes a\n * `requestScopeMiddleware()` (detected via the symbol marker stamped\n * in `middleware/request-scope.ts`).\n *\n * Otherwise `true` — preserves the historical default of \"always wrap\n * requests in an ALS frame\" so existing apps see no behavior change.\n */\n private shouldAutoMountRequestScope(): boolean {\n if (this.options.contextStore === 'manual') return false\n\n const userEntries = this.options.middlewares ?? this.options.middleware ?? []\n for (const entry of userEntries) {\n const handler = typeof entry === 'function' ? entry : entry.handler\n if (isRequestScopeMiddleware(handler)) return false\n }\n return true\n }\n\n private collectAdapterMiddleware() {\n const result = {\n beforeGlobal: [] as AdapterMiddleware[],\n afterGlobal: [] as AdapterMiddleware[],\n beforeRoutes: [] as AdapterMiddleware[],\n afterRoutes: [] as AdapterMiddleware[],\n }\n\n for (const adapter of this.adapters) {\n const entries = adapter.middleware?.() ?? []\n for (const entry of entries) {\n const phase = entry.phase ?? 'afterGlobal'\n result[phase].push(entry)\n }\n }\n\n return result\n }\n\n private mountMiddlewareList(entries: AdapterMiddleware[]): void {\n for (const entry of entries) {\n if (entry.path === undefined) {\n this.runtime.useConnect(this.app, entry.handler)\n continue\n }\n this.runtime.useConnect(this.app, entry.handler, { path: entry.path })\n }\n }\n\n private mountMiddlewareEntry(entry: MiddlewareEntry): void {\n if (typeof entry === 'function') {\n this.runtime.useConnect(this.app, entry)\n } else {\n this.runtime.useConnect(this.app, entry.handler, { path: entry.path })\n }\n }\n\n /** Middleware that tracks in-flight requests for graceful draining */\n private requestTrackingMiddleware(): RequestHandler {\n return (_req, res, next) => {\n this._inFlightRequests++\n const onFinish = () => {\n res.removeListener('finish', onFinish)\n res.removeListener('close', onFinish)\n this._inFlightRequests--\n // If draining and no more in-flight requests, resolve all waiters\n if (this._draining && this._inFlightRequests === 0) {\n for (const resolve of this._drainResolvers) {\n resolve()\n }\n this._drainResolvers = []\n }\n }\n res.on('finish', onFinish)\n res.on('close', onFinish)\n next()\n }\n }\n\n /** Mount /health/live and /health/ready endpoints at the root (no API prefix) */\n private mountHealthEndpoints(): void {\n const http = this.adapterHttp()\n\n http.route('GET', '/health/live', (ctx) => {\n if (this._draining) {\n ctx.res.status(503).json({ status: 'draining', uptime: process.uptime() })\n } else {\n ctx.json({ status: 'ok', uptime: process.uptime() })\n }\n })\n\n http.route('GET', '/health/ready', async (ctx) => {\n if (this._draining) {\n ctx.res.status(503).json({ status: 'draining', checks: [] })\n return\n }\n const adaptersWithHealth = this.adapters.filter((a) => a.onHealthCheck)\n const checks = await Promise.allSettled(adaptersWithHealth.map((a) => a.onHealthCheck!()))\n const results = checks.map((c, i) => {\n if (c.status === 'fulfilled') return c.value\n return { name: adaptersWithHealth[i].name ?? 'unknown', status: 'down' as const }\n })\n const healthy = results.every((r) => r.status === 'up')\n ctx.res.status(healthy ? 200 : 503).json({\n status: healthy ? 'ready' : 'degraded',\n checks: results,\n })\n })\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @forinda/kickjs v6.3.1
|
|
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{METADATA}from"./core/interfaces.mjs";import{createLogger}from"./core/logger.mjs";import{i as getMetaRecord,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,r as getMetaMap,t as getClassMeta}from"./metadata-JG-zrMEb.mjs";import"reflect-metadata";let impl=null;function _setAssetResolver(fn){impl=fn}function resolveAssetViaSlot(namespace,key){if(!impl)throw Error(`@Asset('${namespace}/${key}'): the asset manager is not loaded. Import it (any import from '@forinda/kickjs' loads it) — note @Asset reads from the filesystem and is not supported on edge runtimes.`);return impl(namespace,key)}var KickError=class extends Error{code;summary;cause;fix;docsUrl;context;constructor(init){super(formatKickError(init,{color:!1})),this.name=`KickError`,this.code=init.code,this.summary=init.summary,init.cause!==void 0&&(this.cause=init.cause),init.fix!==void 0&&(this.fix=init.fix),init.docsUrl!==void 0&&(this.docsUrl=init.docsUrl),init.context!==void 0&&(this.context=init.context)}};function ansiEnabled(){return process.env.NO_COLOR?!1:process.env.FORCE_COLOR?!0:process.stderr?.isTTY===!0}function paint(text,code,enabled){return enabled?`[${code}m${text}[0m`:text}const c={red:(s,enabled)=>paint(s,`31`,enabled),bold:(s,enabled)=>paint(s,`1`,enabled),dim:(s,enabled)=>paint(s,`2`,enabled),cyan:(s,enabled)=>paint(s,`36`,enabled),blue:(s,enabled)=>paint(s,`34`,enabled),underline:(s,enabled)=>paint(s,`4`,enabled)};function formatKickError(err,options={}){let color=options.color??ansiEnabled(),lines=[],code=c.bold(c.red(err.code,color),color);if(lines.push(`${code}: ${err.summary}`),err.cause){lines.push(``,` ${c.bold(c.dim(`Cause:`,color),color)}`);for(let line of err.cause.split(`
|
|
12
|
-
`))lines.push(` ${highlightInline(line,color)}`)}if(err.fix){lines.push(``,` ${c.bold(c.dim(`Fix:`,color),color)}`);for(let line of err.fix.split(`
|
|
13
|
-
`))lines.push(` ${highlightInline(line,color)}`)}return err.docsUrl&&(lines.push(``,` ${c.bold(c.dim(`Docs:`,color),color)}`),lines.push(` ${c.underline(c.blue(err.docsUrl,color),color)}`)),lines.join(`
|
|
14
|
-
`)}function highlightInline(line,color){return color?line.replace(/`([^`]+)`/g,(_,inner)=>`\`${c.cyan(inner,!0)}\``):line}const DOCS_BASE=`https://kickjs.app`;function noProviderError(tokenName){return new KickError({code:`KICK001`,summary:`No provider for ${tokenName}`,cause:`\`${tokenName}\` was requested from the DI container but no binding is registered.
|
|
15
|
-
This usually means one of:
|
|
16
|
-
• The class is decorated with @Service() / @Repository() / @Controller(),
|
|
17
|
-
but its enclosing module isn't passed to bootstrap({ modules: [...] }).
|
|
18
|
-
• The class isn't decorated at all (decorators register the binding).
|
|
19
|
-
• You're injecting a token (created with createToken()) that nothing
|
|
20
|
-
provides — add a Container.register(TOKEN, ...) call or a module that
|
|
21
|
-
binds it.`,fix:`If \`${tokenName}\` lives in a module, add the module to bootstrap:
|
|
22
|
-
|
|
23
|
-
bootstrap({
|
|
24
|
-
modules: [
|
|
25
|
-
UsersModule, // add this
|
|
26
|
-
OtherModule,
|
|
27
|
-
],
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
If it's a custom token, register it explicitly:
|
|
31
|
-
|
|
32
|
-
const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')
|
|
33
|
-
Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#registering-services`,context:{token:tokenName}})}function requestScopeMiddlewareMissingError(tokenName){return new KickError({code:`KICK002`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" — request scope middleware not mounted`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` but no AsyncLocalStorage frame is wired.
|
|
34
|
-
The framework's request-scope middleware sets up that frame; without it,
|
|
35
|
-
REQUEST-scoped resolutions have nowhere to cache the per-request instance.`,fix:`Add \`requestScopeMiddleware()\` to your global middleware pipeline,
|
|
36
|
-
or let the framework auto-mount it by using \`contextStore: 'auto'\`
|
|
37
|
-
(the default — only check this if you set it to 'manual' explicitly):
|
|
38
|
-
|
|
39
|
-
bootstrap({
|
|
40
|
-
modules,
|
|
41
|
-
// contextStore: 'auto', // default; no need to set
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
If you set \`contextStore: 'manual'\` deliberately (rare), you own the
|
|
45
|
-
ALS wrapping — call \`requestStore.run({...}, () => ...)\` around the
|
|
46
|
-
code that resolves the binding.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function requestScopeOutsideRequestError(tokenName){return new KickError({code:`KICK003`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" outside an HTTP request`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` and was resolved outside
|
|
47
|
-
any HTTP request context. The request-scope middleware is wired, but
|
|
48
|
-
the call happened before a request started (e.g. in a top-level
|
|
49
|
-
import, a constructor, a cron job, a test without setup).`,fix:`If the caller is request-scoped (controller, request-scoped service),
|
|
50
|
-
make sure the call happens inside the request lifecycle, not at module
|
|
51
|
-
load time.
|
|
52
|
-
|
|
53
|
-
In tests, wrap setup in an ALS frame manually:
|
|
54
|
-
|
|
55
|
-
import { requestStore } from '@forinda/kickjs'
|
|
56
|
-
|
|
57
|
-
requestStore.run(
|
|
58
|
-
{ requestId: 'test', instances: new Map(), values: new Map() },
|
|
59
|
-
() => {
|
|
60
|
-
const svc = container.resolve(MyRequestScopedService)
|
|
61
|
-
// ...
|
|
62
|
-
},
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
If the caller is a SINGLETON, change \`${tokenName}\` to TRANSIENT or
|
|
66
|
-
SINGLETON scope, or inject a factory instead of the instance directly.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function envValueMissingError(envKey){return new KickError({code:`KICK004`,summary:`@Value('${envKey}'): environment variable not set and no default provided`,cause:`\`@Value('${envKey}')\` resolved but the env var isn't present and the
|
|
67
|
-
decorator didn't declare a default. Possible reasons:
|
|
68
|
-
• The variable is genuinely missing from your environment / .env file.
|
|
69
|
-
• Your \`src/env.ts\` calls \`loadEnv(envSchema)\` but \`src/index.ts\`
|
|
70
|
-
doesn't import \`./env\` before \`bootstrap()\`. Without that import
|
|
71
|
-
the schema never runs and ConfigService stays empty — \`@Value\`
|
|
72
|
-
only finds the var via the \`process.env\` fallback.`,fix:`Either set the variable in your shell / .env file:
|
|
73
|
-
|
|
74
|
-
${envKey}=...
|
|
75
|
-
|
|
76
|
-
Or add a default at the decorator:
|
|
77
|
-
|
|
78
|
-
@Value('${envKey}', { default: 'fallback' })
|
|
79
|
-
private value!: string
|
|
80
|
-
|
|
81
|
-
If you're using a schema, make sure \`src/env.ts\` is imported from
|
|
82
|
-
\`src/index.ts\` BEFORE bootstrap runs:
|
|
83
|
-
|
|
84
|
-
// src/index.ts
|
|
85
|
-
import 'reflect-metadata'
|
|
86
|
-
import './env' // ← this line is required
|
|
87
|
-
import { bootstrap } from '@forinda/kickjs'
|
|
88
|
-
// ...`,docsUrl:`${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,context:{envKey}})}function moduleRouteMissingControllerError(mountPath){return new KickError({code:`KICK005`,summary:`Module route at ${mountPath} requires either 'controller' or 'router'`,cause:`A module's \`routes()\` returned an entry for \`${mountPath}\` but didn't
|
|
89
|
-
specify how to build the router. The framework needs either a decorated
|
|
90
|
-
controller class (it calls \`buildRoutes()\` internally) or a hand-built
|
|
91
|
-
Express Router.`,fix:`Pass \`controller:\` for the common case:
|
|
92
|
-
|
|
93
|
-
routes() {
|
|
94
|
-
return {
|
|
95
|
-
path: '${mountPath}',
|
|
96
|
-
controller: UsersController,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
Or pass \`router:\` for full control:
|
|
101
|
-
|
|
102
|
-
routes() {
|
|
103
|
-
return {
|
|
104
|
-
path: '${mountPath}',
|
|
105
|
-
router: myExpressRouter,
|
|
106
|
-
}
|
|
107
|
-
}`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{mountPath}})}const INJECTION_TOKEN=Symbol.for(`@kickjs/InjectionToken`);function createToken(name){return Object.freeze({[INJECTION_TOKEN]:!0,name})}function isInjectionToken(value){return typeof value==`object`&&!!value&&value[INJECTION_TOKEN]===!0}const log=createLogger(`Container`);function createReg(fields){return{kind:`unknown`,resolveCount:0,postConstructStatus:`skipped`,dependencies:[],...fields}}function tokenName(token){return typeof token==`symbol`?token.toString():isInjectionToken(token)?token.name:token?.name||String(token)}function getPersistentStore(){return globalThis.__kickjs_persistent||(globalThis.__kickjs_persistent={factories:[],instances:[],resolvedInstances:new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=new Map;resolving=new Set;changeListeners=new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _envResolver=null;static _requestStoreProvider=null;static getInstance(){return Container.instance||=new Container,Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container._onReset?.(Container.instance);let store=getPersistentStore();for(let{token,factory,scope}of store.factories)if(!Container.instance.has(token)){let name=tokenName(token),existing=store.resolvedInstances.get(name);Container.instance.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existing,resolveCount:+!!existing,postConstructStatus:existing?`completed`:`skipped`}))}for(let{token,instance}of store.instances)Container.instance.has(token)||Container.instance.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1}))}static create(){return new Container}register(token,target,scope=`singleton`){let kind=getClassMeta(METADATA.CLASS_KIND,target,`unknown`),reg=createReg({target,scope,kind,dependencies:this.extractDependencies(target)});this.registrations.set(token,reg),typeof token==`function`&&token.name&&this.registrations.set(`__hmr__${token.name}`,reg),this.emit(token,`registered`,kind)}registerFactory(token,factory,scope=`singleton`){let store=getPersistentStore(),name=tokenName(token),factoryUnchanged=store.factories.find(r=>tokenName(r.token)===name)?.factory===factory,existingInstance=factoryUnchanged?store.resolvedInstances.get(name):void 0;factoryUnchanged||store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`}));let idx=store.factories.findIndex(r=>tokenName(r.token)===name);idx>=0?store.factories[idx]={token,factory,scope}:store.factories.push({token,factory,scope}),this.emit(name,`registered`,`factory`)}registerInstance(token,instance){let store=getPersistentStore(),name=tokenName(token);this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),store.resolvedInstances.set(name,instance);let idx=store.instances.findIndex(r=>tokenName(r.token)===name);idx>=0?store.instances[idx]={token,instance}:store.instances.push({token,instance}),this.emit(name,`registered`,`instance`)}has(token){return this.registrations.has(token)}getRegistrations(){let entries=[];for(let[token,reg]of this.registrations)entries.push({token:tokenName(token),scope:reg.scope,kind:reg.kind,instantiated:reg.scope===`singleton`?reg.instance!==void 0:reg.resolveCount>0,resolveCount:reg.resolveCount,lastResolvedAt:reg.lastResolvedAt,firstResolvedAt:reg.firstResolvedAt,resolveDurationMs:reg.resolveDurationMs,postConstructStatus:reg.postConstructStatus,dependencies:reg.dependencies});return entries}resolve(token){let reg=this.registrations.get(token);if(!reg&&typeof token==`function`&&token.name&&(reg=this.registrations.get(`__hmr__${token.name}`)),!reg)throw noProviderError(tokenName(token));if(reg.scope===`singleton`&®.instance!==void 0)return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),reg.instance;if(reg.scope===`request`){if(!Container._requestStoreProvider)throw requestScopeMiddlewareMissingError(tokenName(token));let store=Container._requestStoreProvider();if(!store)throw requestScopeOutsideRequestError(tokenName(token));if(store.values.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.values.get(token);if(store.instances.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.instances.get(token);if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance,getPersistentStore().resolvedInstances.set(tokenName(token),instance)),this.emit(token,`resolved`,reg.kind),instance}if(this.resolving.has(token)){let chain=[...this.resolving].map(tokenName);throw chain.push(tokenName(token)),Error(`Circular dependency detected: ${chain.join(` -> `)}`)}this.resolving.add(token);try{let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance),this.emit(token,`resolved`,reg.kind),instance}finally{this.resolving.delete(token)}}bootstrap(){}onChange(callback){return this.changeListeners.add(callback),()=>this.changeListeners.delete(callback)}invalidate(token){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!getPersistentStore().resolvedInstances.has(name)){reg.instance=void 0,reg.resolveCount=0,reg.postConstructStatus=`skipped`,this.emit(name,`invalidated`,reg.kind);for(let[depToken,depReg]of this.registrations)depReg.dependencies.includes(name)&&this.invalidate(depToken)}}emit(rawToken,event,kind){if(this.changeListeners.size===0)return;let token=tokenName(rawToken),existing=this.pendingChanges.find(p=>p.token===token&&p.event===event);existing?existing.timestamp=Date.now():this.pendingChanges.push({token,event,kind,timestamp:Date.now()}),this.notifyTimer&&clearTimeout(this.notifyTimer),this.notifyTimer=setTimeout(()=>{let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}},Container.DEBOUNCE_MS),this.notifyTimer.unref?.()}flushChanges(){this.notifyTimer&&=(clearTimeout(this.notifyTimer),null);let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}}buildPlan(reg){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,reg.target,[]),injectTokens=getMetaRecord(METADATA.INJECT,reg.target),maxInjectIndex=Object.keys(injectTokens).length>0?Math.max(...Object.keys(injectTokens).map(Number))+1:0,paramCount=Math.max(paramTypes.length,maxInjectIndex),ctorTokens=[];for(let index=0;index<paramCount;index++){let token=injectTokens[index]||paramTypes[index];if(!token)throw Error(`Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). Add @Inject(Token) to every constructor parameter.`);if(reg.scope===`singleton`){let depReg=this.registrations.get(token);if(depReg&&depReg.scope===`request`)throw Error(`Cannot inject REQUEST-scoped "${tokenName(token)}" into SINGLETON "${tokenName(reg.target)}". Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`)}ctorTokens.push(token)}let autowiredProps=getMetaMap(METADATA.AUTOWIRED,reg.target.prototype),autowired=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,reg.target.prototype,prop);resolvedToken&&autowired.push([prop,resolvedToken])}let values=[...getMetaMap(METADATA.VALUE,reg.target.prototype).entries()],assetProps=getMetaMap(METADATA.ASSET,reg.target.prototype),assets=[];for(let[prop,config]of assetProps){let slashIdx=config.assetKey.indexOf(`/`);if(slashIdx===-1)throw Error(`@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`);assets.push([prop,{namespace:config.assetKey.slice(0,slashIdx),key:config.assetKey.slice(slashIdx+1)}])}return{ctorTokens,autowired,values,assets,postConstruct:getClassMetaOrUndefined(METADATA.POST_CONSTRUCT,reg.target.prototype)}}createInstance(reg){let plan=reg.plan??=this.buildPlan(reg),args=[];for(let token of plan.ctorTokens)args.push(this.resolve(token));let instance=new reg.target(...args);this.injectProperties(instance,plan);let postConstruct=plan.postConstruct;if(postConstruct&&typeof instance[postConstruct]==`function`)try{let result=instance[postConstruct]();result&&typeof result.then==`function`?(reg.postConstructStatus=`pending`,result.then(()=>{reg.postConstructStatus=`completed`}).catch(()=>{reg.postConstructStatus=`failed`}),log.warn(`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise but the container is synchronous. The async operation will not be awaited.`)):reg.postConstructStatus=`completed`}catch(err){reg.postConstructStatus=`failed`,log.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[...new Set([...ctorDeps,...propDeps])]}injectProperties(instance,plan){for(let[prop,resolvedToken]of plan.autowired)Object.defineProperty(instance,prop,{get:()=>{let value=this.resolve(resolvedToken);return this.registrations.get(resolvedToken)?.scope===`singleton`&&Object.defineProperty(instance,prop,{value,enumerable:!0,configurable:!0,writable:!1}),value},enumerable:!0,configurable:!0});for(let[prop,config]of plan.values)Object.defineProperty(instance,prop,{get(){if(Container._envResolver){let val=Container._envResolver(config.envKey);if(val!==void 0)return val}let val=process.env[config.envKey];if(val!==void 0)return val;if(config.defaultValue!==void 0)return config.defaultValue;throw envValueMissingError(config.envKey)},enumerable:!0,configurable:!0});for(let[prop,{namespace,key}]of plan.assets)Object.defineProperty(instance,prop,{get(){return resolveAssetViaSlot(namespace,key)},enumerable:!0,configurable:!0})}};export{isInjectionToken as a,formatKickError as c,createToken as i,_setAssetResolver as l,tokenName as n,moduleRouteMissingControllerError as o,INJECTION_TOKEN as r,KickError as s,Container as t};
|
|
108
|
-
//# sourceMappingURL=container-BkAX8ftP.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container-BkAX8ftP.mjs","names":["resolveAsset"],"sources":["../src/core/asset-resolver-slot.ts","../src/core/kick-error.ts","../src/core/kick-errors.ts","../src/core/token.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Indirection slot between the DI container's `@Asset` property injection\n * and the asset manager. The container must NOT import `./assets` directly —\n * its eager `node:fs`/`node:path` imports would poison the edge-safe\n * `@forinda/kickjs/web` entry graph (every graph includes the container).\n *\n * `./assets` registers the real resolver at module scope, so any node app\n * that imports the asset manager (the core barrel does) gets full `@Asset`\n * behavior; an edge bundle that never imports it gets a clear error on\n * first `@Asset` property access instead of a broken bundle.\n */\n\ntype AssetResolver = (namespace: string, key: string) => unknown\n\nlet impl: AssetResolver | null = null\n\n/** Called by `./assets` at module scope to install the real resolver. */\nexport function _setAssetResolver(fn: AssetResolver): void {\n impl = fn\n}\n\n/** Resolve an asset through the slot — throws when no asset manager is loaded. */\nexport function resolveAssetViaSlot(namespace: string, key: string): unknown {\n if (!impl) {\n throw new Error(\n `@Asset('${namespace}/${key}'): the asset manager is not loaded. ` +\n \"Import it (any import from '@forinda/kickjs' loads it) — note @Asset \" +\n 'reads from the filesystem and is not supported on edge runtimes.',\n )\n }\n return impl(namespace, key)\n}\n","/**\n * Structured framework error with code, cause, and actionable fix hint.\n *\n * The goal is \"the developer who hits this knows what to do next.\" A\n * cryptic `Error('No binding found for: UserService')` becomes a\n * multi-line explanation pointing at the likely cause, the exact code\n * change to apply, and a docs link.\n *\n * `.message` carries the full multi-line plain-text body so Node's\n * default `Error.toString()` and unhandled-exception printing still\n * surface the helpful version. Call {@link formatKickError} to render a\n * colorized version for terminals.\n *\n * Per-error factories live in `kick-errors.ts` — those are the public\n * entry points framework code uses. `KickError` is exported so adopters\n * can build their own structured errors using the same shape.\n */\nexport interface KickErrorInit {\n /** Short stable identifier (e.g. `KICK001`, `NO_PROVIDER`). */\n code: string\n /** One-line headline. */\n summary: string\n /** Multi-line explanation of why this happened. */\n cause?: string\n /** Multi-line actionable fix — show exact code if possible. */\n fix?: string\n /** Absolute URL to the docs page that covers this error. */\n docsUrl?: string\n /** Arbitrary structured fields for log consumers / debug tooling. */\n context?: Record<string, unknown>\n}\n\nexport class KickError extends Error {\n public readonly code: string\n public readonly summary: string\n public readonly cause?: string\n public readonly fix?: string\n public readonly docsUrl?: string\n public readonly context?: Record<string, unknown>\n\n constructor(init: KickErrorInit) {\n super(formatKickError(init, { color: false }))\n this.name = 'KickError'\n this.code = init.code\n this.summary = init.summary\n if (init.cause !== undefined) this.cause = init.cause\n if (init.fix !== undefined) this.fix = init.fix\n if (init.docsUrl !== undefined) this.docsUrl = init.docsUrl\n if (init.context !== undefined) this.context = init.context\n }\n}\n\n// ── ANSI colour helpers ────────────────────────────────────────────────\n\n/**\n * `true` when the runtime can render ANSI escape codes on stderr.\n * Honors `NO_COLOR` (https://no-color.org) and `FORCE_COLOR`.\n */\nfunction ansiEnabled(): boolean {\n if (process.env.NO_COLOR) return false\n if (process.env.FORCE_COLOR) return true\n // Node sets `isTTY` on the writable streams when stdout/stderr is a\n // terminal. KickError output goes to stderr conceptually (it's an\n // error), so we follow that stream's affordances.\n return process.stderr?.isTTY === true\n}\n\n// Use the Unicode-escape form (`\u001b`) rather than the hex-escape (`\\x1b`)\n// for the ANSI ESC byte — both produce the same character, but oxlint's\n// `no-control-character` rule flags the hex form as \"unexpected control\n// character in string literal\". Functionally identical, lint-clean.\nconst ESC = '\u001b['\nconst RESET = `${ESC}0m`\n\nfunction paint(text: string, code: string, enabled: boolean): string {\n return enabled ? `${ESC}${code}m${text}${RESET}` : text\n}\n\nconst c = {\n red: (s: string, enabled: boolean) => paint(s, '31', enabled),\n bold: (s: string, enabled: boolean) => paint(s, '1', enabled),\n dim: (s: string, enabled: boolean) => paint(s, '2', enabled),\n cyan: (s: string, enabled: boolean) => paint(s, '36', enabled),\n blue: (s: string, enabled: boolean) => paint(s, '34', enabled),\n underline: (s: string, enabled: boolean) => paint(s, '4', enabled),\n}\n\n// ── Formatter ─────────────────────────────────────────────────────────\n\nexport interface FormatKickErrorOptions {\n /**\n * Render ANSI colour codes. Defaults to TTY detection (`NO_COLOR` /\n * `FORCE_COLOR` env vars honoured). Pass `false` for log files,\n * structured loggers, or HTTP response bodies.\n */\n color?: boolean\n}\n\n/**\n * Render a {@link KickErrorInit} (or {@link KickError} instance) into a\n * multi-line human-readable string. Identical layout regardless of\n * colour — the colour flag only toggles ANSI codes.\n */\nexport function formatKickError(\n err: KickErrorInit | KickError,\n options: FormatKickErrorOptions = {},\n): string {\n const color = options.color ?? ansiEnabled()\n const lines: string[] = []\n\n // Headline: `KICK001: No provider for UserService`\n const code = c.bold(c.red(err.code, color), color)\n lines.push(`${code}: ${err.summary}`)\n\n if (err.cause) {\n lines.push('', ` ${c.bold(c.dim('Cause:', color), color)}`)\n for (const line of err.cause.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.fix) {\n lines.push('', ` ${c.bold(c.dim('Fix:', color), color)}`)\n for (const line of err.fix.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.docsUrl) {\n lines.push('', ` ${c.bold(c.dim('Docs:', color), color)}`)\n lines.push(` ${c.underline(c.blue(err.docsUrl, color), color)}`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Highlight inline `code spans` in a line of cause/fix text. Anything\n * between backticks gets cyan when colour is enabled; backticks stay\n * in the output so the plain-text rendering still distinguishes\n * literals.\n */\nfunction highlightInline(line: string, color: boolean): string {\n if (!color) return line\n return line.replace(/`([^`]+)`/g, (_, inner: string) => `\\`${c.cyan(inner, true)}\\``)\n}\n","/**\n * Catalog of framework errors with structured cause + fix hints.\n *\n * Each factory function returns a {@link KickError} with a stable\n * `code` (KICK001, KICK002, …), a human-readable summary, an\n * explanation of the likely cause, an actionable fix, and a docs URL.\n *\n * Adding a new error: pick the next free code, add the factory below,\n * call it from the framework site that used to throw a bare `Error`.\n * Keep the public function name descriptive — `noProviderError`,\n * `envValueMissingError` — so call sites grep cleanly.\n *\n * Codes are stable. Reordering or renumbering breaks adopters whose\n * tools key on `err.code`.\n */\nimport { KickError } from './kick-error'\n\nconst DOCS_BASE = 'https://kickjs.app'\n\n// ── DI container ──────────────────────────────────────────────────────\n\n/**\n * KICK001 — No provider registered for a token.\n *\n * Thrown by `Container.resolve()` when no binding can be found for the\n * requested token. The most common cause is forgetting to add the\n * enclosing module to `bootstrap({ modules })`.\n */\nexport function noProviderError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK001',\n summary: `No provider for ${tokenName}`,\n cause: `\\`${tokenName}\\` was requested from the DI container but no binding is registered.\nThis usually means one of:\n • The class is decorated with @Service() / @Repository() / @Controller(),\n but its enclosing module isn't passed to bootstrap({ modules: [...] }).\n • The class isn't decorated at all (decorators register the binding).\n • You're injecting a token (created with createToken()) that nothing\n provides — add a Container.register(TOKEN, ...) call or a module that\n binds it.`,\n fix: `If \\`${tokenName}\\` lives in a module, add the module to bootstrap:\n\n bootstrap({\n modules: [\n UsersModule, // add this\n OtherModule,\n ],\n })\n\nIf it's a custom token, register it explicitly:\n\n const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')\n Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#registering-services`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK002 — REQUEST-scoped binding resolved without a request store\n * provider configured.\n */\nexport function requestScopeMiddlewareMissingError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK002',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" — request scope middleware not mounted`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` but no AsyncLocalStorage frame is wired.\nThe framework's request-scope middleware sets up that frame; without it,\nREQUEST-scoped resolutions have nowhere to cache the per-request instance.`,\n fix: `Add \\`requestScopeMiddleware()\\` to your global middleware pipeline,\nor let the framework auto-mount it by using \\`contextStore: 'auto'\\`\n(the default — only check this if you set it to 'manual' explicitly):\n\n bootstrap({\n modules,\n // contextStore: 'auto', // default; no need to set\n })\n\nIf you set \\`contextStore: 'manual'\\` deliberately (rare), you own the\nALS wrapping — call \\`requestStore.run({...}, () => ...)\\` around the\ncode that resolves the binding.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK003 — REQUEST-scoped binding resolved outside any HTTP request\n * context (no ALS store active).\n */\nexport function requestScopeOutsideRequestError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK003',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" outside an HTTP request`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` and was resolved outside\nany HTTP request context. The request-scope middleware is wired, but\nthe call happened before a request started (e.g. in a top-level\nimport, a constructor, a cron job, a test without setup).`,\n fix: `If the caller is request-scoped (controller, request-scoped service),\nmake sure the call happens inside the request lifecycle, not at module\nload time.\n\nIn tests, wrap setup in an ALS frame manually:\n\n import { requestStore } from '@forinda/kickjs'\n\n requestStore.run(\n { requestId: 'test', instances: new Map(), values: new Map() },\n () => {\n const svc = container.resolve(MyRequestScopedService)\n // ...\n },\n )\n\nIf the caller is a SINGLETON, change \\`${tokenName}\\` to TRANSIENT or\nSINGLETON scope, or inject a factory instead of the instance directly.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n// ── Config / env wiring ───────────────────────────────────────────────\n\n/**\n * KICK004 — `@Value('X')` resolved but the env var isn't set and no\n * default was provided.\n *\n * This is the canonical \"you forgot to wire env\" symptom — surfaces\n * when ConfigService can't find the key. The fix usually involves\n * either setting the env var or wiring `loadEnv(envSchema)` at startup.\n */\nexport function envValueMissingError(envKey: string): KickError {\n return new KickError({\n code: 'KICK004',\n summary: `@Value('${envKey}'): environment variable not set and no default provided`,\n cause: `\\`@Value('${envKey}')\\` resolved but the env var isn't present and the\ndecorator didn't declare a default. Possible reasons:\n • The variable is genuinely missing from your environment / .env file.\n • Your \\`src/env.ts\\` calls \\`loadEnv(envSchema)\\` but \\`src/index.ts\\`\n doesn't import \\`./env\\` before \\`bootstrap()\\`. Without that import\n the schema never runs and ConfigService stays empty — \\`@Value\\`\n only finds the var via the \\`process.env\\` fallback.`,\n fix: `Either set the variable in your shell / .env file:\n\n ${envKey}=...\n\nOr add a default at the decorator:\n\n @Value('${envKey}', { default: 'fallback' })\n private value!: string\n\nIf you're using a schema, make sure \\`src/env.ts\\` is imported from\n\\`src/index.ts\\` BEFORE bootstrap runs:\n\n // src/index.ts\n import 'reflect-metadata'\n import './env' // ← this line is required\n import { bootstrap } from '@forinda/kickjs'\n // ...`,\n docsUrl: `${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,\n context: { envKey },\n })\n}\n\n// ── Module setup ──────────────────────────────────────────────────────\n\n/**\n * KICK005 — A module's `routes()` return value declares a mount path\n * but provides neither a `controller` class nor a pre-built `router`.\n */\nexport function moduleRouteMissingControllerError(mountPath: string): KickError {\n return new KickError({\n code: 'KICK005',\n summary: `Module route at ${mountPath} requires either 'controller' or 'router'`,\n cause: `A module's \\`routes()\\` returned an entry for \\`${mountPath}\\` but didn't\nspecify how to build the router. The framework needs either a decorated\ncontroller class (it calls \\`buildRoutes()\\` internally) or a hand-built\nExpress Router.`,\n fix: `Pass \\`controller:\\` for the common case:\n\n routes() {\n return {\n path: '${mountPath}',\n controller: UsersController,\n }\n }\n\nOr pass \\`router:\\` for full control:\n\n routes() {\n return {\n path: '${mountPath}',\n router: myExpressRouter,\n }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { mountPath },\n })\n}\n","/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\n// Slot indirection, NOT `./assets` — that module's eager node:fs import\n// would poison the edge-safe web entry graph (see asset-resolver-slot.ts).\nimport { resolveAssetViaSlot as resolveAsset } from './asset-resolver-slot'\nimport {\n envValueMissingError,\n noProviderError,\n requestScopeMiddlewareMissingError,\n requestScopeOutsideRequestError,\n} from './kick-errors'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n /** Lazily built instantiation plan — see {@link Container.buildPlan}. */\n plan?: InstantiationPlan\n}\n\n/**\n * Precomputed instantiation recipe for a class. Class metadata is immutable\n * after definition, so the Reflect reads (5 per create) and the derived\n * structures only need computing ONCE — previously they re-ran on every\n * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.\n */\ninterface InstantiationPlan {\n /** Ordered, validated constructor parameter tokens. */\n ctorTokens: any[]\n /** `@Autowired` properties: [property, resolved token]. */\n autowired: Array<[string, any]>\n /** `@Value` properties: [property, env config]. */\n values: Array<[string, { envKey: string; defaultValue?: any }]>\n /** `@Asset` properties: [property, pre-split namespace/key]. */\n assets: Array<[string, { namespace: string; key: string }]>\n /** `@PostConstruct` method name, if any. */\n postConstruct?: string | symbol\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n return new Container()\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(token, 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n const existingEntry = store.factories.find((r) => tokenName(r.token) === name)\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance = factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (!factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw noProviderError(tokenName(token))\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw requestScopeMiddlewareMissingError(tokenName(token))\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw requestScopeOutsideRequestError(tokenName(token))\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module re-evaluation\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /**\n * Batch-emit a change event (debounced, flushed after 50ms of quiet).\n * Takes the RAW token — `tokenName()` is only computed after the\n * zero-listener guard, so the hot cached-singleton resolve path never pays\n * the name derivation when nobody is watching (the production default).\n */\n private emit(rawToken: any, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Zero-listener fast path. Nobody subscribes to container changes unless\n // DevTools (or a test) calls `onChange` — the production default. Without\n // this guard, every `resolve()` (including the hot cached-singleton path)\n // would scan `pendingChanges`, push an entry, and reschedule a `setTimeout`\n // only for the eventual flush to iterate an empty listener set and discard\n // the batch. Short-circuiting here keeps `resolve()` allocation- and\n // timer-free when no one is watching. When a listener attaches later,\n // subsequent emits work exactly as before — past resolves had no observer\n // to notify anyway.\n if (this.changeListeners.size === 0) return\n const token = tokenName(rawToken)\n\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n // Don't let a pending debounce window hold the event loop open — flush is\n // best-effort telemetry, not critical work.\n this.notifyTimer.unref?.()\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n /**\n * Build the one-time instantiation plan for a registration. Runs on the\n * FIRST createInstance for the class — the metadata it reads is immutable\n * after class definition, so every later create (REQUEST scope: every\n * request) skips all Reflect reads and re-derivation.\n *\n * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here\n * too: it's a static property of the (parent, dep) registration pair, so\n * checking it once at plan build is equivalent to the old per-create loop.\n */\n private buildPlan(reg: Registration): InstantiationPlan {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n\n // When emitDecoratorMetadata is unavailable (tsx, ts-node without\n // SWC), paramTypes is empty even though @Inject set tokens on\n // specific constructor indices. Derive the constructor arity from\n // the @Inject metadata so constructor injection still works.\n const maxInjectIndex =\n Object.keys(injectTokens).length > 0\n ? Math.max(...Object.keys(injectTokens).map(Number)) + 1\n : 0\n const paramCount = Math.max(paramTypes.length, maxInjectIndex)\n\n const ctorTokens: any[] = []\n for (let index = 0; index < paramCount; index++) {\n const token = injectTokens[index] || paramTypes[index]\n if (!token) {\n throw new Error(\n `Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. ` +\n `No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). ` +\n `Add @Inject(Token) to every constructor parameter.`,\n )\n }\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n throw new Error(\n `Cannot inject REQUEST-scoped \"${tokenName(token)}\" into SINGLETON \"${tokenName(reg.target)}\". ` +\n `Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`,\n )\n }\n }\n ctorTokens.push(token)\n }\n\n // @Autowired properties — resolve each property's token once.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, reg.target.prototype)\n const autowired: InstantiationPlan['autowired'] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, reg.target.prototype, prop)\n if (resolvedToken) autowired.push([prop, resolvedToken])\n }\n\n // @Value properties.\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n reg.target.prototype,\n )\n const values: InstantiationPlan['values'] = [...valueProps.entries()]\n\n // @Asset properties — pre-split namespace/key (and fail fast on bad keys).\n const assetProps = getMetaMap<string, { assetKey: string }>(\n METADATA.ASSET,\n reg.target.prototype,\n )\n const assets: InstantiationPlan['assets'] = []\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n assets.push([\n prop,\n { namespace: config.assetKey.slice(0, slashIdx), key: config.assetKey.slice(slashIdx + 1) },\n ])\n }\n\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n\n return { ctorTokens, autowired, values, assets, postConstruct }\n }\n\n private createInstance(reg: Registration): any {\n const plan = (reg.plan ??= this.buildPlan(reg))\n\n const args: any[] = []\n for (const token of plan.ctorTokens) {\n args.push(this.resolve(token))\n }\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired / @Value / @Asset\n this.injectProperties(instance, plan)\n\n // @PostConstruct lifecycle hook\n const postConstruct = plan.postConstruct\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, plan: InstantiationPlan): void {\n // @Autowired — lazy DI property injection. The getter re-resolves per\n // access by design (a SINGLETON may lazily read a REQUEST-scoped dep at\n // call time). For SINGLETON deps that laziness buys nothing after the\n // first read — the container returns the same cached instance forever —\n // so the getter memoizes itself into a plain data property then. REQUEST\n // and TRANSIENT deps keep the live getter (memoizing would pin the first\n // request's instance across requests).\n for (const [prop, resolvedToken] of plan.autowired) {\n Object.defineProperty(instance, prop, {\n get: () => {\n const value = this.resolve(resolvedToken)\n const depReg = this.registrations.get(resolvedToken)\n if (depReg?.scope === Scope.SINGLETON) {\n Object.defineProperty(instance, prop, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return value\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Value — lazy environment variable injection\n for (const [prop, config] of plan.values) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw envValueMissingError(config.envKey)\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection (assets-plan.md). Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // namespace/key were pre-split at plan build.\n for (const [prop, { namespace, key }] of plan.assets) {\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { T as RequestContext } from "./adapter-stSpsUHs.mjs";
|
|
3
|
-
|
|
4
|
-
//#region src/http/reply.d.ts
|
|
5
|
-
declare const REPLY_BRAND: unique symbol;
|
|
6
|
-
/**
|
|
7
|
-
* A status-carrying response payload for return-value handlers. Create via
|
|
8
|
-
* {@link reply} — the brand is what runtimes detect, and the generics are
|
|
9
|
-
* what the `kick/routes` response typegen unwraps (`Reply<S, T>` → `T`).
|
|
10
|
-
*/
|
|
11
|
-
interface Reply<S extends number = number, T = unknown> {
|
|
12
|
-
readonly [REPLY_BRAND]: true;
|
|
13
|
-
readonly status: S;
|
|
14
|
-
readonly body: T;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Wrap a return-value handler's payload with an explicit status code:
|
|
18
|
-
*
|
|
19
|
-
* ```ts
|
|
20
|
-
* @Post('/')
|
|
21
|
-
* async create(ctx: RequestContext) {
|
|
22
|
-
* return reply(201, await this.tasks.create(ctx.body))
|
|
23
|
-
* }
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
declare function reply<S extends number, T>(status: S, body: T): Reply<S, T>;
|
|
27
|
-
declare namespace reply {
|
|
28
|
-
var created: <T>(body: T) => Reply<201, T>;
|
|
29
|
-
var accepted: <T>(body: T) => Reply<202, T>;
|
|
30
|
-
var noContent: () => Reply<204, undefined>;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* The statically-inferred response type of a controller handler — what
|
|
34
|
-
* `kick typegen` emits into `KickRoutes[...].response`:
|
|
35
|
-
*
|
|
36
|
-
* - `Awaited<ReturnType>` of the method
|
|
37
|
-
* - `Reply<S, T>` unwraps to `T`
|
|
38
|
-
* - `void`/`undefined` members are dropped (imperative `ctx.json` branches);
|
|
39
|
-
* a handler that never returns a value stays `unknown`, matching the
|
|
40
|
-
* pre-inference emission
|
|
41
|
-
*
|
|
42
|
-
* ```ts
|
|
43
|
-
* type R = InferHandlerResponse<UsersController['get']> // → User
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
type InferHandlerResponse<H> = H extends ((...args: never[]) => infer R) ? [Exclude<Awaited<R>, void | undefined>] extends [never] ? unknown : UnwrapReply<Exclude<Awaited<R>, void | undefined>> : unknown;
|
|
47
|
-
type UnwrapReply<A> = A extends Reply<number, infer B> ? B : A;
|
|
48
|
-
declare function isReply(value: unknown): value is Reply;
|
|
49
|
-
/**
|
|
50
|
-
* Send a handler's RETURN value when the pipeline finished without anything
|
|
51
|
-
* having written a response. Callers guard on "driver/response unsettled" —
|
|
52
|
-
* this only decides how to send:
|
|
53
|
-
*
|
|
54
|
-
* - `undefined` → not handled (returns false; runtime keeps today's behavior)
|
|
55
|
-
* - `Reply` wrapper → status + body (204 → `ctx.noContent()`)
|
|
56
|
-
* - anything else → `ctx.json(value)` (200)
|
|
57
|
-
*
|
|
58
|
-
* Returns true when a response was produced.
|
|
59
|
-
*/
|
|
60
|
-
declare function applyHandlerResult(ctx: RequestContext, result: unknown): boolean;
|
|
61
|
-
//#endregion
|
|
62
|
-
export { reply as a, isReply as i, Reply as n, applyHandlerResult as r, InferHandlerResponse as t };
|
|
63
|
-
//# sourceMappingURL=reply-2CvBP_Sl.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"reply-2CvBP_Sl.d.mts","names":[],"sources":["../src/http/reply.ts"],"mappings":";;;;cAeM,WAAA;;AALyC;;;;UAY9B,KAAA;EAAA,UACL,WAAA;EAAA,SACD,MAAA,EAAQ,CAAA;EAAA,SACR,IAAA,EAAM,CAAA;AAAA;;;;;;;;;;;iBAaD,KAAA,sBAA2B,MAAA,EAAQ,CAAA,EAAG,IAAA,EAAM,CAAA,GAAI,KAAA,CAAM,CAAA,EAAG,CAAA;AAAA,kBAAzD,KAAA;EAAA,iBAKE,IAAA,EAAQ,CAAA,KAAI,KAAA,MAAW,CAAA;EAAA,kBAEtB,IAAA,EAAQ,CAAA,KAAI,KAAA,MAAW,CAAA;EAAA,qBAEpB,KAAA;AAAA;;;;;;;;;;;;;;;KAgBV,oBAAA,MAA0B,CAAA,cAAc,IAAA,0BAC/C,OAAA,CAAQ,OAAA,CAAQ,CAAA,kDAEf,WAAA,CAAY,OAAA,CAAQ,OAAA,CAAQ,CAAA;AAAA,KAG7B,WAAA,MAAiB,CAAA,SAAU,KAAA,oBAAyB,CAAA,GAAI,CAAA;AAAA,iBAE7C,OAAA,CAAQ,KAAA,YAAiB,KAAA,IAAS,KAAK;AAjCmB;;;;;;;;;;;AAAA,iBAoD1D,kBAAA,CAAmB,GAAA,EAAK,cAAc,EAAE,MAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router-builder-BK7yPm89.d.mts","names":[],"sources":["../src/http/router-builder.ts"],"mappings":";;;;;iBA8BgB,8BAAA,CAA+B,OAAuC,WAArB,mBAAmB;AAAA,UAInE,kBAAA;;;AAJqE;AAItF;;;EAOE,eAAA,YAA2B,mBAAmB;AAAA;AAuBhD;;;;;;;;;AAGa;;;;;;;;;;;AAHb,iBAAgB,eAAA,CACd,eAAA,OACA,OAAA,GAAS,kBAAA,GACR,UAAU"}
|