@forinda/kickjs 8.4.0 → 8.5.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.
Files changed (96) hide show
  1. package/dist/{application-Bm3wCm06.mjs → application-BFKE8ac5.mjs} +3 -3
  2. package/dist/{application-Bm3wCm06.mjs.map → application-BFKE8ac5.mjs.map} +1 -1
  3. package/dist/{application-C2WveUZr.d.mts → application-BRvykQ6h.d.mts} +65 -6
  4. package/dist/application-BRvykQ6h.d.mts.map +1 -0
  5. package/dist/{bootstrap-DHftqJ1m.mjs → bootstrap-CPRgpW3i.mjs} +3 -3
  6. package/dist/{bootstrap-DHftqJ1m.mjs.map → bootstrap-CPRgpW3i.mjs.map} +1 -1
  7. package/dist/{client-ip-D9nKcjVJ.mjs → client-ip-CW7HwSOm.mjs} +2 -2
  8. package/dist/{client-ip-D9nKcjVJ.mjs.map → client-ip-CW7HwSOm.mjs.map} +1 -1
  9. package/dist/config/index.mjs +2 -2
  10. package/dist/{config-pot4-2DS.mjs → config-Cajrc3qr.mjs} +3 -3
  11. package/dist/{config-pot4-2DS.mjs.map → config-Cajrc3qr.mjs.map} +1 -1
  12. package/dist/{container-BB_XC2ww.mjs → container-Cj2hqlfm.mjs} +3 -3
  13. package/dist/{container-BB_XC2ww.mjs.map → container-Cj2hqlfm.mjs.map} +1 -1
  14. package/dist/{context-Xd8C4fIo.mjs → context-B7N5oE7S.mjs} +3 -3
  15. package/dist/{context-Xd8C4fIo.mjs.map → context-B7N5oE7S.mjs.map} +1 -1
  16. package/dist/{context-errors-doMQ4bAU.mjs → context-errors-Cj2_aDaj.mjs} +2 -2
  17. package/dist/{context-errors-doMQ4bAU.mjs.map → context-errors-Cj2_aDaj.mjs.map} +1 -1
  18. package/dist/{contributor-runner-DflutMFy.mjs → contributor-runner-DQCZLpvk.mjs} +3 -3
  19. package/dist/{contributor-runner-DflutMFy.mjs.map → contributor-runner-DQCZLpvk.mjs.map} +1 -1
  20. package/dist/core/adapter.mjs +1 -1
  21. package/dist/core/app-module.mjs +1 -1
  22. package/dist/core/cache.mjs +1 -1
  23. package/dist/core/container.mjs +2 -2
  24. package/dist/core/cron.mjs +2 -2
  25. package/dist/core/decorators.mjs +2 -2
  26. package/dist/core/errors.mjs +1 -1
  27. package/dist/core/interfaces.mjs +1 -1
  28. package/dist/core/logger.mjs +1 -1
  29. package/dist/core/path.mjs +1 -1
  30. package/dist/core/plugin.mjs +1 -1
  31. package/dist/core/reactivity.mjs +1 -1
  32. package/dist/{core-CTyRc4AM.mjs → core-DZmGThrF.mjs} +3 -3
  33. package/dist/{core-CTyRc4AM.mjs.map → core-DZmGThrF.mjs.map} +1 -1
  34. package/dist/{csrf-CMzwva6A.mjs → csrf-DPPIM72I.mjs} +3 -3
  35. package/dist/{csrf-CMzwva6A.mjs.map → csrf-DPPIM72I.mjs.map} +1 -1
  36. package/dist/{decorate-BbFyJ1kh.mjs → decorate-DHyinlPY.mjs} +1 -1
  37. package/dist/{define-adapter-CsDcNhlh.mjs → define-adapter-0asM4v0B.mjs} +2 -2
  38. package/dist/{define-adapter-CsDcNhlh.mjs.map → define-adapter-0asM4v0B.mjs.map} +1 -1
  39. package/dist/{disposables-CZ7vf6L_.mjs → disposables-BYYy0y_z.mjs} +2 -2
  40. package/dist/{disposables-CZ7vf6L_.mjs.map → disposables-BYYy0y_z.mjs.map} +1 -1
  41. package/dist/{env-BTOmTNsH.mjs → env-DWP_2owJ.mjs} +3 -3
  42. package/dist/{env-BTOmTNsH.mjs.map → env-DWP_2owJ.mjs.map} +1 -1
  43. package/dist/{handler-8OeK5Hkh.mjs → handler-B4MQmJR5.mjs} +3 -3
  44. package/dist/{handler-8OeK5Hkh.mjs.map → handler-B4MQmJR5.mjs.map} +1 -1
  45. package/dist/http/application.d.mts +1 -1
  46. package/dist/http/application.mjs +2 -2
  47. package/dist/http/bootstrap.d.mts +1 -1
  48. package/dist/http/bootstrap.mjs +2 -2
  49. package/dist/http/context.mjs +2 -2
  50. package/dist/http/middleware/cors.mjs +1 -1
  51. package/dist/http/middleware/csrf.mjs +2 -2
  52. package/dist/http/middleware/error-handler.mjs +2 -2
  53. package/dist/http/middleware/helmet.mjs +1 -1
  54. package/dist/http/middleware/rate-limit.mjs +2 -2
  55. package/dist/http/middleware/request-id.mjs +1 -1
  56. package/dist/http/middleware/request-logger.mjs +2 -2
  57. package/dist/http/middleware/session.mjs +2 -2
  58. package/dist/http/middleware/spa.d.mts +1 -1
  59. package/dist/http/middleware/spa.mjs +2 -2
  60. package/dist/http/middleware/upload.mjs +2 -2
  61. package/dist/http/middleware/validate.mjs +1 -1
  62. package/dist/http/middleware/views.d.mts +1 -1
  63. package/dist/http/middleware/views.mjs +2 -2
  64. package/dist/http/query/index.mjs +2 -2
  65. package/dist/http/router-builder.mjs +2 -2
  66. package/dist/http/runtimes/fastify.mjs +2 -2
  67. package/dist/http/runtimes/h3-web.d.mts.map +1 -1
  68. package/dist/http/runtimes/h3-web.mjs +2 -2
  69. package/dist/http/runtimes/h3-web.mjs.map +1 -1
  70. package/dist/http/runtimes/h3.mjs +2 -2
  71. package/dist/index.d.mts +2 -2
  72. package/dist/index.mjs +2 -2
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/{kv-stores-C755yuvc.mjs → kv-stores-D2MKbvcu.mjs} +3 -3
  75. package/dist/{kv-stores-C755yuvc.mjs.map → kv-stores-D2MKbvcu.mjs.map} +1 -1
  76. package/dist/{metadata-C_bLigwT.mjs → metadata-Ceheg5tj.mjs} +2 -2
  77. package/dist/{metadata-C_bLigwT.mjs.map → metadata-Ceheg5tj.mjs.map} +1 -1
  78. package/dist/{module-registry-B_gw6smc.mjs → module-registry-RHo93bJa.mjs} +2 -2
  79. package/dist/{module-registry-B_gw6smc.mjs.map → module-registry-RHo93bJa.mjs.map} +1 -1
  80. package/dist/{query-B7_CJZaf.mjs → query-CWwMw9E6.mjs} +2 -2
  81. package/dist/{query-B7_CJZaf.mjs.map → query-CWwMw9E6.mjs.map} +1 -1
  82. package/dist/{respond-EdWjQfD6.mjs → respond-CI4CCFG4.mjs} +2 -2
  83. package/dist/{respond-EdWjQfD6.mjs.map → respond-CI4CCFG4.mjs.map} +1 -1
  84. package/dist/{route-flag-C7AilXtr.mjs → route-flag-B7t2wGJ2.mjs} +3 -3
  85. package/dist/{route-flag-C7AilXtr.mjs.map → route-flag-B7t2wGJ2.mjs.map} +1 -1
  86. package/dist/{route-policy-BQfQEuFn.mjs → route-policy-MQkq3QEO.mjs} +2 -2
  87. package/dist/{route-policy-BQfQEuFn.mjs.map → route-policy-MQkq3QEO.mjs.map} +1 -1
  88. package/dist/{router-builder-CFAHKq4e.mjs → router-builder-CK7_6DLH.mjs} +3 -3
  89. package/dist/{router-builder-CFAHKq4e.mjs.map → router-builder-CK7_6DLH.mjs.map} +1 -1
  90. package/dist/{runtime-B2oXJDwr.mjs → runtime-B3V_u8FS.mjs} +3 -3
  91. package/dist/{runtime-B2oXJDwr.mjs.map → runtime-B3V_u8FS.mjs.map} +1 -1
  92. package/dist/{upload-config-C23IAKGT.mjs → upload-config-C49VFeoh.mjs} +2 -2
  93. package/dist/{upload-config-C23IAKGT.mjs.map → upload-config-C49VFeoh.mjs.map} +1 -1
  94. package/dist/web.mjs +2 -2
  95. package/package.json +1 -1
  96. package/dist/application-C2WveUZr.d.mts.map +0 -1
@@ -15,8 +15,8 @@ import "./cors-CsibNTQ6.mjs";
15
15
  import "./rate-limit-D5FHbYKG.mjs";
16
16
  import "./session-BHgRyfqZ.mjs";
17
17
  import "./upload-DwGoAQ6R.mjs";
18
- import http from "node:http";
19
- import { Express, NextFunction, Request, RequestHandler, Response, Router } from "express";
18
+ import http, { IncomingMessage, ServerResponse } from "node:http";
19
+ import { Express, NextFunction, Request as Request$1, RequestHandler, Response as Response$1, Router } from "express";
20
20
  import { AsyncLocalStorage } from "node:async_hooks";
21
21
  //#region src/core/disposables.d.ts
22
22
  /**
@@ -436,6 +436,56 @@ declare function offerRoutePolicy(handler: unknown, table: RoutePolicyTable): vo
436
436
  */
437
437
  declare function bootstrap(options: ApplicationOptions): Promise<Application>;
438
438
  //#endregion
