@forinda/kickjs-di 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Felix Orinda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @forinda/kickjs-di
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The dependency injection container behind [KickJS](https://kickjs.app/): typed tokens, decorators, a REQUEST scope, `@PostConstruct` / `@PreDestroy`, and registrations that survive HMR.
4
+
5
+ KickJS apps already have it: `@forinda/kickjs` depends on it and re-exports everything. Import from `@forinda/kickjs` and don't install this package next to it, because two copies would each keep their own container.
6
+
7
+ ```ts
8
+ import 'reflect-metadata'
9
+ import { Container, Service, Inject, createToken } from '@forinda/kickjs-di'
10
+
11
+ const GREETING = createToken<string>('greeting')
12
+
13
+ @Service()
14
+ class Greeter {
15
+ constructor(@Inject(GREETING) private readonly greeting: string) {}
16
+ hi(name: string) {
17
+ return `${this.greeting}, ${name}`
18
+ }
19
+ }
20
+
21
+ const container = Container.getInstance()
22
+ container.registerInstance(GREETING, 'Hello')
23
+ container.resolve(Greeter).hi('Ada') // 'Hello, Ada'
24
+ ```
25
+
26
+ It uses legacy decorators: set `experimentalDecorators` and `emitDecoratorMetadata` in `tsconfig.json`.
27
+
28
+ Full reference: [kickjs.app/api/di](https://kickjs.app/api/di).
@@ -0,0 +1,718 @@
1
+
2
+ import "reflect-metadata";
3
+ //#region src/interfaces.d.ts
4
+ /** Lifecycle scope for DI registrations */
5
+ export declare enum Scope {
6
+ SINGLETON = "singleton",
7
+ TRANSIENT = "transient",
8
+ /** One instance per HTTP request, cached in AsyncLocalStorage. Throws outside request context. */
9
+ REQUEST = "request"
10
+ }
11
+ /** Generic constructor type */
12
+ type Constructor<T = any> = new (...args: any[]) => T;
13
+ /** Options for @Service, @Repository, @Component decorators */
14
+ interface ServiceOptions {
15
+ scope?: Scope;
16
+ }
17
+ /** Fluent builder type */
18
+ type BuilderOf<T> = { [K in keyof T as T[K] extends Function ? never : K]-?: (value: T[K]) => BuilderOf<T>; } & {
19
+ build(): T;
20
+ };
21
+ /** Class with static builder() method */
22
+ interface Buildable<T> {
23
+ builder(): BuilderOf<T>;
24
+ }
25
+ /** Decorator class kind for devtools introspection */
26
+ type ClassKind = 'service' | 'controller' | 'repository' | 'component' | 'injectable' | 'factory' | 'instance' | 'unknown';
27
+ /** PostConstruct lifecycle status */
28
+ type PostConstructStatus = 'pending' | 'completed' | 'failed' | 'skipped';
29
+ /**
30
+ * String metadata keys for the DI system + decorator subsystem. Every
31
+ * key starts with the `kick:` prefix; the suffix is a short identifier
32
+ * that may itself contain `:`, `-`, or `_` separators (historical —
33
+ * keys grew alongside the framework). The §22 v4 convention is "no
34
+ * Symbols", not "use a strict naming scheme" — Symbols don't survive
35
+ * JSON serialisation, can't be addressed by string from worker
36
+ * threads, and don't show up in DevTools snapshots.
37
+ *
38
+ * `reflect-metadata` accepts both Symbol and string keys identically,
39
+ * so the consumer-side change is internal — references go through the
40
+ * `METADATA` enum's named members (e.g. `METADATA.INJECT`), not the
41
+ * underlying string.
42
+ *
43
+ * The `design:*` entries match what TypeScript's
44
+ * `emitDecoratorMetadata` writes; those names are fixed by the
45
+ * compiler + must stay as-is.
46
+ */
47
+ export declare const DI_METADATA: {
48
+ readonly INJECTABLE: 'kick:injectable';
49
+ readonly SCOPE: 'kick:scope';
50
+ readonly CLASS_KIND: 'kick:class-kind';
51
+ readonly AUTOWIRED: 'kick:autowired';
52
+ readonly INJECT: 'kick:inject';
53
+ readonly POST_CONSTRUCT: 'kick:post_construct';
54
+ readonly PRE_DESTROY: 'kick:pre_destroy';
55
+ readonly BUILDER: 'kick:builder';
56
+ readonly VALUE: 'kick:value';
57
+ readonly PARAM_TYPES: 'design:paramtypes';
58
+ readonly PROPERTY_TYPE: 'design:type';
59
+ readonly RETURN_TYPE: 'design:returntype';
60
+ };
61
+ //#endregion
62
+ //#region src/errors.d.ts
63
+ /**
64
+ * The container's own errors, by stable code. A host can replace the factory
65
+ * (`Container._error`) to turn them into richer errors — kickjs maps each
66
+ * code to its catalogue entry, with the cause and the fix.
67
+ */
68
+ type DiErrorCode =
69
+ /** Nothing is registered for the token. */
70
+ 'KICK001' |
71
+ /** A REQUEST-scoped token, but no request store is installed. */
72
+ 'KICK002' |
73
+ /** A REQUEST-scoped token, resolved outside a request. */
74
+ 'KICK003' |
75
+ /** `@Value` key not set, and no default. */
76
+ 'KICK004';
77
+ export declare class DiError extends Error {
78
+ readonly code: DiErrorCode;
79
+ /** The token name, or the env key for KICK004. */
80
+ readonly subject: string;
81
+ constructor(code: DiErrorCode,
82
+ /** The token name, or the env key for KICK004. */
83
+ subject: string, message: string);
84
+ }
85
+ export declare function diError(code: DiErrorCode, subject: string): Error;
86
+ /** Where the container reports non-fatal problems. A host can install its own. */
87
+ interface DiLogger {
88
+ warn(message: string): void;
89
+ error(err: unknown, message: string): void;
90
+ }
91
+ /** Drops warnings; errors (a `@PostConstruct` that threw) go to `console.error`. */
92
+ export declare const defaultLogger: DiLogger;
93
+ //#endregion
94
+ //#region src/token.d.ts
95
+ /**
96
+ * Collision-safe injection tokens with phantom type parameters.
97
+ *
98
+ * `createToken<T>(name)` is the recommended way to declare any DI token
99
+ * that isn't a class. Each call returns a unique frozen object — even if
100
+ * two files write `createToken<X>('Logger')`, they produce two distinct
101
+ * references that the container treats as different tokens. Collisions
102
+ * become impossible by construction, not by convention.
103
+ *
104
+ * The phantom type parameter `T` lets `container.resolve(token)` infer
105
+ * the correct return type without any codegen.
106
+ *
107
+ * ## Hardening layers
108
+ *
109
+ * KickJS has four DI token kinds, listed from safest to riskiest:
110
+ *
111
+ * 1. **Class identity** — `container.resolve(UserService)`. Reference
112
+ * equality, never collides. Type-safe via `Constructor<T>` overload.
113
+ * 2. **`createToken<T>(name)`** — frozen object identity, never collides.
114
+ * Type-safe via the `InjectionToken<T>` overload.
115
+ * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`
116
+ * is interned and CAN collide. Discouraged.
117
+ * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.
118
+ * Reserved for legacy code and runtime-computed token names.
119
+ *
120
+ * Prefer (1) and (2). Use (4) only when you have to.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * // src/tokens.ts
125
+ * export const DATABASE_URL = createToken<string>('config.database.url')
126
+ * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')
127
+ *
128
+ * // bootstrap
129
+ * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)
130
+ *
131
+ * // anywhere else
132
+ * const url = container.resolve(DATABASE_URL) // typed as `string`
133
+ * ```
134
+ *
135
+ * @module @forinda/kickjs/core/token
136
+ */
137
+ /**
138
+ * Symbol marker stamped on every InjectionToken so the container can
139
+ * detect them at runtime regardless of which package created them.
140
+ *
141
+ * Uses `Symbol.for()` so cross-package isolation (different copies of
142
+ * `@forinda/kickjs` loaded under different module identities) still
143
+ * shares the same marker.
144
+ */
145
+ export declare const INJECTION_TOKEN: unique symbol;
146
+ /**
147
+ * A type-safe DI token. Each `createToken()` call returns a unique frozen
148
+ * object identified by reference, not by the `name` string. Two
149
+ * `createToken<X>('foo')` calls in different files produce two distinct
150
+ * tokens that the container treats independently.
151
+ *
152
+ * The phantom `_type` parameter is never assigned at runtime — it exists
153
+ * solely to thread the type through `container.resolve(token)`.
154
+ */
155
+ interface InjectionToken<T> {
156
+ readonly [INJECTION_TOKEN]: true;
157
+ /** Descriptive name used in error messages, logs, and devtools */
158
+ readonly name: string;
159
+ /** Phantom — never assigned at runtime */
160
+ readonly _type?: T;
161
+ }
162
+ /**
163
+ * Create a collision-safe injection token.
164
+ *
165
+ * The returned token is a frozen object identified by reference, not by
166
+ * the `name` string. The `name` is used only for error messages, logs,
167
+ * and debugging.
168
+ */
169
+ export declare function createToken<T>(name: string): InjectionToken<T>;
170
+ /** Type guard — returns `true` if `value` is an InjectionToken */
171
+ export declare function isInjectionToken(value: unknown): value is InjectionToken<unknown>;
172
+ //#endregion
173
+ //#region src/container.d.ts
174
+ /** Internal registration entry that tracks how a dependency should be resolved */
175
+ interface Registration {
176
+ target: Constructor;
177
+ scope: Scope;
178
+ instance?: any;
179
+ factory?: () => any;
180
+ kind: ClassKind;
181
+ resolveCount: number;
182
+ lastResolvedAt?: number;
183
+ firstResolvedAt?: number;
184
+ resolveDurationMs?: number;
185
+ postConstructStatus: PostConstructStatus;
186
+ dependencies: string[];
187
+ /** Lazily built instantiation plan — see {@link Container.buildPlan}. */
188
+ plan?: InstantiationPlan;
189
+ }
190
+ /**
191
+ * Precomputed instantiation recipe for a class. Class metadata is immutable
192
+ * after definition, so the Reflect reads (5 per create) and the derived
193
+ * structures only need computing ONCE — previously they re-ran on every
194
+ * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.
195
+ */
196
+ interface InstantiationPlan {
197
+ /** Ordered, validated constructor parameter tokens. */
198
+ ctorTokens: any[];
199
+ /** `@Autowired` properties: [property, resolved token]. */
200
+ autowired: Array<[string, any]>;
201
+ /** `@Value` properties: [property, env config]. */
202
+ values: Array<[string, {
203
+ envKey: string;
204
+ defaultValue?: any;
205
+ }]>;
206
+ /** Host-injected properties (`_propertyInjectors`): [property, getter]. */
207
+ injected: Array<[string, () => unknown]>;
208
+ /** `@PostConstruct` method name, if any. */
209
+ postConstruct?: string | symbol;
210
+ }
211
+ /** Format a token for display in error messages */
212
+ export declare function tokenName(token: any): string;
213
+ /** A single container change event */
214
+ interface ContainerChangeEvent {
215
+ token: string;
216
+ event: 'registered' | 'resolved' | 'invalidated';
217
+ kind?: ClassKind;
218
+ timestamp: number;
219
+ }
220
+ /** Listener that receives batched change events */
221
+ type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void;
222
+ /**
223
+ * Type-safe registry of string DI tokens. Augmented by `kick typegen` —
224
+ * the generated `.kickjs/types/registry.d.ts` adds entries like:
225
+ *
226
+ * ```ts
227
+ * declare module '@forinda/kickjs' {
228
+ * interface KickJsRegistry {
229
+ * UserService: import('../../src/modules/users/user.service').UserService
230
+ * }
231
+ * }
232
+ * ```
233
+ *
234
+ * Once typegen has run, `container.resolve('UserService')` returns the
235
+ * correct type instead of `any`. Class-token resolution
236
+ * (`container.resolve(UserService)`) is already type-safe via the
237
+ * `Constructor<T>` overload and does not need typegen.
238
+ */
239
+ interface KickJsRegistry {}
240
+ /**
241
+ * Inversion-of-Control (IoC) container that manages dependency registration,
242
+ * resolution, and lifecycle. Implements the Singleton pattern so all parts of
243
+ * the application share a single container instance.
244
+ *
245
+ * Supports constructor injection, property injection (@Autowired),
246
+ * factory registrations, and lifecycle hooks (@PostConstruct).
247
+ *
248
+ * Persistent state (DB connections, Redis, etc.) is stored on globalThis so
249
+ * it survives both Container.reset() and Vite module re-evaluation.
250
+ */
251
+ export declare class Container {
252
+ private static instance;
253
+ private registrations;
254
+ private resolving;
255
+ private changeListeners;
256
+ private pendingChanges;
257
+ private notifyTimer;
258
+ private static readonly DEBOUNCE_MS;
259
+ /** Callback set by the decorators module to flush pending registrations */
260
+ static _onReady: ((container: Container) => void) | null;
261
+ /** Callback invoked on reset so decorators can update their container reference */
262
+ static _onReset: ((container: Container) => void) | null;
263
+ /**
264
+ * Seeds a NEWLY CREATED container with the decorator registrations, without
265
+ * adopting it as the global one. `_onReset` does both; an isolated container
266
+ * must only have the first half, or it would capture every subsequent
267
+ * decorator registration in the process.
268
+ */
269
+ static _onCreate: ((container: Container) => void) | null;
270
+ /**
271
+ * Environment resolver for @Value decorator. Set by the unified
272
+ * `@forinda/kickjs` config layer to return Zod-validated, type-coerced
273
+ * env values instead of raw process.env strings.
274
+ */
275
+ static _envResolver: ((key: string) => any) | null;
276
+ /**
277
+ * Builds the container's errors from a stable code and the token name (or
278
+ * env key). The default gives a plain `DiError`; kickjs installs one that
279
+ * returns its catalogued `KickError`.
280
+ */
281
+ static _error: (code: DiErrorCode, subject: string) => Error;
282
+ /** Where non-fatal problems go (an async `@PostConstruct`, one that threw). Errors reach `console.error` by default. */
283
+ static _logger: DiLogger;
284
+ /**
285
+ * Extra lazy property injections, keyed by metadata key. For each property
286
+ * a class stores under that key (a `Map` on the prototype, as `@Value`
287
+ * does), `prepare(config, prop)` runs once when the class's plan is built
288
+ * — so it can reject a bad config up front — and returns the getter each
289
+ * instance's property reads through. kickjs adds `@Asset` this way.
290
+ */
291
+ static _propertyInjectors: Map<string, (config: any, prop: string) => () => unknown>;
292
+ /**
293
+ * Request store provider for REQUEST-scoped DI. Set by the HTTP layer
294
+ * inside `@forinda/kickjs` to return the current request's
295
+ * AsyncLocalStorage store. Returns { instances: Map, values: Map } or
296
+ * null if outside a request.
297
+ */
298
+ /**
299
+ * Whether this container is the global singleton.
300
+ *
301
+ * The persistent store exists to carry the GLOBAL container's registrations
302
+ * across HMR and module re-evaluation. An isolated container is per-test and
303
+ * must neither write to it — a `registerInstance` override was replayed into
304
+ * every later `Container.reset()` — nor read from it, which would pull global
305
+ * state into a container created to be free of it.
306
+ */
307
+ private isGlobal;
308
+ static _requestStoreProvider: (() => {
309
+ instances: Map<any, any>;
310
+ values: Map<any, any>;
311
+ } | null) | null;
312
+ static getInstance(): Container;
313
+ /**
314
+ * Resets the container by replacing the singleton with a fresh instance.
315
+ * Persistent registrations (DB, Redis, etc.) are replayed from globalThis
316
+ * so they survive both reset() and Vite module re-evaluation.
317
+ */
318
+ static reset(): void;
319
+ /**
320
+ * Create an isolated container instance (not the global singleton).
321
+ * Useful for concurrent tests that must not share DI state.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * const container = Container.create()
326
+ * container.register(UserService, UserService)
327
+ * const svc = container.resolve(UserService)
328
+ * ```
329
+ */
330
+ static create(): Container;
331
+ /** Register a class constructor under the given token */
332
+ register(token: any, target: Constructor, scope?: Scope): void;
333
+ /** Register a factory function under the given token */
334
+ registerFactory(token: any, factory: () => any, scope?: Scope): void;
335
+ /** Register a pre-constructed singleton instance */
336
+ registerInstance(token: any, instance: any): void;
337
+ /** Check if a binding exists for the given token */
338
+ has(token: any): boolean;
339
+ /** Return a snapshot of all registered tokens with their scope and instantiation status */
340
+ getRegistrations(): Array<{
341
+ token: string;
342
+ scope: string;
343
+ kind: ClassKind;
344
+ instantiated: boolean;
345
+ resolveCount: number;
346
+ lastResolvedAt?: number;
347
+ firstResolvedAt?: number;
348
+ resolveDurationMs?: number;
349
+ postConstructStatus: PostConstructStatus;
350
+ dependencies: string[];
351
+ }>;
352
+ /**
353
+ * Resolve a dependency by its token.
354
+ *
355
+ * Four overloads, in order of preference:
356
+ * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.
357
+ * Collision-safe by construction; no codegen required.
358
+ * - **Class constructor** → returns an instance of that class. Always
359
+ * type-safe; no codegen required.
360
+ * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →
361
+ * returns the augmented type.
362
+ * - **Anything else** → returns `any`. Use this only for legacy code or
363
+ * string tokens that haven't been registered with typegen yet.
364
+ */
365
+ resolve<T>(token: InjectionToken<T>): T;
366
+ resolve<T>(token: Constructor<T>): T;
367
+ resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K];
368
+ resolve<T = any>(token: any): T;
369
+ /** Lifecycle hook called during Application.setup() after module registration */
370
+ bootstrap(): void;
371
+ /**
372
+ * Subscribe to container changes. Returns an unsubscribe function.
373
+ *
374
+ * Changes are batched (50ms debounce) so bulk operations like
375
+ * `kick g module` creating 10+ files emit ONE batch, not 10 events.
376
+ *
377
+ * Any adapter can subscribe:
378
+ * - SwaggerAdapter: re-build spec when controllers change
379
+ * - DevToolsAdapter: push SSE events to dashboard
380
+ * - WsAdapter: re-discover namespaces when controllers change
381
+ * - OtelAdapter: emit tracing spans for DI resolution
382
+ * - Custom user adapters
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * const unsub = container.onChange((changes) => {
387
+ * for (const c of changes) {
388
+ * console.log(`${c.event}: ${c.token} (${c.kind})`)
389
+ * }
390
+ * })
391
+ * // later:
392
+ * unsub()
393
+ * ```
394
+ */
395
+ onChange(callback: ContainerChangeListener): () => void;
396
+ /**
397
+ * Invalidate a specific registration. Clears the cached instance so the
398
+ * next resolve() creates a fresh one. Also invalidates dependents
399
+ * (anything that injected this token) by walking the dependency graph.
400
+ *
401
+ * Called by the Vite HMR plugin when a module file changes.
402
+ * Skips persistent registrations (DB connections, etc.).
403
+ */
404
+ invalidate(token: any, visited?: Set<Registration>): void;
405
+ /**
406
+ * Batch-emit a change event (debounced, flushed after 50ms of quiet).
407
+ * Takes the RAW token — `tokenName()` is only computed after the
408
+ * zero-listener guard, so the hot cached-singleton resolve path never pays
409
+ * the name derivation when nobody is watching (the production default).
410
+ */
411
+ private emit;
412
+ /** Flush pending change events immediately (useful in tests) */
413
+ flushChanges(): void;
414
+ /**
415
+ * Build the one-time instantiation plan for a registration. Runs on the
416
+ * FIRST createInstance for the class — the metadata it reads is immutable
417
+ * after class definition, so every later create (REQUEST scope: every
418
+ * request) skips all Reflect reads and re-derivation.
419
+ *
420
+ * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here
421
+ * too: it's a static property of the (parent, dep) registration pair, so
422
+ * checking it once at plan build is equivalent to the old per-create loop.
423
+ */
424
+ private buildPlan;
425
+ private createInstance;
426
+ /**
427
+ * Extract every dependency token name a class declares — both
428
+ * constructor params (`@Inject` + emitted `design:paramtypes`) and
429
+ * `@Autowired` properties (read lazily at injection time, but
430
+ * captured here so devtools surfaces a complete dependency graph).
431
+ *
432
+ * Without the property branch, classes that rely entirely on
433
+ * `@Autowired` show `dependencies: []` in the introspection panel
434
+ * even though they have real DI deps — see /_debug topology.
435
+ */
436
+ private extractDependencies;
437
+ private injectProperties;
438
+ }
439
+ //#endregion
440
+ //#region src/decorators.d.ts
441
+ /**
442
+ * Register a decorated class with the container. @internal — exported for
443
+ * class decorators built outside this module (`@Controller`).
444
+ */
445
+ export declare function _registerInjectable(target: any, scope: Scope): void;
446
+ /** Mark a class as injectable with lifecycle scope */
447
+ export declare function Injectable(options?: ServiceOptions): ClassDecorator;
448
+ /** Mark a class as a service (semantic alias for Injectable) */
449
+ export declare function Service(options?: ServiceOptions): ClassDecorator;
450
+ /** Mark a class as a generic managed component */
451
+ export declare function Component(options?: ServiceOptions): ClassDecorator;
452
+ /** Mark a class as a repository */
453
+ export declare function Repository(options?: ServiceOptions): ClassDecorator;
454
+ /** Mark a method as a lifecycle hook called after instantiation */
455
+ export declare function PostConstruct(): MethodDecorator;
456
+ /**
457
+ * Mark a method as a teardown hook — the counterpart to {@link PostConstruct}.
458
+ *
459
+ * For REQUEST-scoped services it runs when the request's scope closes
460
+ * (response finished or aborted): release per-request resources there
461
+ * (transactions, handles, subscriptions). May be async; errors are logged
462
+ * and swallowed so one failing hook can't break request completion.
463
+ *
464
+ * It does NOT run on a SINGLETON — the default scope — because nothing closes
465
+ * a singleton's scope. Unlike {@link PostConstruct}, which does run for
466
+ * singletons, so the pair is not symmetric. Applying it to a singleton logs a
467
+ * warning naming the class rather than failing silently.
468
+ *
469
+ * For application-lifetime resources (a connection pool, a timer, a socket
470
+ * server) use an adapter's `shutdown()` hook, which the framework runs on a
471
+ * real shutdown AND on every HMR reload, time-boxed and concurrent.
472
+ */
473
+ export declare function PreDestroy(): MethodDecorator;
474
+ /**
475
+ * A decorator that's valid as either a property decorator OR a
476
+ * constructor-parameter decorator. forinda/kick-js#235 §2 — `@Autowired`
477
+ * and `@Inject` previously diverged at the type level (property-only
478
+ * vs parameter-only) and adopters who picked the wrong name for the
479
+ * position got a cryptic TS1240 error. Both names now accept either
480
+ * position and route to the correct metadata bucket at runtime.
481
+ */
482
+ interface PropertyOrParameterDecorator {
483
+ /** Property decorator form — `@Autowired(TOKEN) private foo!: Foo`. */
484
+ (target: object, propertyKey: string | symbol): void;
485
+ /** Constructor-parameter form — `constructor(@Inject(TOKEN) foo: Foo) {}`. */
486
+ (target: object, propertyKey: string | symbol | undefined, parameterIndex: number): void;
487
+ }
488
+ /**
489
+ * Inject a dependency. Works in two positions:
490
+ *
491
+ * - **Property decorator** — `@Autowired(TOKEN) private repo!: UserRepo`.
492
+ * Resolved lazily from the container the first time the property is
493
+ * read.
494
+ * - **Constructor parameter decorator** — `constructor(@Autowired(TOKEN) repo: UserRepo) {}`.
495
+ * Resolved at instantiation; injected into the ctor call.
496
+ *
497
+ * `@Inject` is the same function under another name — they share
498
+ * runtime + types so the two names are interchangeable. Pick whichever
499
+ * reads better at the call site.
500
+ *
501
+ * Like `@Inject`, the typed string-literal overload narrows the token
502
+ * to a key of the augmented `KickJsRegistry`. After `kick typegen`
503
+ * populates the registry, `@Autowired('kick/prisma/Client')` auto-
504
+ * completes the key and typo'd literals become TS2345 errors.
505
+ */
506
+ export declare function Autowired<K extends keyof KickJsRegistry & string>(token: K): PropertyOrParameterDecorator;
507
+ export declare function Autowired(token?: unknown): PropertyOrParameterDecorator;
508
+ /**
509
+ * Inject a dependency by token. Same shape as {@link Autowired} —
510
+ * works as either a property decorator or a constructor-parameter
511
+ * decorator. Pick whichever name reads better at the call site.
512
+ *
513
+ * ## Typed string-literal overload
514
+ *
515
+ * When called with a string literal that matches a key of the
516
+ * augmented `KickJsRegistry`, TypeScript narrows the parameter type
517
+ * to the registered type. Typo'd literals become compile errors
518
+ * (the string isn't `keyof KickJsRegistry`). Class identities and
519
+ * `InjectionToken<T>` keep working unchanged via the second overload.
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * import { Service, Inject } from '@forinda/kickjs'
524
+ * import type { PrismaClient } from '@prisma/client'
525
+ *
526
+ * `@Service()`
527
+ * class UserRepo {
528
+ * constructor(@Inject('kick/prisma/Client') private db: PrismaClient) {}
529
+ * // OR property form, same decorator:
530
+ * @Inject('kick/prisma/Client') private db2!: PrismaClient
531
+ * }
532
+ * ```
533
+ *
534
+ * After `kick typegen` runs, the literal autocompletes from the
535
+ * registry and a typo (`@Inject('kick/prisma/Cleint')`) becomes a
536
+ * TS2345 error.
537
+ */
538
+ export declare function Inject<K extends keyof KickJsRegistry & string>(token: K): PropertyOrParameterDecorator;
539
+ export declare function Inject(token?: unknown): PropertyOrParameterDecorator;
540
+ /**
541
+ * Global ambient registry of typed environment variables, populated by
542
+ * `kick typegen` from the project's env schema (typically
543
+ * `src/env.ts`'s `export default defineEnv(...)`).
544
+ *
545
+ * Empty by default — declarations come from `.kickjs/types/env.ts`
546
+ * generated alongside the rest of the typegen output. Once populated,
547
+ * `@Value('PORT')` autocompletes the key, type-checks the default
548
+ * value, and `process.env.PORT` is also typed via the parallel
549
+ * `NodeJS.ProcessEnv` augmentation in the same generated file.
550
+ */
551
+ declare global {
552
+ interface KickEnv {}
553
+ }
554
+ /** A string key known to the typed env registry (`never` until typegen runs) */
555
+ type EnvKey = keyof KickEnv & string;
556
+ /**
557
+ * Look up the type of a typed env entry.
558
+ *
559
+ * Decorators can't propagate the property type from the key, so users
560
+ * still need to write the property's type — but `Env<K>` lets them
561
+ * derive it from `KickEnv` instead of duplicating the schema:
562
+ *
563
+ * ```ts
564
+ * @Service()
565
+ * class MyService {
566
+ * @Value('PORT')
567
+ * private port!: Env<'PORT'> // → number, narrowed from KickEnv['PORT']
568
+ * }
569
+ * ```
570
+ */
571
+ type Env<K extends EnvKey = EnvKey> = KickEnv[K];
572
+ /**
573
+ * Constraint that allows any string when the typed env registry is
574
+ * empty (no `kick typegen` has run yet, so existing code with raw
575
+ * string keys keeps working) but locks the argument down to known
576
+ * `EnvKey` literals once the registry is populated.
577
+ *
578
+ * Implementation: when `EnvKey` is `never`, the conditional resolves
579
+ * to the user's `K`, accepting anything. Once typegen has populated
580
+ * `KickEnv`, `EnvKey` becomes a real union and the conditional forces
581
+ * `K` to be assignable to it.
582
+ */
583
+ type ValueKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey;
584
+ /**
585
+ * Inject an environment variable value. Evaluated lazily so the env
586
+ * is available at access time, not at decoration time.
587
+ *
588
+ * If no default is provided and the env var is missing, throws at access time
589
+ * to catch misconfiguration early instead of returning undefined.
590
+ *
591
+ * Uses metadata + instance getter to work correctly with `useDefineForClassFields`.
592
+ *
593
+ * Type-safety:
594
+ * - Without `kick typegen` run, any string key is accepted (back-compat).
595
+ * - After typegen, `KickEnv` is populated and unknown keys become a tsc
596
+ * error. The optional `defaultValue` is type-checked against the
597
+ * schema's inferred type for that key.
598
+ */
599
+ export declare function Value<K extends string>(envKey: ValueKey<K>, defaultValue?: K extends EnvKey ? KickEnv[K] : unknown): PropertyDecorator;
600
+ /**
601
+ * Add a static `builder()` method for fluent construction.
602
+ *
603
+ * Legacy decorators cannot widen the class type, so by default the
604
+ * caller sees the unmodified shape. Opt into typing by adding a single
605
+ * `declare static` line:
606
+ *
607
+ * ```ts
608
+ * @Builder
609
+ * class UserDto {
610
+ * name!: string
611
+ * email!: string
612
+ * declare static readonly builder: () => BuilderOf<UserDto>
613
+ * }
614
+ * ```
615
+ *
616
+ * `readonly` keeps SonarQube's `typescript:S1444` quiet — the runtime
617
+ * assigns `target.builder` once at decoration time and never again.
618
+ *
619
+ * If you'd rather not write the `declare`, use `withBuilder()` instead —
620
+ * same runtime, types inferred automatically.
621
+ */
622
+ export declare function Builder(target: any): void;
623
+ /**
624
+ * Factory variant of `@Builder`. Wraps the class with the same runtime
625
+ * and returns it intersected with `{ builder(): BuilderOf<T> }` so the
626
+ * static is fully typed without any `declare` boilerplate.
627
+ *
628
+ * ```ts
629
+ * class UserDtoBase {
630
+ * name!: string
631
+ * email!: string
632
+ * }
633
+ * export const UserDto = withBuilder(UserDtoBase)
634
+ * export type UserDto = InstanceType<typeof UserDto>
635
+ *
636
+ * UserDto.builder().name('Alice').email('a@b.com').build() // fully typed
637
+ * ```
638
+ */
639
+ export declare function withBuilder<C extends new (...args: any[]) => any>(ctor: C): C & {
640
+ builder(): BuilderOf<InstanceType<C>>;
641
+ };
642
+ //#endregion
643
+ //#region src/disposables.d.ts
644
+ /**
645
+ * Registry for framework-owned resources that hold timers or other handles
646
+ * outside the adapter/plugin lifecycle — e.g. the in-memory session and
647
+ * rate-limit stores' cleanup intervals. `Application.shutdown()` drains it so
648
+ * startup and shutdown stay symmetric even for resources created lazily by
649
+ * middleware factories.
650
+ *
651
+ * Intervals are `.unref()`'d so they never wedge process exit — this registry
652
+ * exists for IN-PROCESS teardown (test suites, HMR, multi-app hosts) where
653
+ * each un-disposed store would otherwise leak a live interval + backing Map
654
+ * per app instance.
655
+ */
656
+ type Disposable = () => void | Promise<void>;
657
+ /** Register a cleanup callback. Returns an unregister function. */
658
+ export declare function registerDisposable(fn: Disposable): () => void;
659
+ /**
660
+ * Remove and return all currently registered disposables WITHOUT running
661
+ * them. Used by HMR rebuild to snapshot the outgoing app's resources before
662
+ * `setup()` registers the incoming app's — so the stale set can be disposed
663
+ * on success, or restored on failure, without touching the new one.
664
+ */
665
+ export declare function drainDisposables(): Disposable[];
666
+ /**
667
+ * Run a list of disposables. Errors — including SYNCHRONOUS throws — are
668
+ * swallowed per-entry: the `async` callback converts a sync throw into a
669
+ * rejection that `allSettled` absorbs, so one bad disposable can't abort
670
+ * the rest (a bare `Promise.resolve(fn())` would let the throw escape the
671
+ * `.map()` before `allSettled` ever ran).
672
+ */
673
+ export declare function runDisposables(list: Disposable[]): Promise<void>;
674
+ /** Run and clear all registered disposables. Errors are swallowed per-entry. */
675
+ export declare function disposeAll(): Promise<void>;
676
+ //#endregion
677
+ //#region src/metadata.d.ts
678
+ /**
679
+ * Typed utilities for reflect-metadata operations.
680
+ *
681
+ * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single
682
+ * source of truth. If we later move to Stage 3 decorators or a WeakMap-based
683
+ * store, only this file changes — all 24+ consumer files stay untouched.
684
+ *
685
+ * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by
686
+ * `createToken()`. Using `createToken` is the preferred pattern because the
687
+ * token's phantom type flows through the helpers, enabling type-safe reads.
688
+ */
689
+ type MetaKey = symbol | string | InjectionToken<unknown>;
690
+ /** Set metadata on a class (or prototype for property decorators) */
691
+ export declare function setClassMeta<T>(key: MetaKey, value: T, target: object): void;
692
+ /** Set metadata on a specific method */
693
+ export declare function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void;
694
+ /** Get metadata from a class, returning `fallback` if not set */
695
+ export declare function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T;
696
+ /** Get metadata from a specific method, returning `fallback` if not set */
697
+ export declare function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T;
698
+ /** Check if metadata exists on a class */
699
+ export declare function hasClassMeta(key: MetaKey, target: object): boolean;
700
+ /** Get metadata from a class with no default (returns undefined if absent) */
701
+ export declare function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined;
702
+ /** Get metadata from a method with no default (returns undefined if absent) */
703
+ export declare function getMethodMetaOrUndefined<T>(key: MetaKey, target: object, method: string): T | undefined;
704
+ /** Append items to an array stored in class metadata */
705
+ export declare function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void;
706
+ /** Append items to an array stored in method metadata */
707
+ export declare function pushMethodMeta<T>(key: MetaKey, target: object, method: string, ...items: T[]): void;
708
+ /** Set a key/value in a Map stored in class metadata */
709
+ export declare function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void;
710
+ /** Get a Map from class metadata (returns empty Map if absent) */
711
+ export declare function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V>;
712
+ /** Set a key/value in a Record stored in class metadata */
713
+ export declare function setInMetaRecord<V>(key: MetaKey, target: object, recKey: string | number, recValue: V): void;
714
+ /** Get a Record from class metadata (returns empty object if absent) */
715
+ export declare function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V>;
716
+ //#endregion
717
+ export type { Buildable, BuilderOf, ClassKind, Constructor, ContainerChangeEvent, ContainerChangeListener, DiErrorCode, DiLogger, Disposable, Env, EnvKey, InjectionToken, KickJsRegistry, PostConstructStatus, PropertyOrParameterDecorator, ServiceOptions };
718
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/interfaces.ts","../src/errors.ts","../src/token.ts","../src/container.ts","../src/decorators.ts","../src/disposables.ts","../src/metadata.ts"],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs-di v0.1.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import"reflect-metadata";const KEY=Symbol.for(`@forinda/kickjs-di:loaded-from`),store=globalThis,here=import.meta.url;store[KEY]===void 0?store[KEY]=here:store[KEY]!==here&&console.warn(` @forinda/kickjs-di is loaded twice:\n ${store[KEY]}\n ${here}\n Each copy has its own container, so services registered in one are missing from the other.\n Install a single version (it comes with @forinda/kickjs; don't add it separately).`);let Scope=/* @__PURE__ */ function(Scope){return Scope.SINGLETON=`singleton`,Scope.TRANSIENT=`transient`,Scope.REQUEST=`request`,Scope}({});const DI_METADATA={INJECTABLE:`kick:injectable`,SCOPE:`kick:scope`,CLASS_KIND:`kick:class-kind`,AUTOWIRED:`kick:autowired`,INJECT:`kick:inject`,POST_CONSTRUCT:`kick:post_construct`,PRE_DESTROY:`kick:pre_destroy`,BUILDER:`kick:builder`,VALUE:`kick:value`,PARAM_TYPES:`design:paramtypes`,PROPERTY_TYPE:`design:type`,RETURN_TYPE:`design:returntype`};var DiError=class extends Error{code;subject;constructor(code,subject,message){super(message),this.code=code,this.subject=subject,this.name=`DiError`}};const MESSAGES={KICK001:s=>`No provider for ${s}`,KICK002:s=>`Cannot resolve REQUEST-scoped "${s}": no request store is installed`,KICK003:s=>`Cannot resolve REQUEST-scoped "${s}" outside a request`,KICK004:s=>`@Value('${s}'): not set and no default provided`};function diError(code,subject){return new DiError(code,subject,`${code}: ${MESSAGES[code](subject)}`)}const defaultLogger={warn(){},error(err,message){console.error(message,err)}};function setClassMeta(key,value,target){Reflect.defineMetadata(key,value,target)}function setMethodMeta(key,value,target,method){Reflect.defineMetadata(key,value,target,method)}function getClassMeta(key,target,fallback){return Reflect.getMetadata(key,target)??fallback}function getMethodMeta(key,target,method,fallback){return Reflect.getMetadata(key,target,method)??fallback}function hasClassMeta(key,target){return Reflect.hasMetadata(key,target)}function getClassMetaOrUndefined(key,target){return Reflect.getMetadata(key,target)}function getMethodMetaOrUndefined(key,target,method){return Reflect.getMetadata(key,target,method)}function pushClassMeta(key,target,...items){let existing=Reflect.getOwnMetadata(key,target)??[];Reflect.defineMetadata(key,[...existing,...items],target)}function pushMethodMeta(key,target,method,...items){let existing=Reflect.getOwnMetadata(key,target,method)??[];Reflect.defineMetadata(key,[...existing,...items],target,method)}function setInMetaMap(key,target,mapKey,mapValue){let inherited=Reflect.getOwnMetadata(key,target),map=inherited?new Map(inherited):/* @__PURE__ */ new Map;map.set(mapKey,mapValue),Reflect.defineMetadata(key,map,target)}function getMetaMap(key,target){return Reflect.getMetadata(key,target)??/* @__PURE__ */ new Map}function setInMetaRecord(key,target,recKey,recValue){let inherited=Reflect.getOwnMetadata(key,target),rec=inherited?{...inherited}:{};rec[recKey]=recValue,Reflect.defineMetadata(key,rec,target)}function getMetaRecord(key,target){return Reflect.getMetadata(key,target)??{}}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}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:/* @__PURE__ */ new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=/* @__PURE__ */ new Map;resolving=/* @__PURE__ */ new Set;changeListeners=/* @__PURE__ */ new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _onCreate=null;static _envResolver=null;static _error=diError;static _logger=defaultLogger;static _propertyInjectors=/* @__PURE__ */ new Map;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(DI_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 Container._error(`KICK001`,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 Container._error(`KICK002`,tokenName(token));let store=Container._requestStoreProvider();if(!store)throw Container._error(`KICK003`,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,visited=/* @__PURE__ */ new Set){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!visited.has(reg)&&(visited.add(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,visited)}}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(DI_METADATA.PARAM_TYPES,reg.target,[]),injectTokens=getMetaRecord(DI_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(DI_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(DI_METADATA.AUTOWIRED,reg.target.prototype),autowired=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(DI_METADATA.PROPERTY_TYPE,reg.target.prototype,prop);resolvedToken&&autowired.push([prop,resolvedToken])}let values=[...getMetaMap(DI_METADATA.VALUE,reg.target.prototype).entries()],injected=[];for(let[key,prepare]of Container._propertyInjectors)for(let[prop,config]of getMetaMap(key,reg.target.prototype))injected.push([prop,prepare(config,prop)]);return{ctorTokens,autowired,values,injected,postConstruct:getClassMetaOrUndefined(DI_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`}),Container._logger.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`,Container._logger.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(DI_METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(DI_METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(DI_METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(DI_METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[.../* @__PURE__ */ 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 Container._error(`KICK004`,config.envKey)},enumerable:!0,configurable:!0});for(let[prop,get]of plan.injected)Object.defineProperty(instance,prop,{get,enumerable:!0,configurable:!0})}};const pendingRegistrations=[],allRegistrations=/* @__PURE__ */ new Map;let containerRef=null;function flushPending(container){containerRef=container;for(let{target,scope}of pendingRegistrations)container.has(target)||container.register(target,target,scope);pendingRegistrations.length=0}Container._onReady=flushPending;function seed(container){for(let[,{target,scope}]of allRegistrations)container.has(target)||container.register(target,target,scope)}Container._onReset=container=>{containerRef=container,seed(container)},Container._onCreate=seed;const preDestroyWarned=/* @__PURE__ */ new WeakSet;function warnIfInertPreDestroy(target,scope){if(scope===`request`)return;let proto=target?.prototype;if(!proto||!Reflect.getMetadata?.(DI_METADATA.PRE_DESTROY,proto)||preDestroyWarned.has(target))return;preDestroyWarned.add(target);let name=target.name||String(target);console.warn(` kickjs: @PreDestroy on ${name} (${scope}) will never run — that hook fires only when a REQUEST scope closes.\n For application-lifetime resources, release them from an adapter's shutdown() hook instead.`)}function _registerInjectable(target,scope){setClassMeta(DI_METADATA.INJECTABLE,!0,target),setClassMeta(DI_METADATA.SCOPE,scope,target),warnIfInertPreDestroy(target,scope);let name=target.name||String(target);allRegistrations.set(name,{target,scope}),containerRef?containerRef.has(target)||containerRef.register(target,target,scope):pendingRegistrations.push({target,scope})}function Injectable(options){return target=>{setClassMeta(DI_METADATA.CLASS_KIND,`injectable`,target),_registerInjectable(target,options?.scope??`singleton`)}}function Service(options){return target=>{setClassMeta(DI_METADATA.CLASS_KIND,`service`,target),_registerInjectable(target,options?.scope??`singleton`)}}function Component(options){return target=>{setClassMeta(DI_METADATA.CLASS_KIND,`component`,target),_registerInjectable(target,options?.scope??`singleton`)}}function Repository(options){return target=>{setClassMeta(DI_METADATA.CLASS_KIND,`repository`,target),_registerInjectable(target,options?.scope??`singleton`)}}function PostConstruct(){return(target,propertyKey)=>{setClassMeta(DI_METADATA.POST_CONSTRUCT,propertyKey,target)}}function PreDestroy(){return(target,propertyKey)=>{setClassMeta(DI_METADATA.PRE_DESTROY,propertyKey,target)}}function applyInjection(token,target,propertyKey,parameterIndex){typeof parameterIndex==`number`?setInMetaRecord(DI_METADATA.INJECT,target,parameterIndex,token):propertyKey!=null&&setInMetaMap(DI_METADATA.AUTOWIRED,target,propertyKey,token)}function Autowired(token){return((target,propertyKey,parameterIndex)=>{applyInjection(token,target,propertyKey,parameterIndex)})}function Inject(token){return((target,propertyKey,parameterIndex)=>{applyInjection(token,target,propertyKey,parameterIndex)})}function Value(envKey,defaultValue){return(target,propertyKey)=>{setInMetaMap(DI_METADATA.VALUE,target,propertyKey,{envKey,defaultValue})}}function attachBuilder(target){setClassMeta(DI_METADATA.BUILDER,!0,target),target.builder=function(){let props={},proxy=new Proxy({},{get(_,key){return key===`build`?()=>Object.assign(new target,props):value=>(props[key]=value,proxy)}});return proxy}}function Builder(target){attachBuilder(target)}function withBuilder(ctor){return attachBuilder(ctor),ctor}const disposables=/* @__PURE__ */ new Set;function registerDisposable(fn){return disposables.add(fn),()=>disposables.delete(fn)}function drainDisposables(){let pending=[...disposables];return disposables.clear(),pending}async function runDisposables(list){await Promise.allSettled(list.map(async fn=>fn()))}async function disposeAll(){await runDisposables(drainDisposables())}export{Autowired,Builder,Component,Container,DI_METADATA,DiError,INJECTION_TOKEN,Inject,Injectable,PostConstruct,PreDestroy,Repository,Scope,Service,Value,_registerInjectable,createToken,defaultLogger,diError,disposeAll,drainDisposables,getClassMeta,getClassMetaOrUndefined,getMetaMap,getMetaRecord,getMethodMeta,getMethodMetaOrUndefined,hasClassMeta,isInjectionToken,pushClassMeta,pushMethodMeta,registerDisposable,runDisposables,setClassMeta,setInMetaMap,setInMetaRecord,setMethodMeta,tokenName,withBuilder};
12
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["METADATA","METADATA","\"SINGLETON\"","\"TRANSIENT\"","\"REQUEST\""],"sources":["../src/instance-guard.ts","../src/interfaces.ts","../src/errors.ts","../src/metadata.ts","../src/token.ts","../src/container.ts","../src/decorators.ts","../src/disposables.ts"],"sourcesContent":["/**\n * Two installed copies of this package each keep their own container and\n * their own decorator registry, so half the app's registrations would be\n * missing from whichever copy resolves them — with no error. Warn when a\n * second copy loads. The same file evaluated again (HMR, a test re-import)\n * is one copy, not two.\n */\nconst KEY = Symbol.for('@forinda/kickjs-di:loaded-from')\nconst store = globalThis as { [KEY]?: string }\nconst here = import.meta.url\n\nif (store[KEY] === undefined) {\n store[KEY] = here\n} else if (store[KEY] !== here) {\n console.warn(\n ` @forinda/kickjs-di is loaded twice:\\n ${store[KEY]}\\n ${here}\\n` +\n ` Each copy has its own container, so services registered in one are missing from the other.\\n` +\n ` Install a single version (it comes with @forinda/kickjs; don't add it separately).`,\n )\n}\n","import 'reflect-metadata'\n\n/** Lifecycle scope for DI registrations */\nexport enum Scope {\n SINGLETON = 'singleton',\n TRANSIENT = 'transient',\n /** One instance per HTTP request, cached in AsyncLocalStorage. Throws outside request context. */\n REQUEST = 'request',\n}\n\n/** Generic constructor type */\nexport type Constructor<T = any> = new (...args: any[]) => T\n\n/** Options for @Service, @Repository, @Component decorators */\nexport interface ServiceOptions {\n scope?: Scope\n}\n\n/** Fluent builder type */\nexport type BuilderOf<T> = {\n [K in keyof T as T[K] extends Function ? never : K]-?: (value: T[K]) => BuilderOf<T>\n} & { build(): T }\n\n/** Class with static builder() method */\nexport interface Buildable<T> {\n builder(): BuilderOf<T>\n}\n\n/** Decorator class kind for devtools introspection */\nexport type ClassKind =\n | 'service'\n | 'controller'\n | 'repository'\n | 'component'\n | 'injectable'\n | 'factory'\n | 'instance'\n | 'unknown'\n\n/** PostConstruct lifecycle status */\nexport type PostConstructStatus = 'pending' | 'completed' | 'failed' | 'skipped'\n\n/**\n * String metadata keys for the DI system + decorator subsystem. Every\n * key starts with the `kick:` prefix; the suffix is a short identifier\n * that may itself contain `:`, `-`, or `_` separators (historical —\n * keys grew alongside the framework). The §22 v4 convention is \"no\n * Symbols\", not \"use a strict naming scheme\" — Symbols don't survive\n * JSON serialisation, can't be addressed by string from worker\n * threads, and don't show up in DevTools snapshots.\n *\n * `reflect-metadata` accepts both Symbol and string keys identically,\n * so the consumer-side change is internal — references go through the\n * `METADATA` enum's named members (e.g. `METADATA.INJECT`), not the\n * underlying string.\n *\n * The `design:*` entries match what TypeScript's\n * `emitDecoratorMetadata` writes; those names are fixed by the\n * compiler + must stay as-is.\n */\nexport const DI_METADATA = {\n INJECTABLE: 'kick:injectable',\n SCOPE: 'kick:scope',\n CLASS_KIND: 'kick:class-kind',\n AUTOWIRED: 'kick:autowired',\n INJECT: 'kick:inject',\n POST_CONSTRUCT: 'kick:post_construct',\n PRE_DESTROY: 'kick:pre_destroy',\n BUILDER: 'kick:builder',\n VALUE: 'kick:value',\n // TypeScript emit metadata keys — fixed by the compiler.\n PARAM_TYPES: 'design:paramtypes',\n PROPERTY_TYPE: 'design:type',\n RETURN_TYPE: 'design:returntype',\n} as const\n","/**\n * The container's own errors, by stable code. A host can replace the factory\n * (`Container._error`) to turn them into richer errors — kickjs maps each\n * code to its catalogue entry, with the cause and the fix.\n */\nexport type DiErrorCode =\n /** Nothing is registered for the token. */\n | 'KICK001'\n /** A REQUEST-scoped token, but no request store is installed. */\n | 'KICK002'\n /** A REQUEST-scoped token, resolved outside a request. */\n | 'KICK003'\n /** `@Value` key not set, and no default. */\n | 'KICK004'\n\nexport class DiError extends Error {\n constructor(\n readonly code: DiErrorCode,\n /** The token name, or the env key for KICK004. */\n readonly subject: string,\n message: string,\n ) {\n super(message)\n this.name = 'DiError'\n }\n}\n\nconst MESSAGES: Record<DiErrorCode, (subject: string) => string> = {\n KICK001: (s) => `No provider for ${s}`,\n KICK002: (s) => `Cannot resolve REQUEST-scoped \"${s}\": no request store is installed`,\n KICK003: (s) => `Cannot resolve REQUEST-scoped \"${s}\" outside a request`,\n KICK004: (s) => `@Value('${s}'): not set and no default provided`,\n}\n\nexport function diError(code: DiErrorCode, subject: string): Error {\n return new DiError(code, subject, `${code}: ${MESSAGES[code](subject)}`)\n}\n\n/** Where the container reports non-fatal problems. A host can install its own. */\nexport interface DiLogger {\n warn(message: string): void\n error(err: unknown, message: string): void\n}\n\n/** Drops warnings; errors (a `@PostConstruct` that threw) go to `console.error`. */\nexport const defaultLogger: DiLogger = {\n warn() {},\n error(err, message) {\n console.error(message, err)\n },\n}\n","import 'reflect-metadata'\nimport type { InjectionToken } from './token'\n\n/**\n * Typed utilities for reflect-metadata operations.\n *\n * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single\n * source of truth. If we later move to Stage 3 decorators or a WeakMap-based\n * store, only this file changes — all 24+ consumer files stay untouched.\n *\n * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by\n * `createToken()`. Using `createToken` is the preferred pattern because the\n * token's phantom type flows through the helpers, enabling type-safe reads.\n */\n\ntype MetaKey = symbol | string | InjectionToken<unknown>\n\n// ── Setters ─────────────────────────────────────────────────────\n\n/** Set metadata on a class (or prototype for property decorators) */\nexport function setClassMeta<T>(key: MetaKey, value: T, target: object): void {\n Reflect.defineMetadata(key, value, target)\n}\n\n/** Set metadata on a specific method */\nexport function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void {\n Reflect.defineMetadata(key, value, target, method)\n}\n\n// ── Getters ─────────────────────────────────────────────────────\n\n/** Get metadata from a class, returning `fallback` if not set */\nexport function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T {\n return (Reflect.getMetadata(key, target) as T) ?? fallback\n}\n\n/** Get metadata from a specific method, returning `fallback` if not set */\nexport function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T {\n return (Reflect.getMetadata(key, target, method) as T) ?? fallback\n}\n\n/** Check if metadata exists on a class */\nexport function hasClassMeta(key: MetaKey, target: object): boolean {\n return Reflect.hasMetadata(key, target)\n}\n\n/** Get metadata from a class with no default (returns undefined if absent) */\nexport function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined {\n return Reflect.getMetadata(key, target) as T | undefined\n}\n\n/** Get metadata from a method with no default (returns undefined if absent) */\nexport function getMethodMetaOrUndefined<T>(\n key: MetaKey,\n target: object,\n method: string,\n): T | undefined {\n return Reflect.getMetadata(key, target, method) as T | undefined\n}\n\n// ── Accumulate: Arrays ──────────────────────────────────────────\n\n/** Append items to an array stored in class metadata */\nexport function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target)\n}\n\n/** Append items to an array stored in method metadata */\nexport function pushMethodMeta<T>(\n key: MetaKey,\n target: object,\n method: string,\n ...items: T[]\n): void {\n const existing: T[] = Reflect.getOwnMetadata(key, target, method) ?? []\n Reflect.defineMetadata(key, [...existing, ...items], target, method)\n}\n\n// ── Accumulate: Maps ────────────────────────────────────────────\n\n/** Set a key/value in a Map stored in class metadata */\nexport function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void {\n const inherited: Map<K, V> | undefined = Reflect.getOwnMetadata(key, target)\n const map = inherited ? new Map(inherited) : new Map<K, V>()\n map.set(mapKey, mapValue)\n Reflect.defineMetadata(key, map, target)\n}\n\n/** Get a Map from class metadata (returns empty Map if absent) */\nexport function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V> {\n return Reflect.getMetadata(key, target) ?? new Map()\n}\n\n// ── Accumulate: Records ─────────────────────────────────────────\n\n/** Set a key/value in a Record stored in class metadata */\nexport function setInMetaRecord<V>(\n key: MetaKey,\n target: object,\n recKey: string | number,\n recValue: V,\n): void {\n const inherited: Record<string | number, V> | undefined = Reflect.getOwnMetadata(key, target)\n const rec = inherited ? { ...inherited } : {}\n rec[recKey] = recValue\n Reflect.defineMetadata(key, rec, target)\n}\n\n/** Get a Record from class metadata (returns empty object if absent) */\nexport function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V> {\n return Reflect.getMetadata(key, target) ?? {}\n}\n","/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n DI_METADATA as METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\nimport { diError, defaultLogger, type DiErrorCode, type DiLogger } from './errors'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n /** Lazily built instantiation plan — see {@link Container.buildPlan}. */\n plan?: InstantiationPlan\n}\n\n/**\n * Precomputed instantiation recipe for a class. Class metadata is immutable\n * after definition, so the Reflect reads (5 per create) and the derived\n * structures only need computing ONCE — previously they re-ran on every\n * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.\n */\ninterface InstantiationPlan {\n /** Ordered, validated constructor parameter tokens. */\n ctorTokens: any[]\n /** `@Autowired` properties: [property, resolved token]. */\n autowired: Array<[string, any]>\n /** `@Value` properties: [property, env config]. */\n values: Array<[string, { envKey: string; defaultValue?: any }]>\n /** Host-injected properties (`_propertyInjectors`): [property, getter]. */\n injected: Array<[string, () => unknown]>\n /** `@PostConstruct` method name, if any. */\n postConstruct?: string | symbol\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n\n /**\n * Seeds a NEWLY CREATED container with the decorator registrations, without\n * adopting it as the global one. `_onReset` does both; an isolated container\n * must only have the first half, or it would capture every subsequent\n * decorator registration in the process.\n */\n static _onCreate: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Builds the container's errors from a stable code and the token name (or\n * env key). The default gives a plain `DiError`; kickjs installs one that\n * returns its catalogued `KickError`.\n */\n static _error: (code: DiErrorCode, subject: string) => Error = diError\n /** Where non-fatal problems go (an async `@PostConstruct`, one that threw). Errors reach `console.error` by default. */\n static _logger: DiLogger = defaultLogger\n /**\n * Extra lazy property injections, keyed by metadata key. For each property\n * a class stores under that key (a `Map` on the prototype, as `@Value`\n * does), `prepare(config, prop)` runs once when the class's plan is built\n * — so it can reject a bad config up front — and returns the getter each\n * instance's property reads through. kickjs adds `@Asset` this way.\n */\n static _propertyInjectors = new Map<string, (config: any, prop: string) => () => unknown>()\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n /**\n * Whether this container is the global singleton.\n *\n * The persistent store exists to carry the GLOBAL container's registrations\n * across HMR and module re-evaluation. An isolated container is per-test and\n * must neither write to it — a `registerInstance` override was replayed into\n * every later `Container.reset()` — nor read from it, which would pull global\n * state into a container created to be free of it.\n */\n private isGlobal = false\n\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n const container = new Container()\n // Decorators register at class-definition time, into whichever container\n // was global then. Without this replay an isolated container is missing\n // every @Service / @Repository / @Controller in the process — and\n // `isolated: true` is what the generated docs recommend for test\n // isolation, so the failure reached adopters as `No provider for X`.\n Container._onCreate?.(container)\n return container\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(token, 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n // Reads are global-only too. An isolated container adopting a cached\n // instance from the global store would be contaminated on creation — the\n // same leak, in the other direction.\n const existingEntry = this.isGlobal\n ? store.factories.find((r) => tokenName(r.token) === name)\n : undefined\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance =\n this.isGlobal && factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (this.isGlobal && !factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only, or an isolated container's factories are replayed\n // into the next Container.reset().\n if (this.isGlobal) {\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only. An isolated one that wrote here had its overrides\n // replayed into the next Container.reset().\n if (this.isGlobal) {\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw Container._error('KICK001', tokenName(token))\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw Container._error('KICK002', tokenName(token))\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw Container._error('KICK003', tokenName(token))\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module\n // re-evaluation — global container only, for the same reason.\n if (this.isGlobal) {\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any, visited = new Set<Registration>()): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n // `visited`: two classes that @Autowired each other depend on each other,\n // and the walk below went round that cycle until the stack overflowed.\n if (!reg || visited.has(reg)) return\n visited.add(reg)\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken, visited)\n }\n }\n }\n\n /**\n * Batch-emit a change event (debounced, flushed after 50ms of quiet).\n * Takes the RAW token — `tokenName()` is only computed after the\n * zero-listener guard, so the hot cached-singleton resolve path never pays\n * the name derivation when nobody is watching (the production default).\n */\n private emit(rawToken: any, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Zero-listener fast path. Nobody subscribes to container changes unless\n // DevTools (or a test) calls `onChange` — the production default. Without\n // this guard, every `resolve()` (including the hot cached-singleton path)\n // would scan `pendingChanges`, push an entry, and reschedule a `setTimeout`\n // only for the eventual flush to iterate an empty listener set and discard\n // the batch. Short-circuiting here keeps `resolve()` allocation- and\n // timer-free when no one is watching. When a listener attaches later,\n // subsequent emits work exactly as before — past resolves had no observer\n // to notify anyway.\n if (this.changeListeners.size === 0) return\n const token = tokenName(rawToken)\n\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n // Don't let a pending debounce window hold the event loop open — flush is\n // best-effort telemetry, not critical work.\n this.notifyTimer.unref?.()\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n /**\n * Build the one-time instantiation plan for a registration. Runs on the\n * FIRST createInstance for the class — the metadata it reads is immutable\n * after class definition, so every later create (REQUEST scope: every\n * request) skips all Reflect reads and re-derivation.\n *\n * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here\n * too: it's a static property of the (parent, dep) registration pair, so\n * checking it once at plan build is equivalent to the old per-create loop.\n */\n private buildPlan(reg: Registration): InstantiationPlan {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n\n // When emitDecoratorMetadata is unavailable (tsx, ts-node without\n // SWC), paramTypes is empty even though @Inject set tokens on\n // specific constructor indices. Derive the constructor arity from\n // the @Inject metadata so constructor injection still works.\n const maxInjectIndex =\n Object.keys(injectTokens).length > 0\n ? Math.max(...Object.keys(injectTokens).map(Number)) + 1\n : 0\n const paramCount = Math.max(paramTypes.length, maxInjectIndex)\n\n const ctorTokens: any[] = []\n for (let index = 0; index < paramCount; index++) {\n const token = injectTokens[index] || paramTypes[index]\n if (!token) {\n throw new Error(\n `Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. ` +\n `No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). ` +\n `Add @Inject(Token) to every constructor parameter.`,\n )\n }\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n // The fix depends on the parent: @Controller() takes no options and\n // is always SINGLETON, so \"change the parent's scope\" is advice a\n // controller cannot follow. @Autowired works for every parent — its\n // getter re-resolves REQUEST deps per access (injectProperties).\n const parent = tokenName(reg.target)\n const dep = tokenName(token)\n const kind = getClassMetaOrUndefined<ClassKind>(METADATA.CLASS_KIND, reg.target)\n const fix =\n kind === 'controller'\n ? `@Controller() is always SINGLETON, so inject \"${dep}\" with @Autowired() instead of the constructor.`\n : `Give \"${parent}\" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), ` +\n `or inject \"${dep}\" with @Autowired() instead of the constructor.`\n throw new Error(\n `Cannot inject REQUEST-scoped \"${dep}\" into SINGLETON \"${parent}\". ` +\n `Singletons outlive requests. ${fix}`,\n )\n }\n }\n ctorTokens.push(token)\n }\n\n // @Autowired properties — resolve each property's token once.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, reg.target.prototype)\n const autowired: InstantiationPlan['autowired'] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, reg.target.prototype, prop)\n if (resolvedToken) autowired.push([prop, resolvedToken])\n }\n\n // @Value properties.\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n reg.target.prototype,\n )\n const values: InstantiationPlan['values'] = [...valueProps.entries()]\n\n // Properties added by host injectors (`_propertyInjectors`), e.g. `@Asset`.\n const injected: InstantiationPlan['injected'] = []\n for (const [key, prepare] of Container._propertyInjectors) {\n for (const [prop, config] of getMetaMap<string, unknown>(key, reg.target.prototype)) {\n injected.push([prop, prepare(config, prop)])\n }\n }\n\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n\n return { ctorTokens, autowired, values, injected, postConstruct }\n }\n\n private createInstance(reg: Registration): any {\n const plan = (reg.plan ??= this.buildPlan(reg))\n\n const args: any[] = []\n for (const token of plan.ctorTokens) {\n args.push(this.resolve(token))\n }\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired / @Value / @Asset\n this.injectProperties(instance, plan)\n\n // @PostConstruct lifecycle hook\n const postConstruct = plan.postConstruct\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n Container._logger.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n Container._logger.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, plan: InstantiationPlan): void {\n // @Autowired — lazy DI property injection. The getter re-resolves per\n // access by design (a SINGLETON may lazily read a REQUEST-scoped dep at\n // call time). For SINGLETON deps that laziness buys nothing after the\n // first read — the container returns the same cached instance forever —\n // so the getter memoizes itself into a plain data property then. REQUEST\n // and TRANSIENT deps keep the live getter (memoizing would pin the first\n // request's instance across requests).\n for (const [prop, resolvedToken] of plan.autowired) {\n Object.defineProperty(instance, prop, {\n get: () => {\n const value = this.resolve(resolvedToken)\n const depReg = this.registrations.get(resolvedToken)\n if (depReg?.scope === Scope.SINGLETON) {\n Object.defineProperty(instance, prop, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return value\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Value — lazy environment variable injection\n for (const [prop, config] of plan.values) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw Container._error('KICK004', config.envKey)\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // Host injectors — getters prepared at plan build.\n for (const [prop, get] of plan.injected) {\n Object.defineProperty(instance, prop, { get, enumerable: true, configurable: true })\n }\n }\n}\n","import 'reflect-metadata'\nimport { DI_METADATA as METADATA, Scope, type BuilderOf, type ServiceOptions } from './interfaces'\nimport { Container, type KickJsRegistry } from './container'\nimport { setClassMeta, setInMetaMap, setInMetaRecord } from './metadata'\n\n// ── Decorator Registration System ───────────────────────────────────────\n// Decorators execute at class-definition time (module load). The Container\n// may not exist yet when the first decorator fires. We queue registrations\n// and flush them on first Container.getInstance() call.\n//\n// `allRegistrations` is a persistent registry that survives Container.reset().\n// On reset (HMR), all previously decorated classes are re-registered on the\n// fresh container. This ensures @Service, @Controller, @Repository, etc.\n// all survive HMR without manual re-registration.\n\ntype PendingRegistration = { target: any; scope: Scope }\nconst pendingRegistrations: PendingRegistration[] = []\n// Keyed by class name to prevent memory leaks during HMR — new class with\n// the same name replaces the old entry instead of accumulating references.\nconst allRegistrations = new Map<string, { target: any; scope: Scope }>()\nlet containerRef: any = null\n\nfunction flushPending(container: any): void {\n containerRef = container\n for (const { target, scope } of pendingRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n pendingRegistrations.length = 0\n}\n\n// Wire up synchronously — Container._onReady is called on first getInstance()\nContainer._onReady = flushPending\n\n/** Replay every decorator registration onto a container. */\nfunction seed(container: any): void {\n for (const [, { target, scope }] of allRegistrations) {\n if (!container.has(target)) {\n container.register(target, target, scope)\n }\n }\n}\n\n// On Container.reset(), update containerRef and replay ALL decorator\n// registrations on the fresh container. This handles HMR where the container\n// is wiped but not all decorated modules are re-evaluated.\nContainer._onReset = (container: any) => {\n containerRef = container\n seed(container)\n}\n\n// On Container.create(), seed the same way but leave `containerRef` alone: an\n// isolated container should receive what has been registered so far, not\n// become the destination for everything registered next.\nContainer._onCreate = seed\n\n// ── Class Decorators ────────────────────────────────────────────────────\n\n/**\n * Classes already warned about.\n *\n * Keyed by the constructor, not its `name`: distinct classes can share a name —\n * two `DatabaseService` classes in separate modules is ordinary — and deduping\n * by name silently suppressed the second one's warning, which is precisely the\n * failure this warning exists to prevent.\n *\n * A WeakSet means an HMR reload, which builds a NEW class object, warns again.\n * That is correct rather than noisy: the reloaded code still has the problem,\n * and the notice stops as soon as it is fixed.\n */\nconst preDestroyWarned = new WeakSet<object>()\n\n/**\n * `@PreDestroy` only runs for REQUEST-scoped services — the request scope is\n * what closes and triggers it. On a SINGLETON it is silently inert, which\n * reads as a working teardown and is not: the reported case was a Postgres\n * pool never closed on shutdown, invisible in development and surfacing as\n * connection exhaustion under repeated restarts and HMR reloads.\n *\n * The asymmetry is the trap. `@PostConstruct` DOES run for singletons, so the\n * pair looks like init/teardown while one half quietly opts out on a scope the\n * author may never have considered.\n *\n * Warn rather than run it: making it fire on shutdown is a behaviour change\n * whose ordering, timeout and concurrency semantics have to match the adapter\n * shutdown path, and an adapter's `shutdown()` is already the seam for\n * application-lifetime resources.\n */\nfunction warnIfInertPreDestroy(target: any, scope: Scope): void {\n if (scope === Scope.REQUEST) return\n const proto = target?.prototype\n if (!proto) return\n const hook = Reflect.getMetadata?.(METADATA.PRE_DESTROY, proto)\n if (!hook) return\n if (preDestroyWarned.has(target)) return\n preDestroyWarned.add(target)\n const name = target.name || String(target)\n console.warn(\n ` kickjs: @PreDestroy on ${name} (${scope}) will never run — that hook fires ` +\n `only when a REQUEST scope closes.\\n` +\n ` For application-lifetime resources, release them from an adapter's ` +\n `shutdown() hook instead.`,\n )\n}\n\n/**\n * Register a decorated class with the container. @internal — exported for\n * class decorators built outside this module (`@Controller`).\n */\nexport function _registerInjectable(target: any, scope: Scope): void {\n setClassMeta(METADATA.INJECTABLE, true, target)\n setClassMeta(METADATA.SCOPE, scope, target)\n // Method decorators run before the class decorator, so PRE_DESTROY metadata\n // is already present by the time we get here.\n warnIfInertPreDestroy(target, scope)\n\n // Track in persistent registry — survives Container.reset() for HMR replay.\n // Keyed by name so HMR class re-creation replaces the old entry.\n const name = target.name || String(target)\n allRegistrations.set(name, { target, scope })\n\n if (containerRef) {\n // Container already initialized — register immediately\n if (!containerRef.has(target)) {\n containerRef.register(target, target, scope)\n }\n } else {\n // Container not ready yet — queue for later\n pendingRegistrations.push({ target, scope })\n }\n}\n\n/** Mark a class as injectable with lifecycle scope */\nexport function Injectable(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'injectable', target)\n _registerInjectable(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a service (semantic alias for Injectable) */\nexport function Service(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'service', target)\n _registerInjectable(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a generic managed component */\nexport function Component(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'component', target)\n _registerInjectable(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n/** Mark a class as a repository */\nexport function Repository(options?: ServiceOptions): ClassDecorator {\n return (target: any) => {\n setClassMeta(METADATA.CLASS_KIND, 'repository', target)\n _registerInjectable(target, options?.scope ?? Scope.SINGLETON)\n }\n}\n\n// ── Method Decorators ───────────────────────────────────────────────────\n\n/** Mark a method as a lifecycle hook called after instantiation */\nexport function PostConstruct(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.POST_CONSTRUCT, propertyKey, target)\n }\n}\n\n/**\n * Mark a method as a teardown hook — the counterpart to {@link PostConstruct}.\n *\n * For REQUEST-scoped services it runs when the request's scope closes\n * (response finished or aborted): release per-request resources there\n * (transactions, handles, subscriptions). May be async; errors are logged\n * and swallowed so one failing hook can't break request completion.\n *\n * It does NOT run on a SINGLETON — the default scope — because nothing closes\n * a singleton's scope. Unlike {@link PostConstruct}, which does run for\n * singletons, so the pair is not symmetric. Applying it to a singleton logs a\n * warning naming the class rather than failing silently.\n *\n * For application-lifetime resources (a connection pool, a timer, a socket\n * server) use an adapter's `shutdown()` hook, which the framework runs on a\n * real shutdown AND on every HMR reload, time-boxed and concurrent.\n */\nexport function PreDestroy(): MethodDecorator {\n return (target, propertyKey) => {\n setClassMeta(METADATA.PRE_DESTROY, propertyKey, target)\n }\n}\n\n// ── Injection Decorators (property + constructor parameter) ─────────────\n\n/**\n * A decorator that's valid as either a property decorator OR a\n * constructor-parameter decorator. forinda/kick-js#235 §2 — `@Autowired`\n * and `@Inject` previously diverged at the type level (property-only\n * vs parameter-only) and adopters who picked the wrong name for the\n * position got a cryptic TS1240 error. Both names now accept either\n * position and route to the correct metadata bucket at runtime.\n */\nexport interface PropertyOrParameterDecorator {\n /** Property decorator form — `@Autowired(TOKEN) private foo!: Foo`. */\n (target: object, propertyKey: string | symbol): void\n /** Constructor-parameter form — `constructor(@Inject(TOKEN) foo: Foo) {}`. */\n (target: object, propertyKey: string | symbol | undefined, parameterIndex: number): void\n}\n\nfunction applyInjection(\n token: unknown,\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex: number | undefined,\n): void {\n // Legacy parameter decorators receive 3 args (target, propertyKey,\n // parameterIndex); property decorators receive 2 (target, propertyKey).\n // The 3rd-arg-number-vs-undefined check is the canonical position\n // detection in TypeScript's legacy decorator runtime.\n if (typeof parameterIndex === 'number') {\n setInMetaRecord(METADATA.INJECT, target, parameterIndex, token)\n return\n }\n if (propertyKey != null) {\n // `target` for a property decorator on a non-static field is the\n // class prototype; the AUTOWIRED bucket is keyed off the prototype\n // identity at resolution time, so pass it through unchanged.\n setInMetaMap(METADATA.AUTOWIRED, target, propertyKey as string, token)\n }\n}\n\n/**\n * Inject a dependency. Works in two positions:\n *\n * - **Property decorator** — `@Autowired(TOKEN) private repo!: UserRepo`.\n * Resolved lazily from the container the first time the property is\n * read.\n * - **Constructor parameter decorator** — `constructor(@Autowired(TOKEN) repo: UserRepo) {}`.\n * Resolved at instantiation; injected into the ctor call.\n *\n * `@Inject` is the same function under another name — they share\n * runtime + types so the two names are interchangeable. Pick whichever\n * reads better at the call site.\n *\n * Like `@Inject`, the typed string-literal overload narrows the token\n * to a key of the augmented `KickJsRegistry`. After `kick typegen`\n * populates the registry, `@Autowired('kick/prisma/Client')` auto-\n * completes the key and typo'd literals become TS2345 errors.\n */\nexport function Autowired<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator\nexport function Autowired(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Inject a dependency by token. Same shape as {@link Autowired} —\n * works as either a property decorator or a constructor-parameter\n * decorator. Pick whichever name reads better at the call site.\n *\n * ## Typed string-literal overload\n *\n * When called with a string literal that matches a key of the\n * augmented `KickJsRegistry`, TypeScript narrows the parameter type\n * to the registered type. Typo'd literals become compile errors\n * (the string isn't `keyof KickJsRegistry`). Class identities and\n * `InjectionToken<T>` keep working unchanged via the second overload.\n *\n * @example\n * ```ts\n * import { Service, Inject } from '@forinda/kickjs'\n * import type { PrismaClient } from '@prisma/client'\n *\n * `@Service()`\n * class UserRepo {\n * constructor(@Inject('kick/prisma/Client') private db: PrismaClient) {}\n * // OR property form, same decorator:\n * @Inject('kick/prisma/Client') private db2!: PrismaClient\n * }\n * ```\n *\n * After `kick typegen` runs, the literal autocompletes from the\n * registry and a typo (`@Inject('kick/prisma/Cleint')`) becomes a\n * TS2345 error.\n */\nexport function Inject<K extends keyof KickJsRegistry & string>(\n token: K,\n): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator\nexport function Inject(token?: unknown): PropertyOrParameterDecorator {\n return ((\n target: object,\n propertyKey: string | symbol | undefined,\n parameterIndex?: number,\n ): void => {\n applyInjection(token, target, propertyKey, parameterIndex)\n }) as PropertyOrParameterDecorator\n}\n\n/**\n * Global ambient registry of typed environment variables, populated by\n * `kick typegen` from the project's env schema (typically\n * `src/env.ts`'s `export default defineEnv(...)`).\n *\n * Empty by default — declarations come from `.kickjs/types/env.ts`\n * generated alongside the rest of the typegen output. Once populated,\n * `@Value('PORT')` autocompletes the key, type-checks the default\n * value, and `process.env.PORT` is also typed via the parallel\n * `NodeJS.ProcessEnv` augmentation in the same generated file.\n */\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface KickEnv {}\n}\n\n/** A string key known to the typed env registry (`never` until typegen runs) */\nexport type EnvKey = keyof KickEnv & string\n\n/**\n * Look up the type of a typed env entry.\n *\n * Decorators can't propagate the property type from the key, so users\n * still need to write the property's type — but `Env<K>` lets them\n * derive it from `KickEnv` instead of duplicating the schema:\n *\n * ```ts\n * @Service()\n * class MyService {\n * @Value('PORT')\n * private port!: Env<'PORT'> // → number, narrowed from KickEnv['PORT']\n * }\n * ```\n */\nexport type Env<K extends EnvKey = EnvKey> = KickEnv[K]\n\n/**\n * Constraint that allows any string when the typed env registry is\n * empty (no `kick typegen` has run yet, so existing code with raw\n * string keys keeps working) but locks the argument down to known\n * `EnvKey` literals once the registry is populated.\n *\n * Implementation: when `EnvKey` is `never`, the conditional resolves\n * to the user's `K`, accepting anything. Once typegen has populated\n * `KickEnv`, `EnvKey` becomes a real union and the conditional forces\n * `K` to be assignable to it.\n */\ntype ValueKey<K extends string> = [EnvKey] extends [never] ? K : K & EnvKey\n\n/**\n * Inject an environment variable value. Evaluated lazily so the env\n * is available at access time, not at decoration time.\n *\n * If no default is provided and the env var is missing, throws at access time\n * to catch misconfiguration early instead of returning undefined.\n *\n * Uses metadata + instance getter to work correctly with `useDefineForClassFields`.\n *\n * Type-safety:\n * - Without `kick typegen` run, any string key is accepted (back-compat).\n * - After typegen, `KickEnv` is populated and unknown keys become a tsc\n * error. The optional `defaultValue` is type-checked against the\n * schema's inferred type for that key.\n */\nexport function Value<K extends string>(\n envKey: ValueKey<K>,\n defaultValue?: K extends EnvKey ? KickEnv[K] : unknown,\n): PropertyDecorator {\n return (target, propertyKey) => {\n setInMetaMap(METADATA.VALUE, target, propertyKey as string, { envKey, defaultValue })\n }\n}\n// ── Builder Decorator ───────────────────────────────────────────────────\n\nfunction attachBuilder(target: any): void {\n setClassMeta(METADATA.BUILDER, true, target)\n target.builder = function () {\n const props: Record<string, any> = {}\n const proxy: any = new Proxy(\n {},\n {\n get(_, key) {\n if (key === 'build') {\n return () => Object.assign(new target(), props)\n }\n return (value: any) => {\n props[key as string] = value\n return proxy\n }\n },\n },\n )\n return proxy\n }\n}\n\n/**\n * Add a static `builder()` method for fluent construction.\n *\n * Legacy decorators cannot widen the class type, so by default the\n * caller sees the unmodified shape. Opt into typing by adding a single\n * `declare static` line:\n *\n * ```ts\n * @Builder\n * class UserDto {\n * name!: string\n * email!: string\n * declare static readonly builder: () => BuilderOf<UserDto>\n * }\n * ```\n *\n * `readonly` keeps SonarQube's `typescript:S1444` quiet — the runtime\n * assigns `target.builder` once at decoration time and never again.\n *\n * If you'd rather not write the `declare`, use `withBuilder()` instead —\n * same runtime, types inferred automatically.\n */\nexport function Builder(target: any): void {\n attachBuilder(target)\n}\n\n/**\n * Factory variant of `@Builder`. Wraps the class with the same runtime\n * and returns it intersected with `{ builder(): BuilderOf<T> }` so the\n * static is fully typed without any `declare` boilerplate.\n *\n * ```ts\n * class UserDtoBase {\n * name!: string\n * email!: string\n * }\n * export const UserDto = withBuilder(UserDtoBase)\n * export type UserDto = InstanceType<typeof UserDto>\n *\n * UserDto.builder().name('Alice').email('a@b.com').build() // fully typed\n * ```\n */\nexport function withBuilder<C extends new (...args: any[]) => any>(\n ctor: C,\n): C & { builder(): BuilderOf<InstanceType<C>> } {\n attachBuilder(ctor)\n return ctor as C & { builder(): BuilderOf<InstanceType<C>> }\n}\n","/**\n * Registry for framework-owned resources that hold timers or other handles\n * outside the adapter/plugin lifecycle — e.g. the in-memory session and\n * rate-limit stores' cleanup intervals. `Application.shutdown()` drains it so\n * startup and shutdown stay symmetric even for resources created lazily by\n * middleware factories.\n *\n * Intervals are `.unref()`'d so they never wedge process exit — this registry\n * exists for IN-PROCESS teardown (test suites, HMR, multi-app hosts) where\n * each un-disposed store would otherwise leak a live interval + backing Map\n * per app instance.\n */\n\nexport type Disposable = () => void | Promise<void>\n\nconst disposables = new Set<Disposable>()\n\n/** Register a cleanup callback. Returns an unregister function. */\nexport function registerDisposable(fn: Disposable): () => void {\n disposables.add(fn)\n return () => disposables.delete(fn)\n}\n\n/**\n * Remove and return all currently registered disposables WITHOUT running\n * them. Used by HMR rebuild to snapshot the outgoing app's resources before\n * `setup()` registers the incoming app's — so the stale set can be disposed\n * on success, or restored on failure, without touching the new one.\n */\nexport function drainDisposables(): Disposable[] {\n const pending = [...disposables]\n disposables.clear()\n return pending\n}\n\n/**\n * Run a list of disposables. Errors — including SYNCHRONOUS throws — are\n * swallowed per-entry: the `async` callback converts a sync throw into a\n * rejection that `allSettled` absorbs, so one bad disposable can't abort\n * the rest (a bare `Promise.resolve(fn())` would let the throw escape the\n * `.map()` before `allSettled` ever ran).\n */\nexport async function runDisposables(list: Disposable[]): Promise<void> {\n await Promise.allSettled(list.map(async (fn) => fn()))\n}\n\n/** Run and clear all registered disposables. Errors are swallowed per-entry. */\nexport async function disposeAll(): Promise<void> {\n await runDisposables(drainDisposables())\n}\n"],"mappings":";;;;;;;;;;yBAOA,MAAM,IAAM,OAAO,IAAI,gCAAgC,EACjD,MAAQ,WACR,KAAO,YAAY,IAErB,MAAM,OAAS,IAAA,GACjB,MAAM,KAAO,KACJ,MAAM,OAAS,MACxB,QAAQ,KACN,8CAA8C,MAAM,KAAK,QAAQ,KAAK,qLAGxE,ECfF,IAAY,MAAL,yBAAA,MAAA,OACL,OAAAE,UAAA,YACA,MAAAC,UAAA,YAEA,MAAAC,QAAA,eACF,EAAA,CAAA,CAAA,EAoDA,MAAa,YAAc,CACzB,WAAY,kBACZ,MAAO,aACP,WAAY,kBACZ,UAAW,iBACX,OAAQ,cACR,eAAgB,sBAChB,YAAa,mBACb,QAAS,eACT,MAAO,aAEP,YAAa,oBACb,cAAe,cACf,YAAa,mBACf,EC3DA,IAAa,QAAb,cAA6B,KAAM,CAEtB,KAEA,QAHX,YACE,KAEA,QACA,QACA,CACA,MAAM,OAAO,EALJ,KAAA,KAAA,KAEA,KAAA,QAAA,QAIT,KAAK,KAAO,SACd,CACF,EAEA,MAAM,SAA6D,CACjE,QAAU,GAAM,mBAAmB,IACnC,QAAU,GAAM,kCAAkC,EAAE,kCACpD,QAAU,GAAM,kCAAkC,EAAE,qBACpD,QAAU,GAAM,WAAW,EAAE,oCAC/B,EAEA,SAAgB,QAAQ,KAAmB,QAAwB,CACjE,OAAO,IAAI,QAAQ,KAAM,QAAS,GAAG,KAAK,IAAI,SAAS,KAAK,CAAC,OAAO,GAAG,CACzE,CASA,MAAa,cAA0B,CACrC,MAAO,CAAC,EACR,MAAM,IAAK,QAAS,CAClB,QAAQ,MAAM,QAAS,GAAG,CAC5B,CACF,EC9BA,SAAgB,aAAgB,IAAc,MAAU,OAAsB,CAC5E,QAAQ,eAAe,IAAK,MAAO,MAAM,CAC3C,CAGA,SAAgB,cAAiB,IAAc,MAAU,OAAgB,OAAsB,CAC7F,QAAQ,eAAe,IAAK,MAAO,OAAQ,MAAM,CACnD,CAKA,SAAgB,aAAgB,IAAc,OAAgB,SAAgB,CAC5E,OAAQ,QAAQ,YAAY,IAAK,MAAM,GAAW,QACpD,CAGA,SAAgB,cAAiB,IAAc,OAAgB,OAAgB,SAAgB,CAC7F,OAAQ,QAAQ,YAAY,IAAK,OAAQ,MAAM,GAAW,QAC5D,CAGA,SAAgB,aAAa,IAAc,OAAyB,CAClE,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,wBAA2B,IAAc,OAA+B,CACtF,OAAO,QAAQ,YAAY,IAAK,MAAM,CACxC,CAGA,SAAgB,yBACd,IACA,OACA,OACe,CACf,OAAO,QAAQ,YAAY,IAAK,OAAQ,MAAM,CAChD,CAKA,SAAgB,cAAiB,IAAc,OAAgB,GAAG,MAAkB,CAClF,IAAM,SAAgB,QAAQ,eAAe,IAAK,MAAM,GAAK,CAAC,EAC9D,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,MAAM,CAC7D,CAGA,SAAgB,eACd,IACA,OACA,OACA,GAAG,MACG,CACN,IAAM,SAAgB,QAAQ,eAAe,IAAK,OAAQ,MAAM,GAAK,CAAC,EACtE,QAAQ,eAAe,IAAK,CAAC,GAAG,SAAU,GAAG,KAAK,EAAG,OAAQ,MAAM,CACrE,CAKA,SAAgB,aAAmB,IAAc,OAAgB,OAAW,SAAmB,CAC7F,IAAM,UAAmC,QAAQ,eAAe,IAAK,MAAM,EACrE,IAAM,UAAY,IAAI,IAAI,SAAS,kBAAI,IAAI,IACjD,IAAI,IAAI,OAAQ,QAAQ,EACxB,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,WAAiB,IAAc,OAA2B,CACxE,OAAO,QAAQ,YAAY,IAAK,MAAM,mBAAK,IAAI,GACjD,CAKA,SAAgB,gBACd,IACA,OACA,OACA,SACM,CACN,IAAM,UAAoD,QAAQ,eAAe,IAAK,MAAM,EACtF,IAAM,UAAY,CAAE,GAAG,SAAU,EAAI,CAAC,EAC5C,IAAI,QAAU,SACd,QAAQ,eAAe,IAAK,IAAK,MAAM,CACzC,CAGA,SAAgB,cAAiB,IAAc,OAA4C,CACzF,OAAO,QAAQ,YAAY,IAAK,MAAM,GAAK,CAAC,CAC9C,CC7DA,MAAa,gBAAiC,OAAO,IAAI,wBAAwB,EA0BjF,SAAgB,YAAe,KAAiC,CAC9D,OAAO,OAAO,OAAO,EAClB,iBAAkB,GACnB,IACF,CAAC,CACH,CAGA,SAAgB,iBAAiB,MAAkD,CACjF,OACE,OAAO,OAAU,YACjB,OACC,MAAkC,mBAAqB,EAE5D,CCnCA,SAAS,UACP,OACc,CACd,MAAO,CACL,KAAM,UACN,aAAc,EACd,oBAAqB,UACrB,aAAc,CAAC,EACf,GAAG,MACL,CACF,CAGA,SAAgB,UAAU,MAAoB,CAG5C,OAFI,OAAO,OAAU,SAAiB,MAAM,SAAS,EACjD,iBAAiB,KAAK,EAAU,MAAM,KACnC,OAAO,MAAQ,OAAO,KAAK,CACpC,CAqDA,SAAS,oBAAsC,CAQ7C,OAPM,WAAmB,sBACtB,WAAoB,oBAAsB,CACzC,UAAW,CAAC,EACZ,UAAW,CAAC,EACZ,kCAAmB,IAAI,GACzB,GAEM,WAAmB,mBAC7B,CAaA,IAAa,UAAb,MAAa,SAAU,CACrB,OAAe,SACf,8BAAwB,IAAI,IAC5B,0BAAoB,IAAI,IAGxB,gCAA0B,IAAI,IAC9B,eAAiD,CAAC,EAClD,YAA4D,KAC5D,OAAwB,YAAc,GAGtC,OAAO,SAAoD,KAE3D,OAAO,SAAoD,KAQ3D,OAAO,UAAqD,KAM5D,OAAO,aAA8C,KAMrD,OAAO,OAAwD,QAE/D,OAAO,QAAoB,cAQ3B,OAAO,mCAAqB,IAAI,IAgBhC,SAAmB,GAEnB,OAAO,sBAEI,KAEX,OAAO,aAAyB,CAU9B,OATK,UAAU,WACb,UAAU,SAAW,IAAI,UACzB,UAAU,SAAS,SAAW,IAGhC,AAEE,UAAU,YADV,UAAU,SAAS,UAAU,QAAQ,EAChB,MAEhB,UAAU,QACnB,CAOA,OAAO,OAAc,CACnB,UAAU,SAAW,IAAI,UACzB,UAAU,SAAS,SAAW,GAE9B,UAAU,WAAW,UAAU,QAAQ,EAEvC,IAAM,MAAQ,mBAAmB,EACjC,IAAK,GAAM,CAAE,MAAO,QAAS,SAAW,MAAM,UAC5C,GAAI,CAAC,UAAU,SAAS,IAAI,KAAK,EAAG,CAElC,IAAM,KAAO,UAAU,KAAK,EACtB,SAAW,MAAM,kBAAkB,IAAI,IAAI,EACjD,UAAU,SAAS,cAAc,IAC/B,MACA,UAAU,CACR,OAAQ,OACR,MACA,QACA,KAAM,UACN,SAAU,SACV,aAAc,YACd,oBAAqB,SAAW,YAAc,SAChD,CAAC,CACH,CACF,CAEF,IAAK,GAAM,CAAE,MAAO,YAAc,MAAM,UACjC,UAAU,SAAS,IAAI,KAAK,GAC/B,UAAU,SAAS,cAAc,IAC/B,MACA,UAAU,CACR,OAAQ,SAAS,YACjB,MAAA,YACA,SACA,KAAM,WACN,oBAAqB,YACrB,aAAc,CAChB,CAAC,CACH,CAGN,CAaA,OAAO,QAAoB,CACzB,IAAM,UAAY,IAAI,UAOtB,OADA,UAAU,YAAY,SAAS,EACxB,SACT,CAGA,SAAS,MAAY,OAAqB,MAAA,YAAsC,CAC9E,IAAM,KAAO,aAAwBJ,YAAS,WAAY,OAAQ,SAAS,EAErE,IAAM,UAAU,CAAE,OAAQ,MAAO,KAAM,aADxB,KAAK,oBAAoB,MACU,CAAE,CAAC,EAC3D,KAAK,cAAc,IAAI,MAAO,GAAG,EAG7B,OAAO,OAAU,YAAc,MAAM,MACvC,KAAK,cAAc,IAAI,UAAU,MAAM,OAAQ,GAAG,EAEpD,KAAK,KAAK,MAAO,aAAc,IAAI,CACrC,CAGA,gBAAgB,MAAY,QAAoB,MAAA,YAAsC,CACpF,IAAM,MAAQ,mBAAmB,EAC3B,KAAO,UAAU,KAAK,EAWtB,kBAHgB,KAAK,SACvB,MAAM,UAAU,KAAM,GAAM,UAAU,EAAE,KAAK,IAAM,IAAI,EACvD,IAAA,GAAA,EACoC,UAAY,QAC9C,iBACJ,KAAK,UAAY,iBAAmB,MAAM,kBAAkB,IAAI,IAAI,EAAI,IAAA,GAsB1E,GAnBI,KAAK,UAAY,CAAC,kBACpB,MAAM,kBAAkB,OAAO,IAAI,EAGrC,KAAK,cAAc,IACjB,MACA,UAAU,CACR,OAAQ,OACR,MACA,QACA,KAAM,UACN,SAAU,iBACV,aAAc,oBACd,oBAAqB,iBAAmB,YAAc,SACxD,CAAC,CACH,EAII,KAAK,SAAU,CACjB,IAAM,IAAM,MAAM,UAAU,UAAW,GAAM,UAAU,EAAE,KAAK,IAAM,IAAI,EACpE,KAAO,EACT,MAAM,UAAU,KAAO,CAAE,MAAO,QAAS,KAAM,EAE/C,MAAM,UAAU,KAAK,CAAE,MAAO,QAAS,KAAM,CAAC,CAElD,CACA,KAAK,KAAK,KAAM,aAAc,SAAS,CACzC,CAGA,iBAAiB,MAAY,SAAqB,CAChD,IAAM,MAAQ,mBAAmB,EAC3B,KAAO,UAAU,KAAK,EAkB5B,GAhBA,KAAK,cAAc,IACjB,MACA,UAAU,CACR,OAAQ,SAAS,YACjB,MAAA,YACA,SACA,KAAM,WACN,oBAAqB,YACrB,aAAc,EACd,gBAAiB,KAAK,IAAI,EAC1B,eAAgB,KAAK,IAAI,CAC3B,CAAC,CACH,EAII,KAAK,SAAU,CACjB,MAAM,kBAAkB,IAAI,KAAM,QAAQ,EAC1C,IAAM,IAAM,MAAM,UAAU,UAAW,GAAM,UAAU,EAAE,KAAK,IAAM,IAAI,EACpE,KAAO,EACT,MAAM,UAAU,KAAO,CAAE,MAAO,QAAS,EAEzC,MAAM,UAAU,KAAK,CAAE,MAAO,QAAS,CAAC,CAE5C,CACA,KAAK,KAAK,KAAM,aAAc,UAAU,CAC1C,CAGA,IAAI,MAAqB,CACvB,OAAO,KAAK,cAAc,IAAI,KAAK,CACrC,CAGA,kBAWG,CACD,IAAM,QAAoE,CAAC,EAC3E,IAAK,GAAM,CAAC,MAAO,OAAQ,KAAK,cAC9B,QAAQ,KAAK,CACX,MAAO,UAAU,KAAK,EACtB,MAAO,IAAI,MACX,KAAM,IAAI,KAEV,aACE,IAAI,QAAA,YAA4B,IAAI,WAAa,IAAA,GAAY,IAAI,aAAe,EAClF,aAAc,IAAI,aAClB,eAAgB,IAAI,eACpB,gBAAiB,IAAI,gBACrB,kBAAmB,IAAI,kBACvB,oBAAqB,IAAI,oBACzB,aAAc,IAAI,YACpB,CAAC,EAEH,OAAO,OACT,CAmBA,QAAiB,MAAe,CAC9B,IAAI,IAAM,KAAK,cAAc,IAAI,KAAK,EAMtC,GAHI,CAAC,KAAO,OAAO,OAAU,YAAc,MAAM,OAC/C,IAAM,KAAK,cAAc,IAAI,UAAU,MAAM,MAAM,GAEjD,CAAC,IACH,MAAM,UAAU,OAAO,UAAW,UAAU,KAAK,CAAC,EAGpD,GAAI,IAAI,QAAA,aAA6B,IAAI,WAAa,IAAA,GAIpD,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EAC9B,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,IAAI,SAIb,GAAI,IAAI,QAAA,UAAyB,CAC/B,GAAI,CAAC,UAAU,sBACb,MAAM,UAAU,OAAO,UAAW,UAAU,KAAK,CAAC,EAEpD,IAAM,MAAQ,UAAU,sBAAsB,EAC9C,GAAI,CAAC,MACH,MAAM,UAAU,OAAO,UAAW,UAAU,KAAK,CAAC,EAGpD,GAAI,MAAM,OAAO,IAAI,KAAK,EAIxB,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EAC9B,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,MAAM,OAAO,IAAI,KAAK,EAG/B,GAAI,MAAM,UAAU,IAAI,KAAK,EAI3B,MAHA,KAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EAC9B,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,MAAM,UAAU,IAAI,KAAK,EAQlC,GAAI,IAAI,QAAS,CACf,IAAM,MAAQ,YAAY,IAAI,EACxB,SAAW,IAAI,QAAQ,EAO7B,MANA,KAAI,kBAAoB,YAAY,IAAI,EAAI,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EACzB,IAAI,kBAAiB,IAAI,gBAAkB,KAAK,IAAI,GACzD,MAAM,UAAU,IAAI,MAAO,QAAQ,EACnC,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,QACT,CAEA,IAAM,MAAQ,YAAY,IAAI,EACxB,SAAW,KAAK,eAAe,GAAG,EAOxC,MANA,KAAI,kBAAoB,YAAY,IAAI,EAAI,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EACzB,IAAI,kBAAiB,IAAI,gBAAkB,KAAK,IAAI,GACzD,MAAM,UAAU,IAAI,MAAO,QAAQ,EACnC,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,QACT,CAEA,GAAI,IAAI,QAAS,CACf,IAAM,MAAQ,YAAY,IAAI,EACxB,SAAW,IAAI,QAAQ,EAc7B,MAbA,KAAI,kBAAoB,YAAY,IAAI,EAAI,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EACzB,IAAI,kBAAiB,IAAI,gBAAkB,KAAK,IAAI,GACrD,IAAI,QAAA,cACN,IAAI,SAAW,SAGX,KAAK,UACP,mBAAmB,CAAC,CAAC,kBAAkB,IAAI,UAAU,KAAK,EAAG,QAAQ,GAGzE,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,QACT,CAEA,GAAI,KAAK,UAAU,IAAI,KAAK,EAAG,CAC7B,IAAM,MAAQ,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,IAAI,SAAS,EAE/C,MADA,MAAM,KAAK,UAAU,KAAK,CAAC,EACjB,MAAM,iCAAiC,MAAM,KAAK,MAAM,GAAG,CACvE,CACA,KAAK,UAAU,IAAI,KAAK,EAExB,GAAI,CACF,IAAM,MAAQ,YAAY,IAAI,EACxB,SAAW,KAAK,eAAe,GAAG,EASxC,MARA,KAAI,kBAAoB,YAAY,IAAI,EAAI,MAC5C,IAAI,eACJ,IAAI,eAAiB,KAAK,IAAI,EACzB,IAAI,kBAAiB,IAAI,gBAAkB,KAAK,IAAI,GACrD,IAAI,QAAA,cACN,IAAI,SAAW,UAEjB,KAAK,KAAK,MAAO,WAAY,IAAI,IAAI,EAC9B,QACT,QAAU,CACR,KAAK,UAAU,OAAO,KAAK,CAC7B,CACF,CAGA,WAAkB,CAGlB,CA4BA,SAAS,SAA+C,CAEtD,OADA,KAAK,gBAAgB,IAAI,QAAQ,MACpB,KAAK,gBAAgB,OAAO,QAAQ,CACnD,CAUA,WAAW,MAAY,wBAAU,IAAI,IAA2B,CAC9D,IAAM,KAAO,UAAU,KAAK,EAEtB,IAAM,KAAK,cAAc,IAAI,KAAK,GAAK,KAAK,cAAc,IAAI,UAAU,MAAM,EAGpF,GAAK,KAAO,SAAQ,IAAI,GAAG,IAC3B,QAAQ,IAAI,GAAG,EAGD,oBACN,CAAC,CAAC,kBAAkB,IAAI,IAAI,GAGpC,KAAI,SAAW,IAAA,GACf,IAAI,aAAe,EACnB,IAAI,oBAAsB,UAC1B,KAAK,KAAK,KAAM,cAAe,IAAI,IAAI,EAGvC,IAAK,GAAM,CAAC,SAAU,UAAW,KAAK,cAChC,OAAO,aAAa,SAAS,IAAI,GACnC,KAAK,WAAW,SAAU,OAAO,CARtB,CAWjB,CAQA,KAAa,SAAe,MAAsC,KAAwB,CAUxF,GAAI,KAAK,gBAAgB,OAAS,EAAG,OACrC,IAAM,MAAQ,UAAU,QAAQ,EAS1B,SAAW,KAAK,eAAe,KAAM,GAAM,EAAE,QAAU,OAAS,EAAE,QAAU,KAAK,EACnF,SACF,SAAS,UAAY,KAAK,IAAI,EAE9B,KAAK,eAAe,KAAK,CAAE,MAAO,MAAO,KAAM,UAAW,KAAK,IAAI,CAAE,CAAC,EAGpE,KAAK,aAAa,aAAa,KAAK,WAAW,EACnD,KAAK,YAAc,eAAiB,CAClC,IAAM,MAAQ,KAAK,eAAe,OAAO,CAAC,EAC1C,GAAI,MAAM,SAAW,EACrB,IAAK,IAAM,YAAY,KAAK,gBAC1B,GAAI,CACF,SAAS,KAAK,CAChB,MAAQ,CAER,CAEJ,EAAG,UAAU,WAAW,EAGxB,KAAK,YAAY,QAAQ,CAC3B,CAGA,cAAqB,CACnB,AAEE,KAAK,eADL,aAAa,KAAK,WAAW,EACV,MAErB,IAAM,MAAQ,KAAK,eAAe,OAAO,CAAC,EAC1C,GAAI,MAAM,SAAW,EACrB,IAAK,IAAM,YAAY,KAAK,gBAC1B,GAAI,CACF,SAAS,KAAK,CAChB,MAAQ,CAER,CAEJ,CAYA,UAAkB,IAAsC,CACtD,IAAM,WAAa,aAA4BA,YAAS,YAAa,IAAI,OAAQ,CAAC,CAAC,EAC7E,aAAe,cAAmBA,YAAS,OAAQ,IAAI,MAAM,EAM7D,eACJ,OAAO,KAAK,YAAY,CAAC,CAAC,OAAS,EAC/B,KAAK,IAAI,GAAG,OAAO,KAAK,YAAY,CAAC,CAAC,IAAI,MAAM,CAAC,EAAI,EACrD,EACA,WAAa,KAAK,IAAI,WAAW,OAAQ,cAAc,EAEvD,WAAoB,CAAC,EAC3B,IAAK,IAAI,MAAQ,EAAG,MAAQ,WAAY,QAAS,CAC/C,IAAM,MAAQ,aAAa,QAAU,WAAW,OAChD,GAAI,CAAC,MACH,MAAU,MACR,iDAAiD,MAAM,MAAM,UAAU,IAAI,MAAM,EAAE,qIAGrF,EAGF,GAAI,IAAI,QAAA,YAA2B,CACjC,IAAM,OAAS,KAAK,cAAc,IAAI,KAAK,EAC3C,GAAI,QAAU,OAAO,QAAA,UAAyB,CAK5C,IAAM,OAAS,UAAU,IAAI,MAAM,EAC7B,IAAM,UAAU,KAAK,EAErB,IADO,wBAAmCA,YAAS,WAAY,IAAI,MAEpE,IAAM,aACL,iDAAiD,IAAI,iDACrD,SAAS,OAAO,qFACF,IAAI,iDACxB,MAAU,MACR,iCAAiC,IAAI,oBAAoB,OAAO,kCAC9B,KACpC,CACF,CACF,CACA,WAAW,KAAK,KAAK,CACvB,CAGA,IAAM,eAAiB,WAAwBA,YAAS,UAAW,IAAI,OAAO,SAAS,EACjF,UAA4C,CAAC,EACnD,IAAK,GAAM,CAAC,KAAM,SAAU,eAAgB,CAC1C,IAAM,cACJ,OAAS,yBAAyBA,YAAS,cAAe,IAAI,OAAO,UAAW,IAAI,EAClF,eAAe,UAAU,KAAK,CAAC,KAAM,aAAa,CAAC,CACzD,CAOA,IAAM,OAAsC,CAAC,GAJ1B,WACjBA,YAAS,MACT,IAAI,OAAO,SAE4C,CAAC,CAAC,QAAQ,CAAC,EAG9D,SAA0C,CAAC,EACjD,IAAK,GAAM,CAAC,IAAK,WAAY,UAAU,mBACrC,IAAK,GAAM,CAAC,KAAM,UAAW,WAA4B,IAAK,IAAI,OAAO,SAAS,EAChF,SAAS,KAAK,CAAC,KAAM,QAAQ,OAAQ,IAAI,CAAC,CAAC,EAS/C,MAAO,CAAE,WAAY,UAAW,OAAQ,SAAU,cAL5B,wBACpBA,YAAS,eACT,IAAI,OAAO,SAGiD,CAAE,CAClE,CAEA,eAAuB,IAAwB,CAC7C,IAAM,KAAQ,IAAI,OAAS,KAAK,UAAU,GAAG,EAEvC,KAAc,CAAC,EACrB,IAAK,IAAM,SAAS,KAAK,WACvB,KAAK,KAAK,KAAK,QAAQ,KAAK,CAAC,EAG/B,IAAM,SAAW,IAAI,IAAI,OAAO,GAAG,IAAI,EAGvC,KAAK,iBAAiB,SAAU,IAAI,EAGpC,IAAM,cAAgB,KAAK,cAC3B,GAAI,eAAiB,OAAO,SAAS,gBAAmB,WACtD,GAAI,CACF,IAAM,OAAS,SAAS,cAAc,CAAC,EACnC,QAAU,OAAO,OAAO,MAAS,YACnC,IAAI,oBAAsB,UACzB,OACE,SAAW,CACV,IAAI,oBAAsB,WAC5B,CAAC,CAAC,CACD,UAAY,CACX,IAAI,oBAAsB,QAC5B,CAAC,EACH,UAAU,QAAQ,KAChB,qBAAqB,UAAU,IAAI,MAAM,EAAE,GAAG,OAAO,aAAa,EAAE,iGAEtE,GAEA,IAAI,oBAAsB,WAE9B,OAAS,IAAK,CACZ,IAAI,oBAAsB,SAC1B,UAAU,QAAQ,MAChB,IACA,qBAAqB,UAAU,IAAI,MAAM,EAAE,GAAG,OAAO,aAAa,EAAE,kBACtE,CACF,CAGF,OAAO,QACT,CAYA,oBAA4B,OAA+B,CACzD,IAAM,WAAa,aAA4BA,YAAS,YAAa,OAAQ,CAAC,CAAC,EACzE,aAAe,cAAmBA,YAAS,OAAQ,MAAM,EACzD,SAAW,WAAW,KAAK,KAAM,QAE9B,UADO,aAAa,QAAU,IACf,CACvB,EAKK,eAAiB,WAAwBA,YAAS,UAAW,OAAO,SAAS,EAC7E,SAAqB,CAAC,EAC5B,IAAK,GAAM,CAAC,KAAM,SAAU,eAAgB,CAC1C,IAAM,cACJ,OAAS,yBAAyBA,YAAS,cAAe,OAAO,UAAW,IAAI,EAC9E,eAAe,SAAS,KAAK,UAAU,aAAa,CAAC,CAC3D,CAGA,MAAO,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,SAAU,GAAG,QAAQ,CAAC,CAAC,CAChD,CAEA,iBAAyB,SAAe,KAA+B,CAQrE,IAAK,GAAM,CAAC,KAAM,iBAAkB,KAAK,UACvC,OAAO,eAAe,SAAU,KAAM,CACpC,QAAW,CACT,IAAM,MAAQ,KAAK,QAAQ,aAAa,EAUxC,OATe,KAAK,cAAc,IAAI,aAC7B,CAAC,EAAE,QAAA,aACV,OAAO,eAAe,SAAU,KAAM,CACpC,MACA,WAAY,GACZ,aAAc,GACd,SAAU,EACZ,CAAC,EAEI,KACT,EACA,WAAY,GACZ,aAAc,EAChB,CAAC,EAIH,IAAK,GAAM,CAAC,KAAM,UAAW,KAAK,OAChC,OAAO,eAAe,SAAU,KAAM,CACpC,KAAM,CAIJ,GAAI,UAAU,aAAc,CAC1B,IAAM,IAAM,UAAU,aAAa,OAAO,MAAM,EAChD,GAAI,MAAQ,IAAA,GAAW,OAAO,GAChC,CAGA,IAAM,IAAM,QAAQ,IAAI,OAAO,QAC/B,GAAI,MAAQ,IAAA,GAAW,OAAO,IAC9B,GAAI,OAAO,eAAiB,IAAA,GAAW,OAAO,OAAO,aACrD,MAAM,UAAU,OAAO,UAAW,OAAO,MAAM,CACjD,EACA,WAAY,GACZ,aAAc,EAChB,CAAC,EAIH,IAAK,GAAM,CAAC,KAAM,OAAQ,KAAK,SAC7B,OAAO,eAAe,SAAU,KAAM,CAAE,IAAK,WAAY,GAAM,aAAc,EAAK,CAAC,CAEvF,CACF,EC94BA,MAAM,qBAA8C,CAAC,EAG/C,iCAAmB,IAAI,IAC7B,IAAI,aAAoB,KAExB,SAAS,aAAa,UAAsB,CAC1C,aAAe,UACf,IAAK,GAAM,CAAE,OAAQ,SAAW,qBACzB,UAAU,IAAI,MAAM,GACvB,UAAU,SAAS,OAAQ,OAAQ,KAAK,EAG5C,qBAAqB,OAAS,CAChC,CAGA,UAAU,SAAW,aAGrB,SAAS,KAAK,UAAsB,CAClC,IAAK,GAAM,EAAG,CAAE,OAAQ,UAAY,iBAC7B,UAAU,IAAI,MAAM,GACvB,UAAU,SAAS,OAAQ,OAAQ,KAAK,CAG9C,CAKA,UAAU,SAAY,WAAmB,CACvC,aAAe,UACf,KAAK,SAAS,CAChB,EAKA,UAAU,UAAY,KAgBtB,MAAM,iCAAmB,IAAI,QAkB7B,SAAS,sBAAsB,OAAa,MAAoB,CAC9D,GAAI,QAAA,UAAyB,OAC7B,IAAM,MAAQ,QAAQ,UAItB,GAHI,CAAC,OAED,CADS,QAAQ,cAAcC,YAAS,YAAa,KAAK,GAE1D,iBAAiB,IAAI,MAAM,EAAG,OAClC,iBAAiB,IAAI,MAAM,EAC3B,IAAM,KAAO,OAAO,MAAQ,OAAO,MAAM,EACzC,QAAQ,KACN,4BAA4B,KAAK,IAAI,MAAM,oKAI7C,CACF,CAMA,SAAgB,oBAAoB,OAAa,MAAoB,CACnE,aAAaA,YAAS,WAAY,GAAM,MAAM,EAC9C,aAAaA,YAAS,MAAO,MAAO,MAAM,EAG1C,sBAAsB,OAAQ,KAAK,EAInC,IAAM,KAAO,OAAO,MAAQ,OAAO,MAAM,EACzC,iBAAiB,IAAI,KAAM,CAAE,OAAQ,KAAM,CAAC,EAExC,aAEG,aAAa,IAAI,MAAM,GAC1B,aAAa,SAAS,OAAQ,OAAQ,KAAK,EAI7C,qBAAqB,KAAK,CAAE,OAAQ,KAAM,CAAC,CAE/C,CAGA,SAAgB,WAAW,QAA0C,CACnE,MAAQ,SAAgB,CACtB,aAAaA,YAAS,WAAY,aAAc,MAAM,EACtD,oBAAoB,OAAQ,SAAS,OAAA,WAAwB,CAC/D,CACF,CAGA,SAAgB,QAAQ,QAA0C,CAChE,MAAQ,SAAgB,CACtB,aAAaA,YAAS,WAAY,UAAW,MAAM,EACnD,oBAAoB,OAAQ,SAAS,OAAA,WAAwB,CAC/D,CACF,CAGA,SAAgB,UAAU,QAA0C,CAClE,MAAQ,SAAgB,CACtB,aAAaA,YAAS,WAAY,YAAa,MAAM,EACrD,oBAAoB,OAAQ,SAAS,OAAA,WAAwB,CAC/D,CACF,CAGA,SAAgB,WAAW,QAA0C,CACnE,MAAQ,SAAgB,CACtB,aAAaA,YAAS,WAAY,aAAc,MAAM,EACtD,oBAAoB,OAAQ,SAAS,OAAA,WAAwB,CAC/D,CACF,CAKA,SAAgB,eAAiC,CAC/C,OAAQ,OAAQ,cAAgB,CAC9B,aAAaA,YAAS,eAAgB,YAAa,MAAM,CAC3D,CACF,CAmBA,SAAgB,YAA8B,CAC5C,OAAQ,OAAQ,cAAgB,CAC9B,aAAaA,YAAS,YAAa,YAAa,MAAM,CACxD,CACF,CAmBA,SAAS,eACP,MACA,OACA,YACA,eACM,CAKF,OAAO,gBAAmB,SAC5B,gBAAgBA,YAAS,OAAQ,OAAQ,eAAgB,KAAK,EAG5D,aAAe,MAIjB,aAAaA,YAAS,UAAW,OAAQ,YAAuB,KAAK,CAEzE,CAwBA,SAAgB,UAAU,MAA+C,CACvE,QACE,OACA,YACA,iBACS,CACT,eAAe,MAAO,OAAQ,YAAa,cAAc,CAC3D,EACF,CAoCA,SAAgB,OAAO,MAA+C,CACpE,QACE,OACA,YACA,iBACS,CACT,eAAe,MAAO,OAAQ,YAAa,cAAc,CAC3D,EACF,CAkEA,SAAgB,MACd,OACA,aACmB,CACnB,OAAQ,OAAQ,cAAgB,CAC9B,aAAaA,YAAS,MAAO,OAAQ,YAAuB,CAAE,OAAQ,YAAa,CAAC,CACtF,CACF,CAGA,SAAS,cAAc,OAAmB,CACxC,aAAaA,YAAS,QAAS,GAAM,MAAM,EAC3C,OAAO,QAAU,UAAY,CAC3B,IAAM,MAA6B,CAAC,EAC9B,MAAa,IAAI,MACrB,CAAC,EACD,CACE,IAAI,EAAG,IAAK,CAIV,OAHI,MAAQ,YACG,OAAO,OAAO,IAAI,OAAU,KAAK,EAExC,QACN,MAAM,KAAiB,MAChB,MAEX,CACF,CACF,EACA,OAAO,KACT,CACF,CAwBA,SAAgB,QAAQ,OAAmB,CACzC,cAAc,MAAM,CACtB,CAkBA,SAAgB,YACd,KAC+C,CAE/C,OADA,cAAc,IAAI,EACX,IACT,CCxbA,MAAM,4BAAc,IAAI,IAGxB,SAAgB,mBAAmB,GAA4B,CAE7D,OADA,YAAY,IAAI,EAAE,MACL,YAAY,OAAO,EAAE,CACpC,CAQA,SAAgB,kBAAiC,CAC/C,IAAM,QAAU,CAAC,GAAG,WAAW,EAE/B,OADA,YAAY,MAAM,EACX,OACT,CASA,eAAsB,eAAe,KAAmC,CACtE,MAAM,QAAQ,WAAW,KAAK,IAAI,KAAO,KAAO,GAAG,CAAC,CAAC,CACvD,CAGA,eAAsB,YAA4B,CAChD,MAAM,eAAe,iBAAiB,CAAC,CACzC"}
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-di",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The dependency injection container behind KickJS: typed tokens, decorators, REQUEST scope, HMR-safe registrations",
5
+ "keywords": [
6
+ "kickjs",
7
+ "dependency-injection",
8
+ "di",
9
+ "ioc",
10
+ "decorators",
11
+ "typescript"
12
+ ],
13
+ "type": "module",
14
+ "main": "dist/index.mjs",
15
+ "types": "dist/index.d.mts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.mjs",
19
+ "types": "./dist/index.d.mts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "reflect-metadata": "^0.2.2"
27
+ },
28
+ "devDependencies": {
29
+ "@swc/core": "^1.16.13",
30
+ "@types/node": "^26.6.5",
31
+ "typescript": "^7.0.2",
32
+ "unplugin-swc": "^2.0.0",
33
+ "vitest": "^5.0.3"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "license": "MIT",
39
+ "author": "Felix Orinda",
40
+ "engines": {
41
+ "node": ">=20.0"
42
+ },
43
+ "homepage": "https://kickjs.app/",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/forinda/kick-js.git",
47
+ "directory": "packages/di"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/forinda/kick-js/issues"
51
+ },
52
+ "scripts": {
53
+ "build": "tsdown",
54
+ "dev": "tsdown --watch",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "typecheck": "tsc --noEmit",
58
+ "clean": "rm -rf dist .wireit"
59
+ }
6
60
  }