439
+ //#region src/http/handler.d.ts
440
+ /** A KickJS app exposed as request handlers. See {@link createHandler}. */
441
+ interface KickHandler {
442
+ /**
443
+ * Web-standard handler: `fetch(Request) → Response`. Netlify Functions,
444
+ * Vercel's fetch export, and anything else that speaks `Request`.
445
+ */
446
+ fetch(request: Request): Promise<Response>;
447
+ /** Node handler: `(req, res)`. Vercel Node functions, `http.createServer`. */
448
+ node(req: IncomingMessage, res: ServerResponse): Promise<void>;
449
+ /** Set the app up now instead of on the first request (warm-up). */
450
+ ready(): Promise<Application>;
451
+ /** Shut adapters down and stop the internal forwarding server, if one started. */
452
+ close(): Promise<void>;
453
+ }
454
+ /**
455
+ * Build a KickJS app as request handlers instead of a listening server.
456
+ *
457
+ * Takes the same options as `bootstrap()`. The app is set up once per process
458
+ * (per function instance), lazily on the first request, and reused after that.
459
+ * Nothing listens on a port, `afterStart` does not run, and no process signal
460
+ * handlers are registered — the platform owns the process.
461
+ *
462
+ * `fetch` uses the runtime's own fetch when it has one (the h3 v2 runtime).
463
+ * Runtimes built on Node request/response objects — Express, the default —
464
+ * are served through a server bound to `127.0.0.1` on a random port inside the
465
+ * same process, started on first use: the `Request` is forwarded to it and its
466
+ * response returned. Request bodies are buffered on that path; response bodies
467
+ * stream. Adapters that rewrite the `res` prototype (Express does) make a
468
+ * direct Request→`res` bridge unreliable, which is why this forwards instead.
469
+ *
470
+ * One handler per process: the DI container is process-wide.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * // server/src/serverless.ts
475
+ * import 'reflect-metadata'
476
+ * import './config'
477
+ * import { createHandler } from '@forinda/kickjs'
478
+ * import { modules } from './modules'
479
+ *
480
+ * export const handler = createHandler({ modules })
481
+ *
482
+ * // netlify/functions/api.mjs
483
+ * export default (request) => handler.fetch(request)
484
+ * export const config = { path: '/api/*' }
485
+ * ```
486
+ */
487
+ declare function createHandler(options: ApplicationOptions): KickHandler;
488
+ //#endregion
439
489
  //#region src/http/cluster.d.ts
440
490
  interface ClusterOptions {
441
491
  /** Number of worker processes (default: os.cpus().length) */
@@ -592,7 +642,7 @@ declare function getRequestValue<K extends ContextMetaKey>(key: K): MetaValue<K>
592
642
  * `Application` mounts this automatically unless `contextStore: 'manual'` is set
593
643
  * or the user already includes one in their middleware list.
594
644
  */
595
- declare function requestScopeMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
645
+ declare function requestScopeMiddleware(): (req: Request$1, res: Response$1, next: NextFunction) => void;
596
646
  //#endregion
597
647
  //#region src/http/middleware/trace-context.d.ts
598
648
  /**
@@ -646,7 +696,7 @@ declare function parseTraceparent(header: string): TraceContext | null;
646
696
  * })
647
697
  * ```
648
698
  */
649
- declare function traceContext(options?: TraceContextOptions): (req: Request, res: Response, next: NextFunction) => void;
699
+ declare function traceContext(options?: TraceContextOptions): (req: Request$1, res: Response$1, next: NextFunction) => void;
650
700
  //#endregion
651
701
  //#region src/http/middleware/views.d.ts
652
702
  interface ViewAdapterOptions {
@@ -1297,6 +1347,15 @@ declare class Application {
1297
1347
  setup(): Promise<void>;
1298
1348
  /** Register modules and DI without starting the HTTP server (used by kick tinker) */
1299
1349
  registerOnly(): Promise<void>;
1350
+ /**
1351
+ * Make the app ready to serve requests handed to it, with no server of its
1352
+ * own — the serverless path behind {@link createHandler}.
1353
+ *
1354
+ * Runs the same `setup()` as {@link start} and the plugins' `onReady`, but
1355
+ * nothing that needs a listening server: there is no port, no `afterStart`
1356
+ * (it receives the `http.Server`), and no process signal handlers.
1357
+ */
1358
+ startWithoutServer(): Promise<void>;
1300
1359
  /**
1301
1360
  * Start the HTTP server.
1302
1361
  *
@@ -1394,5 +1453,5 @@ declare class Application {
1394
1453
  private requestTrackingMiddleware;
1395
1454
  }
1396
1455
  //#endregion
1397
- export { CircuitBreakerOptions as $, ClusterOptions as A, groupAssetKeys as B, getRequestStore as C, expressRuntime as D, buildRoutes as E, offerRoutePolicy as F, MountKind as G, DuplicateMountNameError as H, AssetKeyPair as I, ContributorCycleError as J, MountSortItem as K, AssetKeyStrategy as L, bootstrap as M, RoutePolicyTable as N, materializeRouter as O, bindRoutePolicy as P, CircuitBreaker as Q, GroupAssetKeysOptions as R, RequestStore as S, requestStore as T, MissingMountDepError as U, stripExt as V, MountCycleError as W, MissingContextValueError as X, DuplicateContributorError as Y, MissingContributorError as Z, TraceContext as _, HEALTH_PROBE as a, KickErrorInit as at, traceContext as b, HealthModuleConfig as c, disposeAll as ct, SpaAdapter as d, CircuitBreakerState as et, SpaAdapterOptions as f, TRACEPARENT_HEADER as g, ViewAdapterOptions as h, ShutdownOptions as i, KickError as it, isClusterPrimary as j, defineHttpContextDecorator as k, HealthProbe as l, registerDisposable as lt, ViewAdapter as m, ApplicationOptions as n, CircuitOpenError as nt, HealthCheckResult as o, formatKickError as ot, resolveClientDir as p, mountSort as q, MiddlewareEntry as r, FormatKickErrorOptions as rt, HealthController as s, Disposable as st, Application as t, CircuitBreakerStats as tt, healthModule as u, TraceContextOptions as v, getRequestValue as w, requestScopeMiddleware as x, parseTraceparent as y, GroupAssetKeysResult as z };
1398
- //# sourceMappingURL=application-C2WveUZr.d.mts.map
1456
+ export { MissingContributorError as $, ClusterOptions as A, GroupAssetKeysOptions as B, getRequestStore as C, expressRuntime as D, buildRoutes as E, RoutePolicyTable as F, MissingMountDepError as G, groupAssetKeys as H, bindRoutePolicy as I, MountSortItem as J, MountCycleError as K, offerRoutePolicy as L, KickHandler as M, createHandler as N, materializeRouter as O, bootstrap as P, MissingContextValueError as Q, AssetKeyPair as R, RequestStore as S, requestStore as T, stripExt as U, GroupAssetKeysResult as V, DuplicateMountNameError as W, ContributorCycleError as X, mountSort as Y, DuplicateContributorError as Z, TraceContext as _, HEALTH_PROBE as a, FormatKickErrorOptions as at, traceContext as b, HealthModuleConfig as c, formatKickError as ct, SpaAdapter as d, registerDisposable as dt, CircuitBreaker as et, SpaAdapterOptions as f, TRACEPARENT_HEADER as g, ViewAdapterOptions as h, ShutdownOptions as i, CircuitOpenError as it, isClusterPrimary as j, defineHttpContextDecorator as k, HealthProbe as l, Disposable as lt, ViewAdapter as m, ApplicationOptions as n, CircuitBreakerState as nt, HealthCheckResult as o, KickError as ot, resolveClientDir as p, MountKind as q, MiddlewareEntry as r, CircuitBreakerStats as rt, HealthController as s, KickErrorInit as st, Application as t, CircuitBreakerOptions as tt, healthModule as u, disposeAll as ut, TraceContextOptions as v, getRequestValue as w, requestScopeMiddleware as x, parseTraceparent as y, AssetKeyStrategy as z };
1457
+ //# sourceMappingURL=application-BRvykQ6h.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-BRvykQ6h.d.mts","names":[],"sources":["../src/core/disposables.ts","../src/core/kick-error.ts","../src/core/circuit-breaker.ts","../src/core/context-errors.ts","../src/core/mount-sort.ts","../src/core/asset-keys.ts","../src/core/route-policy.ts","../src/http/bootstrap.ts","../src/http/handler.ts","../src/http/cluster.ts","../src/http/define-http-context-decorator.ts","../src/http/runtimes/express.ts","../src/http/request-store.ts","../src/http/middleware/request-scope.ts","../src/http/middleware/trace-context.ts","../src/http/middleware/views.ts","../src/http/middleware/spa.ts","../src/http/health-module.ts","../src/http/application.ts"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.4.0
2
+ * @forinda/kickjs v8.5.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{o as reloadEnv}from"./env-BTOmTNsH.mjs";import{t as Container}from"./container-BB_XC2ww.mjs";import{createLogger}from"./core/logger.mjs";import{t as Application}from"./application-Bm3wCm06.mjs";import{reactive,ref}from"./core/reactivity.mjs";import"./core-CTyRc4AM.mjs";import cluster from"node:cluster";import os from"node:os";const log$1=createLogger(`Cluster`);function isClusterPrimary(){return cluster.isPrimary}function setupClusterPrimary(opts){let numWorkers=opts.workers??os.cpus().length,gracefulTimeout=opts.gracefulShutdownTimeout??5e3,restartDelay=opts.restartDelay??1e3,maxFailures=opts.maxFailures??5,restartWindow=opts.restartWindow??3e4,startupTimeouts=new Map,state=reactive({failureTimestamps:[],shuttingDown:!1}),activeWorkers=ref(0);log$1.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`);for(let i=0;i<numWorkers;i++)cluster.fork();cluster.on(`fork`,worker=>{let timeout=setTimeout(()=>{log$1.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`),worker.kill()},1e4);startupTimeouts.set(worker.id,timeout),activeWorkers.value++,log$1.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)}),cluster.on(`listening`,(worker,address)=>{let timeout=startupTimeouts.get(worker.id);timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),log$1.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)}),cluster.on(`online`,worker=>{log$1.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)}),cluster.on(`exit`,(worker,code,signal)=>{let timeout=startupTimeouts.get(worker.id);if(timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),activeWorkers.value--,worker.exitedAfterDisconnect){log$1.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`);return}let now=Date.now();for(;state.failureTimestamps.length>0&&now-state.failureTimestamps[0]>=restartWindow;)state.failureTimestamps.shift();if(state.failureTimestamps.push(now),state.failureTimestamps.length>maxFailures){log$1.error(`${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`),activeWorkers.value===0&&(log$1.error(`All workers have crashed, exiting primary`),process.exit(1));return}log$1.warn(`Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`),setTimeout(()=>{activeWorkers.value<numWorkers&&cluster.fork()},restartDelay)}),cluster.on(`disconnect`,worker=>{log$1.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)});for(let signal of[`SIGTERM`,`SIGINT`])process.on(signal,()=>{state.shuttingDown||(state.shuttingDown=!0,log$1.info(`Primary received ${signal}, disconnecting workers...`),cluster.disconnect(()=>{log$1.info(`All workers disconnected, exiting primary`),process.exit(0)}),setTimeout(()=>{log$1.warn(`Graceful shutdown timed out, forcing exit`),process.exit(1)},gracefulTimeout).unref())})}function tryReloadEnv(){try{reloadEnv()}catch{}}const log=createLogger(`Process`);async function bootstrap(options){let isViteDev=!!globalThis.__kickjs_httpServer;if(options.cluster&&isViteDev&&log.warn(`Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).`),options.cluster&&cluster.isPrimary&&!isViteDev)return setupClusterPrimary(typeof options.cluster==`object`?options.cluster:{}),new Application(options);let g=globalThis,processHooks=options.processHooks??`auto`;if(!g.__kickBootstrapped){if(processHooks!==`manual`&&(process.on(`uncaughtException`,err=>{log.error(err,`Uncaught exception`)}),process.on(`unhandledRejection`,reason=>{log.error(reason,`Unhandled rejection`)})),processHooks===`auto`&&!g.__kickjs_httpServer)for(let signal of[`SIGINT`,`SIGTERM`])process.on(signal,async()=>{log.info(`Received ${signal}, shutting down...`),g.__app&&await g.__app.shutdown(),process.exit(0)});g.__kickBootstrapped=!0}if(g.__app){log.debug(`HMR rebuild triggered`);try{await g.__app.shutdown({closeServer:!1})}catch(err){log.error(err,`HMR teardown of the previous app failed`)}tryReloadEnv(),Container.reset();let freshApp=new Application(options);return g.__app=freshApp,g.__kickjs_container=freshApp.getContainer(),await freshApp.start(),log.debug(`HMR rebuild complete`),freshApp}let app=new Application(options);return g.__app=app,g.__kickjs_container=app.getContainer(),process.env.KICK_TINKER?(await app.registerOnly(),app):(await app.start(),app)}export{isClusterPrimary as n,bootstrap as t};
12
- //# sourceMappingURL=bootstrap-DHftqJ1m.mjs.map
11
+ import{o as reloadEnv}from"./env-DWP_2owJ.mjs";import{t as Container}from"./container-Cj2hqlfm.mjs";import{createLogger}from"./core/logger.mjs";import{t as Application}from"./application-BFKE8ac5.mjs";import{reactive,ref}from"./core/reactivity.mjs";import"./core-DZmGThrF.mjs";import cluster from"node:cluster";import os from"node:os";const log$1=createLogger(`Cluster`);function isClusterPrimary(){return cluster.isPrimary}function setupClusterPrimary(opts){let numWorkers=opts.workers??os.cpus().length,gracefulTimeout=opts.gracefulShutdownTimeout??5e3,restartDelay=opts.restartDelay??1e3,maxFailures=opts.maxFailures??5,restartWindow=opts.restartWindow??3e4,startupTimeouts=new Map,state=reactive({failureTimestamps:[],shuttingDown:!1}),activeWorkers=ref(0);log$1.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`);for(let i=0;i<numWorkers;i++)cluster.fork();cluster.on(`fork`,worker=>{let timeout=setTimeout(()=>{log$1.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`),worker.kill()},1e4);startupTimeouts.set(worker.id,timeout),activeWorkers.value++,log$1.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)}),cluster.on(`listening`,(worker,address)=>{let timeout=startupTimeouts.get(worker.id);timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),log$1.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)}),cluster.on(`online`,worker=>{log$1.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)}),cluster.on(`exit`,(worker,code,signal)=>{let timeout=startupTimeouts.get(worker.id);if(timeout&&(clearTimeout(timeout),startupTimeouts.delete(worker.id)),activeWorkers.value--,worker.exitedAfterDisconnect){log$1.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`);return}let now=Date.now();for(;state.failureTimestamps.length>0&&now-state.failureTimestamps[0]>=restartWindow;)state.failureTimestamps.shift();if(state.failureTimestamps.push(now),state.failureTimestamps.length>maxFailures){log$1.error(`${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`),activeWorkers.value===0&&(log$1.error(`All workers have crashed, exiting primary`),process.exit(1));return}log$1.warn(`Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`),setTimeout(()=>{activeWorkers.value<numWorkers&&cluster.fork()},restartDelay)}),cluster.on(`disconnect`,worker=>{log$1.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)});for(let signal of[`SIGTERM`,`SIGINT`])process.on(signal,()=>{state.shuttingDown||(state.shuttingDown=!0,log$1.info(`Primary received ${signal}, disconnecting workers...`),cluster.disconnect(()=>{log$1.info(`All workers disconnected, exiting primary`),process.exit(0)}),setTimeout(()=>{log$1.warn(`Graceful shutdown timed out, forcing exit`),process.exit(1)},gracefulTimeout).unref())})}function tryReloadEnv(){try{reloadEnv()}catch{}}const log=createLogger(`Process`);async function bootstrap(options){let isViteDev=!!globalThis.__kickjs_httpServer;if(options.cluster&&isViteDev&&log.warn(`Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).`),options.cluster&&cluster.isPrimary&&!isViteDev)return setupClusterPrimary(typeof options.cluster==`object`?options.cluster:{}),new Application(options);let g=globalThis,processHooks=options.processHooks??`auto`;if(!g.__kickBootstrapped){if(processHooks!==`manual`&&(process.on(`uncaughtException`,err=>{log.error(err,`Uncaught exception`)}),process.on(`unhandledRejection`,reason=>{log.error(reason,`Unhandled rejection`)})),processHooks===`auto`&&!g.__kickjs_httpServer)for(let signal of[`SIGINT`,`SIGTERM`])process.on(signal,async()=>{log.info(`Received ${signal}, shutting down...`),g.__app&&await g.__app.shutdown(),process.exit(0)});g.__kickBootstrapped=!0}if(g.__app){log.debug(`HMR rebuild triggered`);try{await g.__app.shutdown({closeServer:!1})}catch(err){log.error(err,`HMR teardown of the previous app failed`)}tryReloadEnv(),Container.reset();let freshApp=new Application(options);return g.__app=freshApp,g.__kickjs_container=freshApp.getContainer(),await freshApp.start(),log.debug(`HMR rebuild complete`),freshApp}let app=new Application(options);return g.__app=app,g.__kickjs_container=app.getContainer(),process.env.KICK_TINKER?(await app.registerOnly(),app):(await app.start(),app)}export{isClusterPrimary as n,bootstrap as t};
12
+ //# sourceMappingURL=bootstrap-CPRgpW3i.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-DHftqJ1m.mjs","names":["log"],"sources":["../src/http/cluster.ts","../src/http/bootstrap.ts"],"sourcesContent":["import cluster from 'node:cluster'\nimport os from 'node:os'\nimport { createLogger } from '../core'\nimport { reactive, ref } from '../core/reactivity'\n\nconst log = createLogger('Cluster')\n\nexport interface ClusterOptions {\n /** Number of worker processes (default: os.cpus().length) */\n workers?: number\n /** Delay before forcefully killing a worker during shutdown (ms), default: 5000 */\n gracefulShutdownTimeout?: number\n /** Delay between worker restarts (ms), default: 1000 */\n restartDelay?: number\n /** Max failures in restartWindow before refusing to restart, default: 5 */\n maxFailures?: number\n /** Time window for counting failures (ms), default: 30000 */\n restartWindow?: number\n}\n\n/** Returns true if the current process is the cluster primary */\nexport function isClusterPrimary(): boolean {\n return cluster.isPrimary\n}\n\n/**\n * Fork worker processes and manage the cluster lifecycle.\n *\n * The primary process forks `workers` child processes. Each worker\n * shares the same port via Node's built-in `cluster` module (OS\n * round-robin load balancing).\n *\n * Features:\n * - Graceful shutdown with timeout fallback to forceful kill\n * - Auto-restart dead workers (respects voluntary disconnects)\n * - Startup timeout detection (stuck workers)\n * - Crash loop prevention (backoff on rapid failures)\n * - Comprehensive lifecycle logging\n */\nexport function setupClusterPrimary(opts: ClusterOptions): void {\n const numWorkers = opts.workers ?? os.cpus().length\n const gracefulTimeout = opts.gracefulShutdownTimeout ?? 5000\n const restartDelay = opts.restartDelay ?? 1000\n const maxFailures = opts.maxFailures ?? 5\n const restartWindow = opts.restartWindow ?? 30000\n\n const startupTimeouts = new Map<number, NodeJS.Timeout>()\n const state = reactive({\n failureTimestamps: [] as number[],\n shuttingDown: false,\n })\n const activeWorkers = ref(0)\n\n log.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`)\n\n // Initial fork\n for (let i = 0; i < numWorkers; i++) {\n cluster.fork()\n }\n\n // Detect stuck workers (no 'listening' event within timeout)\n cluster.on('fork', (worker) => {\n const timeout = setTimeout(() => {\n log.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`)\n worker.kill()\n }, 10000)\n\n startupTimeouts.set(worker.id, timeout)\n activeWorkers.value++\n log.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)\n })\n\n // Clear startup timeout when worker is ready\n cluster.on('listening', (worker, address) => {\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n log.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)\n })\n\n // Log when worker comes online\n cluster.on('online', (worker) => {\n log.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)\n })\n\n // Auto-restart dead workers (unless intentionally disconnected)\n cluster.on('exit', (worker, code, signal) => {\n // Clear startup timeout if still pending\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n\n // Don't restart if worker was intentionally disconnected\n activeWorkers.value--\n\n if (worker.exitedAfterDisconnect) {\n log.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`)\n return\n }\n\n // Track crash history globally (worker IDs change on restart)\n const now = Date.now()\n // Remove timestamps outside the window, then add the current failure\n while (\n state.failureTimestamps.length > 0 &&\n now - state.failureTimestamps[0] >= restartWindow\n ) {\n state.failureTimestamps.shift()\n }\n state.failureTimestamps.push(now)\n\n // Prevent restart loops\n if (state.failureTimestamps.length > maxFailures) {\n log.error(\n `${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`,\n )\n // Optionally exit the primary if too many workers are crashing\n if (activeWorkers.value === 0) {\n log.error('All workers have crashed, exiting primary')\n process.exit(1)\n }\n return\n }\n\n log.warn(\n `Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`,\n )\n\n setTimeout(() => {\n // Only restart if we still need more workers\n if (activeWorkers.value < numWorkers) {\n cluster.fork()\n }\n }, restartDelay)\n })\n\n // Log when worker disconnects (can happen before exit)\n cluster.on('disconnect', (worker) => {\n log.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)\n })\n\n // Forward shutdown signals to workers (disconnect so exitedAfterDisconnect is set)\n for (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.on(signal, () => {\n if (state.shuttingDown) return\n state.shuttingDown = true\n log.info(`Primary received ${signal}, disconnecting workers...`)\n cluster.disconnect(() => {\n log.info('All workers disconnected, exiting primary')\n process.exit(0)\n })\n // Force kill after graceful timeout\n setTimeout(() => {\n log.warn('Graceful shutdown timed out, forcing exit')\n process.exit(1)\n }, gracefulTimeout).unref()\n })\n }\n}\n","import cluster from 'node:cluster'\nimport { createLogger, Container } from '../core'\nimport { reloadEnv } from '../config/env'\nimport { Application, type ApplicationOptions } from './application'\nimport { setupClusterPrimary, type ClusterOptions } from './cluster'\n\n/** Reload env from .env file (HMR rebuild) — config now lives in-tree. */\nfunction tryReloadEnv(): void {\n try {\n reloadEnv()\n } catch {\n // Best-effort: never crash bootstrap because env reload failed.\n }\n}\n\nconst log = createLogger('Process')\n\n/**\n * Bootstrap a KickJS application.\n *\n * Creates the Express app, registers modules and adapters, and starts\n * the HTTP server. In dev mode (`kick dev`), the Vite plugin manages\n * the server and HMR — source changes are picked up instantly without\n * losing database connections or WebSocket state.\n *\n * @returns The {@link Application} instance.\n *\n * @example\n * ```ts\n * // src/index.ts\n * import 'reflect-metadata'\n * import { bootstrap } from '@forinda/kickjs'\n * import { modules } from './modules'\n *\n * export const app = await bootstrap({ modules })\n * ```\n */\nexport async function bootstrap(options: ApplicationOptions): Promise<Application> {\n // ── Cluster mode ─────────────────────────────────────────────────────\n // In cluster mode the primary process forks workers and never runs the\n // Express application itself. Only workers proceed past this block.\n // Cluster is disabled in Vite dev mode — Vite owns the HTTP server and\n // forked workers would each create their own Vite instance on separate ports.\n const isViteDev = !!(globalThis as any).__kickjs_httpServer\n if (options.cluster && isViteDev) {\n log.warn(\n 'Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).',\n )\n }\n if (options.cluster && cluster.isPrimary && !isViteDev) {\n const clusterOpts: ClusterOptions = typeof options.cluster === 'object' ? options.cluster : {}\n setupClusterPrimary(clusterOpts)\n\n // Return a placeholder — the primary never serves HTTP traffic.\n // The Application is created only so callers get a valid reference.\n return new Application(options)\n }\n\n // Internal: globalThis stores the app instance and Vite's HTTP server\n // reference across HMR rebuilds. When __kickjs_httpServer is set, the\n // Vite plugin owns the port and bootstrap() skips server creation.\n // On subsequent calls (HMR), the Express handler is swapped without\n // restarting the server — preserving DB connections and port bindings.\n const g = globalThis as any\n\n // ── Global error handlers + signal hooks ─────────────────────────────\n // `processHooks` controls whether KickJS registers its own\n // process-level handlers. Defaults to `'auto'` — register everything.\n // Adopters that bring their own observability stack (OpenTelemetry SDK,\n // Sentry, custom shutdown choreographer) often register their own\n // SIGINT/SIGTERM handlers and need to opt out of ours so the two don't\n // race to call `process.exit(0)`. Three modes:\n // - 'auto' (default): register uncaughtException/unhandledRejection\n // loggers + SIGINT/SIGTERM → `app.shutdown()` → `process.exit(0)`.\n // - 'errors-only': register the error loggers; skip signal handlers.\n // Use this when you have your own shutdown choreographer that calls\n // `app.shutdown()` itself.\n // - 'manual': skip everything. Adopter is responsible for both error\n // logging and signal-driven shutdown.\n const processHooks = options.processHooks ?? 'auto'\n\n if (!g.__kickBootstrapped) {\n if (processHooks !== 'manual') {\n process.on('uncaughtException', (err) => {\n log.error(err, 'Uncaught exception')\n })\n\n process.on('unhandledRejection', (reason) => {\n log.error(reason as any, 'Unhandled rejection')\n })\n }\n\n if (processHooks === 'auto' && !g.__kickjs_httpServer) {\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, async () => {\n log.info(`Received ${signal}, shutting down...`)\n if (g.__app) await g.__app.shutdown()\n process.exit(0)\n })\n }\n }\n\n g.__kickBootstrapped = true\n }\n\n // ── HMR rebuild ──────────────────────────────────────────────────────\n if (g.__app) {\n log.debug('HMR rebuild triggered')\n\n // Tear the PREVIOUS app down before replacing it. Without this the old\n // Application was dropped on the floor still running: its adapters kept\n // their sockets and consumers open, so every save added another set.\n //\n // Observed in the wild as one process holding several consumer-group\n // members — on a single-partition topic only one can hold the assignment,\n // and it was a leaked consumer no longer wired to anything, so jobs\n // stopped being processed. A second socket.io server on the same HTTP\n // server crashed `handleUpgrade()` the same way.\n //\n // `closeServer: false` is load-bearing: the dev HTTP server is shared\n // across rebuilds, so a full shutdown would close the listening socket and\n // kill HMR on the first save.\n //\n // Failures are logged, not thrown — one broken adapter must not leave the\n // dev server wedged with no app at all.\n try {\n await g.__app.shutdown({ closeServer: false })\n } catch (err) {\n log.error(err as Error, 'HMR teardown of the previous app failed')\n }\n\n tryReloadEnv()\n Container.reset()\n\n const freshApp = new Application(options)\n g.__app = freshApp\n g.__kickjs_container = freshApp.getContainer()\n\n await freshApp.start()\n log.debug('HMR rebuild complete')\n return freshApp\n }\n\n // ── First boot ───────────────────────────────────────────────────────\n const app = new Application(options)\n g.__app = app\n g.__kickjs_container = app.getContainer()\n\n if (process.env.KICK_TINKER) {\n await app.registerOnly()\n return app\n }\n\n await app.start()\n\n return app\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bootstrap-CPRgpW3i.mjs","names":["log"],"sources":["../src/http/cluster.ts","../src/http/bootstrap.ts"],"sourcesContent":["import cluster from 'node:cluster'\nimport os from 'node:os'\nimport { createLogger } from '../core'\nimport { reactive, ref } from '../core/reactivity'\n\nconst log = createLogger('Cluster')\n\nexport interface ClusterOptions {\n /** Number of worker processes (default: os.cpus().length) */\n workers?: number\n /** Delay before forcefully killing a worker during shutdown (ms), default: 5000 */\n gracefulShutdownTimeout?: number\n /** Delay between worker restarts (ms), default: 1000 */\n restartDelay?: number\n /** Max failures in restartWindow before refusing to restart, default: 5 */\n maxFailures?: number\n /** Time window for counting failures (ms), default: 30000 */\n restartWindow?: number\n}\n\n/** Returns true if the current process is the cluster primary */\nexport function isClusterPrimary(): boolean {\n return cluster.isPrimary\n}\n\n/**\n * Fork worker processes and manage the cluster lifecycle.\n *\n * The primary process forks `workers` child processes. Each worker\n * shares the same port via Node's built-in `cluster` module (OS\n * round-robin load balancing).\n *\n * Features:\n * - Graceful shutdown with timeout fallback to forceful kill\n * - Auto-restart dead workers (respects voluntary disconnects)\n * - Startup timeout detection (stuck workers)\n * - Crash loop prevention (backoff on rapid failures)\n * - Comprehensive lifecycle logging\n */\nexport function setupClusterPrimary(opts: ClusterOptions): void {\n const numWorkers = opts.workers ?? os.cpus().length\n const gracefulTimeout = opts.gracefulShutdownTimeout ?? 5000\n const restartDelay = opts.restartDelay ?? 1000\n const maxFailures = opts.maxFailures ?? 5\n const restartWindow = opts.restartWindow ?? 30000\n\n const startupTimeouts = new Map<number, NodeJS.Timeout>()\n const state = reactive({\n failureTimestamps: [] as number[],\n shuttingDown: false,\n })\n const activeWorkers = ref(0)\n\n log.info(`Primary ${process.pid} starting ${numWorkers} worker(s)`)\n\n // Initial fork\n for (let i = 0; i < numWorkers; i++) {\n cluster.fork()\n }\n\n // Detect stuck workers (no 'listening' event within timeout)\n cluster.on('fork', (worker) => {\n const timeout = setTimeout(() => {\n log.error(`Worker ${worker.process.pid} did not respond within 10s, killing...`)\n worker.kill()\n }, 10000)\n\n startupTimeouts.set(worker.id, timeout)\n activeWorkers.value++\n log.debug(`Worker ${worker.process.pid} forked (id=${worker.id})`)\n })\n\n // Clear startup timeout when worker is ready\n cluster.on('listening', (worker, address) => {\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n log.info(`Worker ${worker.process.pid} listening on ${address.address}:${address.port}`)\n })\n\n // Log when worker comes online\n cluster.on('online', (worker) => {\n log.debug(`Worker ${worker.process.pid} online (id=${worker.id})`)\n })\n\n // Auto-restart dead workers (unless intentionally disconnected)\n cluster.on('exit', (worker, code, signal) => {\n // Clear startup timeout if still pending\n const timeout = startupTimeouts.get(worker.id)\n if (timeout) {\n clearTimeout(timeout)\n startupTimeouts.delete(worker.id)\n }\n\n // Don't restart if worker was intentionally disconnected\n activeWorkers.value--\n\n if (worker.exitedAfterDisconnect) {\n log.info(`Worker ${worker.process.pid} exited gracefully (id=${worker.id})`)\n return\n }\n\n // Track crash history globally (worker IDs change on restart)\n const now = Date.now()\n // Remove timestamps outside the window, then add the current failure\n while (\n state.failureTimestamps.length > 0 &&\n now - state.failureTimestamps[0] >= restartWindow\n ) {\n state.failureTimestamps.shift()\n }\n state.failureTimestamps.push(now)\n\n // Prevent restart loops\n if (state.failureTimestamps.length > maxFailures) {\n log.error(\n `${state.failureTimestamps.length} worker failures in ${restartWindow}ms, giving up`,\n )\n // Optionally exit the primary if too many workers are crashing\n if (activeWorkers.value === 0) {\n log.error('All workers have crashed, exiting primary')\n process.exit(1)\n }\n return\n }\n\n log.warn(\n `Worker ${worker.process.pid} died (code=${code}, signal=${signal}), restarting in ${restartDelay}ms... (failures: ${state.failureTimestamps.length}/${maxFailures})`,\n )\n\n setTimeout(() => {\n // Only restart if we still need more workers\n if (activeWorkers.value < numWorkers) {\n cluster.fork()\n }\n }, restartDelay)\n })\n\n // Log when worker disconnects (can happen before exit)\n cluster.on('disconnect', (worker) => {\n log.debug(`Worker ${worker.process.pid} disconnected (id=${worker.id})`)\n })\n\n // Forward shutdown signals to workers (disconnect so exitedAfterDisconnect is set)\n for (const signal of ['SIGTERM', 'SIGINT'] as const) {\n process.on(signal, () => {\n if (state.shuttingDown) return\n state.shuttingDown = true\n log.info(`Primary received ${signal}, disconnecting workers...`)\n cluster.disconnect(() => {\n log.info('All workers disconnected, exiting primary')\n process.exit(0)\n })\n // Force kill after graceful timeout\n setTimeout(() => {\n log.warn('Graceful shutdown timed out, forcing exit')\n process.exit(1)\n }, gracefulTimeout).unref()\n })\n }\n}\n","import cluster from 'node:cluster'\nimport { createLogger, Container } from '../core'\nimport { reloadEnv } from '../config/env'\nimport { Application, type ApplicationOptions } from './application'\nimport { setupClusterPrimary, type ClusterOptions } from './cluster'\n\n/** Reload env from .env file (HMR rebuild) — config now lives in-tree. */\nfunction tryReloadEnv(): void {\n try {\n reloadEnv()\n } catch {\n // Best-effort: never crash bootstrap because env reload failed.\n }\n}\n\nconst log = createLogger('Process')\n\n/**\n * Bootstrap a KickJS application.\n *\n * Creates the Express app, registers modules and adapters, and starts\n * the HTTP server. In dev mode (`kick dev`), the Vite plugin manages\n * the server and HMR — source changes are picked up instantly without\n * losing database connections or WebSocket state.\n *\n * @returns The {@link Application} instance.\n *\n * @example\n * ```ts\n * // src/index.ts\n * import 'reflect-metadata'\n * import { bootstrap } from '@forinda/kickjs'\n * import { modules } from './modules'\n *\n * export const app = await bootstrap({ modules })\n * ```\n */\nexport async function bootstrap(options: ApplicationOptions): Promise<Application> {\n // ── Cluster mode ─────────────────────────────────────────────────────\n // In cluster mode the primary process forks workers and never runs the\n // Express application itself. Only workers proceed past this block.\n // Cluster is disabled in Vite dev mode — Vite owns the HTTP server and\n // forked workers would each create their own Vite instance on separate ports.\n const isViteDev = !!(globalThis as any).__kickjs_httpServer\n if (options.cluster && isViteDev) {\n log.warn(\n 'Cluster mode is ignored in dev — Vite owns the server. Cluster applies in production (kick start).',\n )\n }\n if (options.cluster && cluster.isPrimary && !isViteDev) {\n const clusterOpts: ClusterOptions = typeof options.cluster === 'object' ? options.cluster : {}\n setupClusterPrimary(clusterOpts)\n\n // Return a placeholder — the primary never serves HTTP traffic.\n // The Application is created only so callers get a valid reference.\n return new Application(options)\n }\n\n // Internal: globalThis stores the app instance and Vite's HTTP server\n // reference across HMR rebuilds. When __kickjs_httpServer is set, the\n // Vite plugin owns the port and bootstrap() skips server creation.\n // On subsequent calls (HMR), the Express handler is swapped without\n // restarting the server — preserving DB connections and port bindings.\n const g = globalThis as any\n\n // ── Global error handlers + signal hooks ─────────────────────────────\n // `processHooks` controls whether KickJS registers its own\n // process-level handlers. Defaults to `'auto'` — register everything.\n // Adopters that bring their own observability stack (OpenTelemetry SDK,\n // Sentry, custom shutdown choreographer) often register their own\n // SIGINT/SIGTERM handlers and need to opt out of ours so the two don't\n // race to call `process.exit(0)`. Three modes:\n // - 'auto' (default): register uncaughtException/unhandledRejection\n // loggers + SIGINT/SIGTERM → `app.shutdown()` → `process.exit(0)`.\n // - 'errors-only': register the error loggers; skip signal handlers.\n // Use this when you have your own shutdown choreographer that calls\n // `app.shutdown()` itself.\n // - 'manual': skip everything. Adopter is responsible for both error\n // logging and signal-driven shutdown.\n const processHooks = options.processHooks ?? 'auto'\n\n if (!g.__kickBootstrapped) {\n if (processHooks !== 'manual') {\n process.on('uncaughtException', (err) => {\n log.error(err, 'Uncaught exception')\n })\n\n process.on('unhandledRejection', (reason) => {\n log.error(reason as any, 'Unhandled rejection')\n })\n }\n\n if (processHooks === 'auto' && !g.__kickjs_httpServer) {\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, async () => {\n log.info(`Received ${signal}, shutting down...`)\n if (g.__app) await g.__app.shutdown()\n process.exit(0)\n })\n }\n }\n\n g.__kickBootstrapped = true\n }\n\n // ── HMR rebuild ──────────────────────────────────────────────────────\n if (g.__app) {\n log.debug('HMR rebuild triggered')\n\n // Tear the PREVIOUS app down before replacing it. Without this the old\n // Application was dropped on the floor still running: its adapters kept\n // their sockets and consumers open, so every save added another set.\n //\n // Observed in the wild as one process holding several consumer-group\n // members — on a single-partition topic only one can hold the assignment,\n // and it was a leaked consumer no longer wired to anything, so jobs\n // stopped being processed. A second socket.io server on the same HTTP\n // server crashed `handleUpgrade()` the same way.\n //\n // `closeServer: false` is load-bearing: the dev HTTP server is shared\n // across rebuilds, so a full shutdown would close the listening socket and\n // kill HMR on the first save.\n //\n // Failures are logged, not thrown — one broken adapter must not leave the\n // dev server wedged with no app at all.\n try {\n await g.__app.shutdown({ closeServer: false })\n } catch (err) {\n log.error(err as Error, 'HMR teardown of the previous app failed')\n }\n\n tryReloadEnv()\n Container.reset()\n\n const freshApp = new Application(options)\n g.__app = freshApp\n g.__kickjs_container = freshApp.getContainer()\n\n await freshApp.start()\n log.debug('HMR rebuild complete')\n return freshApp\n }\n\n // ── First boot ───────────────────────────────────────────────────────\n const app = new Application(options)\n g.__app = app\n g.__kickjs_container = app.getContainer()\n\n if (process.env.KICK_TINKER) {\n await app.registerOnly()\n return app\n }\n\n await app.start()\n\n return app\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.4.0
2
+ * @forinda/kickjs v8.5.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  function firstHop(value){return typeof value==`string`&&value.length>0?value.split(`,`)[0].trim():void 0}function headerValue(headers,name){if(headers)return typeof headers.get==`function`?headers.get(name):headers[name]}function resolveClientIp(req){if(typeof req.ip==`string`&&req.ip.length>0)return req.ip;let socketAddress=req.socket?.remoteAddress;if(socketAddress)return socketAddress;let h=req.headers;return firstHop(headerValue(h,`cf-connecting-ip`))??firstHop(headerValue(h,`x-forwarded-for`))??firstHop(headerValue(h,`x-real-ip`))}function resolvePathname(req){if(typeof req.path==`string`&&req.path.length>0)return req.path;let url=req.url;if(!url)return`/`;if(/^[a-z][a-z0-9+.-]*:\/\//i.test(url))try{return new URL(url).pathname||`/`}catch{return`/`}let q=url.indexOf(`?`);return(q===-1?url:url.slice(0,q))||`/`}export{resolvePathname as n,resolveClientIp as t};
12
- //# sourceMappingURL=client-ip-D9nKcjVJ.mjs.map
12
+ //# sourceMappingURL=client-ip-CW7HwSOm.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-ip-D9nKcjVJ.mjs","names":[],"sources":["../src/http/client-ip.ts"],"sourcesContent":["/**\n * Client address + pathname resolution shared by everything that runs as\n * connect-style middleware.\n *\n * Fastify and h3 hand middleware the RAW node request, so the Express\n * conveniences (`req.ip`, `req.path`, `req.originalUrl`) are `undefined`\n * there. Reading them directly produced two live bugs:\n *\n * rate-limit → every caller keyed as the `'127.0.0.1'` fallback, i.e. ONE\n * shared bucket, so a single client could exhaust the limit\n * for everyone\n * csrf → `ignorePaths` never matched, so configured exemptions were\n * silently dead\n *\n * Edge-safe: no node imports.\n */\n\n/** The request members these helpers read, from node's shape rather than Express's. */\nexport interface ClientRequestLike {\n url?: string\n /** Express-only convenience. */\n path?: string\n /** Express-only; the full path before router mounting rewrote `url`. */\n originalUrl?: string\n /** Express and Fastify compute this from their trust-proxy settings. */\n ip?: unknown\n /**\n * A plain object on every path the framework currently takes — `WebRequestShim`\n * normalizes a WHATWG `Headers` before the context is built. A `Headers`\n * instance is accepted defensively: it is not index-accessible, so if one ever\n * did arrive, every lookup would return `undefined` SILENTLY and drop each\n * caller into one shared rate-limit bucket.\n */\n headers?: Record<string, string | string[] | undefined> | Headers\n socket?: { remoteAddress?: string }\n}\n\n/** First entry of a possibly comma-separated header value. */\nfunction firstHop(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value.split(',')[0]!.trim() : undefined\n}\n\n/** Read one header from either a plain object or a WHATWG `Headers`. */\nfunction headerValue(\n headers: Record<string, string | string[] | undefined> | Headers | undefined,\n name: string,\n): unknown {\n if (!headers) return undefined\n // `Headers` has no index access, so `headers[name]` silently yields\n // `undefined` rather than the value.\n if (typeof (headers as Headers).get === 'function') return (headers as Headers).get(name)\n return (headers as Record<string, string | string[] | undefined>)[name]\n}\n\n/**\n * The client's IP address, or `undefined` when it cannot be determined.\n *\n * Prefers the address the runtime computed — Express derives `req.ip` from\n * `trust proxy`, Fastify from `trustProxy` — because raw forwarded headers are\n * client-SPOOFABLE wherever nothing normalizes them. h3 takes the same stance:\n * its `getRequestIP` only consults `x-forwarded-for` when explicitly opted in.\n *\n * Order is `req.ip` → `socket.remoteAddress` → forwarded headers. The socket\n * comes BEFORE the headers deliberately: it cannot be spoofed, whereas a direct\n * client can vary `x-forwarded-for` per request to get a fresh rate-limit\n * bucket each time. Headers are consulted only where there is no socket — the\n * web/edge entry, where the platform terminates the connection.\n */\nexport function resolveClientIp(req: ClientRequestLike): string | undefined {\n // 1. What the runtime computed. Express derives `req.ip` from `trust proxy`,\n // Fastify from `trustProxy` — a value vetted against a CONFIGURED proxy,\n // which is the only reason to believe a forwarded header at all.\n if (typeof req.ip === 'string' && req.ip.length > 0) return req.ip\n\n // 2. The socket. Unspoofable, and present on every node runtime.\n //\n // This MUST precede the headers. A raw Fastify / h3 request has no\n // `req.ip`, so consulting `x-forwarded-for` first let a DIRECT client send\n // a different value per request and land in a fresh rate-limit bucket each\n // time — evading the limit entirely. Behind a real proxy the socket is the\n // proxy's address, which buckets clients together; the fix for that is to\n // configure the runtime's trust-proxy setting so step 1 answers, not to\n // trust an unverified header here.\n const socketAddress = req.socket?.remoteAddress\n if (socketAddress) return socketAddress\n\n // 3. Headers, only where there is no socket at all — the web/edge entry,\n // where the platform terminates the connection and these are the only\n // signal available.\n const h = req.headers\n return (\n firstHop(headerValue(h, 'cf-connecting-ip')) ??\n firstHop(headerValue(h, 'x-forwarded-for')) ??\n firstHop(headerValue(h, 'x-real-ip'))\n )\n}\n\n/** Pathname only — `req.url` carries the query string, and `req.path` is Express-only. */\nexport function resolvePathname(req: ClientRequestLike): string {\n if (typeof req.path === 'string' && req.path.length > 0) return req.path\n const url = req.url\n if (!url) return '/'\n // `WebRequestShim` already reduces the WHATWG `Request.url` to path+search,\n // but an ABSOLUTE url would otherwise be returned whole — so a skip prefix\n // like `/health` would never match `https://host/health`.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url)) {\n try {\n return new URL(url).pathname || '/'\n } catch {\n return '/'\n }\n }\n const q = url.indexOf('?')\n return (q === -1 ? url : url.slice(0, q)) || '/'\n}\n"],"mappings":";;;;;;;;;;AAsCA,SAAS,SAAS,MAAoC,CACpD,OAAO,OAAO,OAAU,UAAY,MAAM,OAAS,EAAI,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,EAAI,IAAA,EACvF,CAGA,SAAS,YACP,QACA,KACS,CACJ,WAIL,OADI,OAAQ,QAAoB,KAAQ,WAAoB,QAAoB,IAAI,IAAI,EAChF,QAA0D,KACpE,CAgBA,SAAgB,gBAAgB,IAA4C,CAI1E,GAAI,OAAO,IAAI,IAAO,UAAY,IAAI,GAAG,OAAS,EAAG,OAAO,IAAI,GAWhE,IAAM,cAAgB,IAAI,QAAQ,cAClC,GAAI,cAAe,OAAO,cAK1B,IAAM,EAAI,IAAI,QACd,OACE,SAAS,YAAY,EAAG,kBAAkB,CAAC,GAC3C,SAAS,YAAY,EAAG,iBAAiB,CAAC,GAC1C,SAAS,YAAY,EAAG,WAAW,CAAC,CAExC,CAGA,SAAgB,gBAAgB,IAAgC,CAC9D,GAAI,OAAO,IAAI,MAAS,UAAY,IAAI,KAAK,OAAS,EAAG,OAAO,IAAI,KACpE,IAAM,IAAM,IAAI,IAChB,GAAI,CAAC,IAAK,MAAO,IAIjB,GAAI,2BAA2B,KAAK,GAAG,EACrC,GAAI,CACF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC,UAAY,GAClC,MAAQ,CACN,MAAO,GACT,CAEF,IAAM,EAAI,IAAI,QAAQ,GAAG,EACzB,OAAQ,IAAM,GAAK,IAAM,IAAI,MAAM,EAAG,CAAC,IAAM,GAC/C"}
1
+ {"version":3,"file":"client-ip-CW7HwSOm.mjs","names":[],"sources":["../src/http/client-ip.ts"],"sourcesContent":["/**\n * Client address + pathname resolution shared by everything that runs as\n * connect-style middleware.\n *\n * Fastify and h3 hand middleware the RAW node request, so the Express\n * conveniences (`req.ip`, `req.path`, `req.originalUrl`) are `undefined`\n * there. Reading them directly produced two live bugs:\n *\n * rate-limit → every caller keyed as the `'127.0.0.1'` fallback, i.e. ONE\n * shared bucket, so a single client could exhaust the limit\n * for everyone\n * csrf → `ignorePaths` never matched, so configured exemptions were\n * silently dead\n *\n * Edge-safe: no node imports.\n */\n\n/** The request members these helpers read, from node's shape rather than Express's. */\nexport interface ClientRequestLike {\n url?: string\n /** Express-only convenience. */\n path?: string\n /** Express-only; the full path before router mounting rewrote `url`. */\n originalUrl?: string\n /** Express and Fastify compute this from their trust-proxy settings. */\n ip?: unknown\n /**\n * A plain object on every path the framework currently takes — `WebRequestShim`\n * normalizes a WHATWG `Headers` before the context is built. A `Headers`\n * instance is accepted defensively: it is not index-accessible, so if one ever\n * did arrive, every lookup would return `undefined` SILENTLY and drop each\n * caller into one shared rate-limit bucket.\n */\n headers?: Record<string, string | string[] | undefined> | Headers\n socket?: { remoteAddress?: string }\n}\n\n/** First entry of a possibly comma-separated header value. */\nfunction firstHop(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value.split(',')[0]!.trim() : undefined\n}\n\n/** Read one header from either a plain object or a WHATWG `Headers`. */\nfunction headerValue(\n headers: Record<string, string | string[] | undefined> | Headers | undefined,\n name: string,\n): unknown {\n if (!headers) return undefined\n // `Headers` has no index access, so `headers[name]` silently yields\n // `undefined` rather than the value.\n if (typeof (headers as Headers).get === 'function') return (headers as Headers).get(name)\n return (headers as Record<string, string | string[] | undefined>)[name]\n}\n\n/**\n * The client's IP address, or `undefined` when it cannot be determined.\n *\n * Prefers the address the runtime computed — Express derives `req.ip` from\n * `trust proxy`, Fastify from `trustProxy` — because raw forwarded headers are\n * client-SPOOFABLE wherever nothing normalizes them. h3 takes the same stance:\n * its `getRequestIP` only consults `x-forwarded-for` when explicitly opted in.\n *\n * Order is `req.ip` → `socket.remoteAddress` → forwarded headers. The socket\n * comes BEFORE the headers deliberately: it cannot be spoofed, whereas a direct\n * client can vary `x-forwarded-for` per request to get a fresh rate-limit\n * bucket each time. Headers are consulted only where there is no socket — the\n * web/edge entry, where the platform terminates the connection.\n */\nexport function resolveClientIp(req: ClientRequestLike): string | undefined {\n // 1. What the runtime computed. Express derives `req.ip` from `trust proxy`,\n // Fastify from `trustProxy` — a value vetted against a CONFIGURED proxy,\n // which is the only reason to believe a forwarded header at all.\n if (typeof req.ip === 'string' && req.ip.length > 0) return req.ip\n\n // 2. The socket. Unspoofable, and present on every node runtime.\n //\n // This MUST precede the headers. A raw Fastify / h3 request has no\n // `req.ip`, so consulting `x-forwarded-for` first let a DIRECT client send\n // a different value per request and land in a fresh rate-limit bucket each\n // time — evading the limit entirely. Behind a real proxy the socket is the\n // proxy's address, which buckets clients together; the fix for that is to\n // configure the runtime's trust-proxy setting so step 1 answers, not to\n // trust an unverified header here.\n const socketAddress = req.socket?.remoteAddress\n if (socketAddress) return socketAddress\n\n // 3. Headers, only where there is no socket at all — the web/edge entry,\n // where the platform terminates the connection and these are the only\n // signal available.\n const h = req.headers\n return (\n firstHop(headerValue(h, 'cf-connecting-ip')) ??\n firstHop(headerValue(h, 'x-forwarded-for')) ??\n firstHop(headerValue(h, 'x-real-ip'))\n )\n}\n\n/** Pathname only — `req.url` carries the query string, and `req.path` is Express-only. */\nexport function resolvePathname(req: ClientRequestLike): string {\n if (typeof req.path === 'string' && req.path.length > 0) return req.path\n const url = req.url\n if (!url) return '/'\n // `WebRequestShim` already reduces the WHATWG `Request.url` to path+search,\n // but an ABSOLUTE url would otherwise be returned whole — so a skip prefix\n // like `/health` would never match `https://host/health`.\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url)) {\n try {\n return new URL(url).pathname || '/'\n } catch {\n return '/'\n }\n }\n const q = url.indexOf('?')\n return (q === -1 ? url : url.slice(0, q)) || '/'\n}\n"],"mappings":";;;;;;;;;;AAsCA,SAAS,SAAS,MAAoC,CACpD,OAAO,OAAO,OAAU,UAAY,MAAM,OAAS,EAAI,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,EAAI,IAAA,EACvF,CAGA,SAAS,YACP,QACA,KACS,CACJ,WAIL,OADI,OAAQ,QAAoB,KAAQ,WAAoB,QAAoB,IAAI,IAAI,EAChF,QAA0D,KACpE,CAgBA,SAAgB,gBAAgB,IAA4C,CAI1E,GAAI,OAAO,IAAI,IAAO,UAAY,IAAI,GAAG,OAAS,EAAG,OAAO,IAAI,GAWhE,IAAM,cAAgB,IAAI,QAAQ,cAClC,GAAI,cAAe,OAAO,cAK1B,IAAM,EAAI,IAAI,QACd,OACE,SAAS,YAAY,EAAG,kBAAkB,CAAC,GAC3C,SAAS,YAAY,EAAG,iBAAiB,CAAC,GAC1C,SAAS,YAAY,EAAG,WAAW,CAAC,CAExC,CAGA,SAAgB,gBAAgB,IAAgC,CAC9D,GAAI,OAAO,IAAI,MAAS,UAAY,IAAI,KAAK,OAAS,EAAG,OAAO,IAAI,KACpE,IAAM,IAAM,IAAI,IAChB,GAAI,CAAC,IAAK,MAAO,IAIjB,GAAI,2BAA2B,KAAK,GAAG,EACrC,GAAI,CACF,OAAO,IAAI,IAAI,GAAG,CAAC,CAAC,UAAY,GAClC,MAAQ,CACN,MAAO,GACT,CAEF,IAAM,EAAI,IAAI,QAAQ,GAAG,EACzB,OAAQ,IAAM,GAAK,IAAM,IAAI,MAAM,EAAG,CAAC,IAAM,GAC/C"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.4.0
2
+ * @forinda/kickjs v8.5.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"../env-BTOmTNsH.mjs";import{n as createConfigService,t as ConfigService}from"../config-pot4-2DS.mjs";export{ConfigService,baseEnvSchema,createConfigService,defineEnv,getEnv,loadEnv,loadEnvFromSchema,reloadEnv,resetEnvCache};
11
+ import{a as loadEnvFromSchema,i as loadEnv,n as defineEnv,o as reloadEnv,r as getEnv,s as resetEnvCache,t as baseEnvSchema}from"../env-DWP_2owJ.mjs";import{n as createConfigService,t as ConfigService}from"../config-Cajrc3qr.mjs";export{ConfigService,baseEnvSchema,createConfigService,defineEnv,getEnv,loadEnv,loadEnvFromSchema,reloadEnv,resetEnvCache};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.4.0
2
+ * @forinda/kickjs v8.5.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{i as loadEnv,o as reloadEnv}from"./env-BTOmTNsH.mjs";import{Service}from"./core/decorators.mjs";import{t as __decorate}from"./decorate-BbFyJ1kh.mjs";let ConfigService=class{get env(){return loadEnv()}get(key){return this.env[key]}getAll(){return Object.freeze({...this.env})}reload(){reloadEnv(),loadEnv()}isProduction(){return this.env.NODE_ENV===`production`}isDevelopment(){return this.env.NODE_ENV===`development`}isTest(){return this.env.NODE_ENV===`test`}};ConfigService=__decorate([Service()],ConfigService);function createConfigService(schema){let SchemaConfigService=class{get env(){return loadEnv(schema)}get(key){return this.env[key]}getAll(){return Object.freeze({...this.env})}reload(){reloadEnv(),loadEnv(schema)}isProduction(){return this.env.NODE_ENV===`production`}isDevelopment(){return this.env.NODE_ENV===`development`}isTest(){return this.env.NODE_ENV===`test`}};return SchemaConfigService=__decorate([Service()],SchemaConfigService),SchemaConfigService}export{createConfigService as n,ConfigService as t};
12
- //# sourceMappingURL=config-pot4-2DS.mjs.map
11
+ import{i as loadEnv,o as reloadEnv}from"./env-DWP_2owJ.mjs";import{Service}from"./core/decorators.mjs";import{t as __decorate}from"./decorate-DHyinlPY.mjs";let ConfigService=class{get env(){return loadEnv()}get(key){return this.env[key]}getAll(){return Object.freeze({...this.env})}reload(){reloadEnv(),loadEnv()}isProduction(){return this.env.NODE_ENV===`production`}isDevelopment(){return this.env.NODE_ENV===`development`}isTest(){return this.env.NODE_ENV===`test`}};ConfigService=__decorate([Service()],ConfigService);function createConfigService(schema){let SchemaConfigService=class{get env(){return loadEnv(schema)}get(key){return this.env[key]}getAll(){return Object.freeze({...this.env})}reload(){reloadEnv(),loadEnv(schema)}isProduction(){return this.env.NODE_ENV===`production`}isDevelopment(){return this.env.NODE_ENV===`development`}isTest(){return this.env.NODE_ENV===`test`}};return SchemaConfigService=__decorate([Service()],SchemaConfigService),SchemaConfigService}export{createConfigService as n,ConfigService as t};
12
+ //# sourceMappingURL=config-Cajrc3qr.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-pot4-2DS.mjs","names":[],"sources":["../src/config/config-service.ts"],"sourcesContent":["import type { z } from 'zod'\nimport { Service } from '../core/decorators'\nimport type { EnvKey } from '../core/decorators'\nimport { loadEnv, reloadEnv } from './env'\n\n/**\n * Constraint that locks the `key` argument to known `EnvKey` literals\n * once `kick typegen` has populated `KickEnv`. Mirrors the same trick\n * used by the `@Value` decorator: when `EnvKey` resolves to `never`\n * (no schema yet), accept any string for back-compat; once typegen\n * has run, require a known key.\n */\ntype ConfigKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Resolve the return type for a `ConfigService.get(key)` call. When\n * `KickEnv` is populated and the key is a known one, returns the\n * schema-inferred type. Otherwise falls back to the caller-provided\n * generic `T` (default `any`) so existing call sites keep working.\n */\ntype ConfigValue<K extends string, T> = K extends EnvKey ? KickEnv[K] : T\n\n/**\n * Injectable service for accessing typed environment configuration.\n *\n * Once `kick typegen` has run against your `src/env.ts`, both `get()`\n * and `getAll()` are fully typed against the schema with **no extra\n * setup** — you just inject `ConfigService` and call it:\n *\n * ```ts\n * @Service()\n * class DatabaseService {\n * @Autowired() private readonly config!: ConfigService\n *\n * connect() {\n * const url = this.config.get('DATABASE_URL') // typed string\n * const port = this.config.get('PORT') // typed number\n * // const bad = this.config.get('NOPE') // tsc error\n * }\n * }\n * ```\n *\n * Without typegen the previous `<T = any>` generic still works, so\n * legacy code calling `config.get<string>('DATABASE_URL')` keeps\n * compiling.\n */\n@Service()\nexport class ConfigService {\n /**\n * Read the current parsed env on every access. We deliberately do **not**\n * snapshot in the constructor: if `ConfigService` is instantiated before\n * the user's `defineEnv()` schema has been registered (e.g. DI wires it\n * eagerly, or HMR's `reloadEnv()` cleared the cache between rebuilds),\n * an eager snapshot would freeze the base shape and never see the\n * extended values — even though `@Value()` (which reads lazily through\n * `Container._envResolver`) would resolve them correctly.\n *\n * `loadEnv()` is sticky: once an extended schema has been parsed it\n * keeps returning that schema's values, so this getter is cheap (one\n * cached lookup) and always consistent with `@Value`.\n */\n private get env(): Record<string, any> {\n return loadEnv() as Record<string, any>\n }\n\n /**\n * Get an env variable by key.\n *\n * - When `kick typegen` has run, `key` is constrained to known\n * `KickEnv` keys and the return type is inferred from the schema.\n * - When `KickEnv` is empty (no typegen run yet), any string is\n * accepted and `T` falls back to the explicit generic for\n * back-compat.\n */\n get<K extends string, T = any>(key: ConfigKey<K>): ConfigValue<K, T> {\n return this.env[key] as ConfigValue<K, T>\n }\n\n /**\n * Get all env config as a readonly object. When `KickEnv` is\n * populated by typegen, returns `Readonly<KickEnv>` so the caller\n * gets autocomplete on every key.\n */\n getAll(): [EnvKey] extends [never] ? Readonly<Record<string, any>> : Readonly<KickEnv> {\n return Object.freeze({ ...this.env }) as any\n }\n\n /** Reload env from .env file (for HMR / file-watch scenarios) */\n reload(): void {\n reloadEnv()\n // Touch loadEnv() so the resolver + cache are repopulated immediately.\n loadEnv()\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n}\n\n/**\n * Typed config service interface — provides autocomplete for env keys.\n * Use with `createConfigService()` for full type safety.\n */\nexport interface TypedConfigService<TEnv extends Record<string, any>> {\n get<K extends string & keyof TEnv>(key: K): TEnv[K]\n getAll(): Readonly<TEnv>\n reload(): void\n isProduction(): boolean\n isDevelopment(): boolean\n isTest(): boolean\n}\n\n/**\n * Create a typed ConfigService class bound to a specific Zod env schema.\n * The returned class is DI-injectable via `@Service()` and provides\n * fully typed `get()` with key autocomplete.\n *\n * @example\n * ```ts\n * const envSchema = defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Create a typed, injectable config service\n * export const AppConfigService = createConfigService(envSchema)\n * export type AppConfigService = InstanceType<typeof AppConfigService>\n *\n * // In a controller or service — inject it:\n * @Controller()\n * class UserController {\n * constructor(@Inject(AppConfigService) private config: AppConfigService) {\n * const dbUrl = config.get('DATABASE_URL') // string — autocompletes!\n * const bad = config.get('NOPE') // TS error\n * }\n * }\n * ```\n */\nexport function createConfigService<T extends z.ZodObject<any>>(schema: T) {\n type TEnv = z.infer<T>\n\n @Service()\n class SchemaConfigService implements TypedConfigService<TEnv> {\n /** See note on `ConfigService.env` — same eager-snapshot hazard. */\n private get env(): TEnv {\n return loadEnv(schema)\n }\n\n get<K extends string & keyof TEnv>(key: K): TEnv[K] {\n return this.env[key]\n }\n\n getAll(): Readonly<TEnv> {\n return Object.freeze({ ...this.env })\n }\n\n reload(): void {\n reloadEnv()\n loadEnv(schema)\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n }\n\n return SchemaConfigService as {\n new (): TypedConfigService<TEnv>\n }\n}\n"],"mappings":";;;;;;;;;;4JA+CO,IAAM,cAAN,KAAoB,CAczB,IAAY,KAA2B,CACrC,OAAO,QAAQ,CACjB,CAWA,IAA+B,IAAsC,CACnE,OAAO,KAAK,IAAI,IAClB,CAOA,QAAuF,CACrF,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,GAAI,CAAC,CACtC,CAGA,QAAe,CACb,UAAU,EAEV,QAAQ,CACV,CAEA,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,YAC/B,CAEA,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,aAC/B,CAEA,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,MAC/B,CACF,EA3DC,cAAA,WAAA,CAAA,QAAQ,CAAA,EAAA,aAAA,EAsGT,SAAgB,oBAAgD,OAAW,CAGzE,IACM,oBADN,KAC8D,CAE5D,IAAY,KAAY,CACtB,OAAO,QAAQ,MAAM,CACvB,CAEA,IAAmC,IAAiB,CAClD,OAAO,KAAK,IAAI,IAClB,CAEA,QAAyB,CACvB,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,GAAI,CAAC,CACtC,CAEA,QAAe,CACb,UAAU,EACV,QAAQ,MAAM,CAChB,CAEA,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,YAC/B,CAEA,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,aAC/B,CAEA,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,MAC/B,CACF,EAEA,MAjCC,qBAAA,WAAA,CAAA,QAAQ,CAAA,EAAA,mBAAA,EAiCF,mBAGT"}
1
+ {"version":3,"file":"config-Cajrc3qr.mjs","names":[],"sources":["../src/config/config-service.ts"],"sourcesContent":["import type { z } from 'zod'\nimport { Service } from '../core/decorators'\nimport type { EnvKey } from '../core/decorators'\nimport { loadEnv, reloadEnv } from './env'\n\n/**\n * Constraint that locks the `key` argument to known `EnvKey` literals\n * once `kick typegen` has populated `KickEnv`. Mirrors the same trick\n * used by the `@Value` decorator: when `EnvKey` resolves to `never`\n * (no schema yet), accept any string for back-compat; once typegen\n * has run, require a known key.\n */\ntype ConfigKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Resolve the return type for a `ConfigService.get(key)` call. When\n * `KickEnv` is populated and the key is a known one, returns the\n * schema-inferred type. Otherwise falls back to the caller-provided\n * generic `T` (default `any`) so existing call sites keep working.\n */\ntype ConfigValue<K extends string, T> = K extends EnvKey ? KickEnv[K] : T\n\n/**\n * Injectable service for accessing typed environment configuration.\n *\n * Once `kick typegen` has run against your `src/env.ts`, both `get()`\n * and `getAll()` are fully typed against the schema with **no extra\n * setup** — you just inject `ConfigService` and call it:\n *\n * ```ts\n * @Service()\n * class DatabaseService {\n * @Autowired() private readonly config!: ConfigService\n *\n * connect() {\n * const url = this.config.get('DATABASE_URL') // typed string\n * const port = this.config.get('PORT') // typed number\n * // const bad = this.config.get('NOPE') // tsc error\n * }\n * }\n * ```\n *\n * Without typegen the previous `<T = any>` generic still works, so\n * legacy code calling `config.get<string>('DATABASE_URL')` keeps\n * compiling.\n */\n@Service()\nexport class ConfigService {\n /**\n * Read the current parsed env on every access. We deliberately do **not**\n * snapshot in the constructor: if `ConfigService` is instantiated before\n * the user's `defineEnv()` schema has been registered (e.g. DI wires it\n * eagerly, or HMR's `reloadEnv()` cleared the cache between rebuilds),\n * an eager snapshot would freeze the base shape and never see the\n * extended values — even though `@Value()` (which reads lazily through\n * `Container._envResolver`) would resolve them correctly.\n *\n * `loadEnv()` is sticky: once an extended schema has been parsed it\n * keeps returning that schema's values, so this getter is cheap (one\n * cached lookup) and always consistent with `@Value`.\n */\n private get env(): Record<string, any> {\n return loadEnv() as Record<string, any>\n }\n\n /**\n * Get an env variable by key.\n *\n * - When `kick typegen` has run, `key` is constrained to known\n * `KickEnv` keys and the return type is inferred from the schema.\n * - When `KickEnv` is empty (no typegen run yet), any string is\n * accepted and `T` falls back to the explicit generic for\n * back-compat.\n */\n get<K extends string, T = any>(key: ConfigKey<K>): ConfigValue<K, T> {\n return this.env[key] as ConfigValue<K, T>\n }\n\n /**\n * Get all env config as a readonly object. When `KickEnv` is\n * populated by typegen, returns `Readonly<KickEnv>` so the caller\n * gets autocomplete on every key.\n */\n getAll(): [EnvKey] extends [never] ? Readonly<Record<string, any>> : Readonly<KickEnv> {\n return Object.freeze({ ...this.env }) as any\n }\n\n /** Reload env from .env file (for HMR / file-watch scenarios) */\n reload(): void {\n reloadEnv()\n // Touch loadEnv() so the resolver + cache are repopulated immediately.\n loadEnv()\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n}\n\n/**\n * Typed config service interface — provides autocomplete for env keys.\n * Use with `createConfigService()` for full type safety.\n */\nexport interface TypedConfigService<TEnv extends Record<string, any>> {\n get<K extends string & keyof TEnv>(key: K): TEnv[K]\n getAll(): Readonly<TEnv>\n reload(): void\n isProduction(): boolean\n isDevelopment(): boolean\n isTest(): boolean\n}\n\n/**\n * Create a typed ConfigService class bound to a specific Zod env schema.\n * The returned class is DI-injectable via `@Service()` and provides\n * fully typed `get()` with key autocomplete.\n *\n * @example\n * ```ts\n * const envSchema = defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Create a typed, injectable config service\n * export const AppConfigService = createConfigService(envSchema)\n * export type AppConfigService = InstanceType<typeof AppConfigService>\n *\n * // In a controller or service — inject it:\n * @Controller()\n * class UserController {\n * constructor(@Inject(AppConfigService) private config: AppConfigService) {\n * const dbUrl = config.get('DATABASE_URL') // string — autocompletes!\n * const bad = config.get('NOPE') // TS error\n * }\n * }\n * ```\n */\nexport function createConfigService<T extends z.ZodObject<any>>(schema: T) {\n type TEnv = z.infer<T>\n\n @Service()\n class SchemaConfigService implements TypedConfigService<TEnv> {\n /** See note on `ConfigService.env` — same eager-snapshot hazard. */\n private get env(): TEnv {\n return loadEnv(schema)\n }\n\n get<K extends string & keyof TEnv>(key: K): TEnv[K] {\n return this.env[key]\n }\n\n getAll(): Readonly<TEnv> {\n return Object.freeze({ ...this.env })\n }\n\n reload(): void {\n reloadEnv()\n loadEnv(schema)\n }\n\n isProduction(): boolean {\n return this.env.NODE_ENV === 'production'\n }\n\n isDevelopment(): boolean {\n return this.env.NODE_ENV === 'development'\n }\n\n isTest(): boolean {\n return this.env.NODE_ENV === 'test'\n }\n }\n\n return SchemaConfigService as {\n new (): TypedConfigService<TEnv>\n }\n}\n"],"mappings":";;;;;;;;;;4JA+CO,IAAM,cAAN,KAAoB,CAczB,IAAY,KAA2B,CACrC,OAAO,QAAQ,CACjB,CAWA,IAA+B,IAAsC,CACnE,OAAO,KAAK,IAAI,IAClB,CAOA,QAAuF,CACrF,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,GAAI,CAAC,CACtC,CAGA,QAAe,CACb,UAAU,EAEV,QAAQ,CACV,CAEA,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,YAC/B,CAEA,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,aAC/B,CAEA,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,MAC/B,CACF,EA3DC,cAAA,WAAA,CAAA,QAAQ,CAAA,EAAA,aAAA,EAsGT,SAAgB,oBAAgD,OAAW,CAGzE,IACM,oBADN,KAC8D,CAE5D,IAAY,KAAY,CACtB,OAAO,QAAQ,MAAM,CACvB,CAEA,IAAmC,IAAiB,CAClD,OAAO,KAAK,IAAI,IAClB,CAEA,QAAyB,CACvB,OAAO,OAAO,OAAO,CAAE,GAAG,KAAK,GAAI,CAAC,CACtC,CAEA,QAAe,CACb,UAAU,EACV,QAAQ,MAAM,CAChB,CAEA,cAAwB,CACtB,OAAO,KAAK,IAAI,WAAa,YAC/B,CAEA,eAAyB,CACvB,OAAO,KAAK,IAAI,WAAa,aAC/B,CAEA,QAAkB,CAChB,OAAO,KAAK,IAAI,WAAa,MAC/B,CACF,EAEA,MAjCC,qBAAA,WAAA,CAAA,QAAQ,CAAA,EAAA,mBAAA,EAiCF,mBAGT"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.4.0
2
+ * @forinda/kickjs v8.5.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
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-C_bLigwT.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}`: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(`
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-Ceheg5tj.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}`: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
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
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
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.
@@ -114,4 +114,4 @@ the second controller under a different module path / version:
114
114
  routes() {
115
115
  return { path: '/tasks', controller: TasksController, version: 2 }
116
116
  }`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{method,path,owner,prior}})}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 _onCreate=null;static _envResolver=null;isGlobal=!1;static _requestStoreProvider=null;static getInstance(){return Container.instance||(Container.instance=new Container,Container.instance.isGlobal=!0),Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container.instance.isGlobal=!0,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(){let container=new Container;return Container._onCreate?.(container),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=(this.isGlobal?store.factories.find(r=>tokenName(r.token)===name):void 0)?.factory===factory,existingInstance=this.isGlobal&&factoryUnchanged?store.resolvedInstances.get(name):void 0;if(this.isGlobal&&!factoryUnchanged&&store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`})),this.isGlobal){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);if(this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),this.isGlobal){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`&&reg.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||(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||(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||(reg.firstResolvedAt=Date.now()),reg.scope===`singleton`&&(reg.instance=instance,this.isGlobal&&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||(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`){let parent=tokenName(reg.target),dep=tokenName(token),fix=getClassMetaOrUndefined(METADATA.CLASS_KIND,reg.target)===`controller`?`@Controller() is always SINGLETON, so inject "${dep}" with @Autowired() instead of the constructor.`:`Give "${parent}" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), or inject "${dep}" with @Autowired() instead of the constructor.`;throw Error(`Cannot inject REQUEST-scoped "${dep}" into SINGLETON "${parent}". Singletons outlive requests. ${fix}`)}}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,KickError as c,createToken as i,formatKickError as l,tokenName as n,duplicateRouteError as o,INJECTION_TOKEN as r,moduleRouteMissingControllerError as s,Container as t,_setAssetResolver as u};
117
- //# sourceMappingURL=container-BB_XC2ww.mjs.map
117
+ //# sourceMappingURL=container-Cj2hqlfm.mjs.map