@nextrush/di 3.0.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.
@@ -0,0 +1,517 @@
1
+ /**
2
+ * @nextrush/di - Types
3
+ *
4
+ * Type definitions for the dependency injection container.
5
+ */
6
+ /**
7
+ * Token used to identify a dependency in the container.
8
+ * Can be a class constructor, string, or symbol.
9
+ */
10
+ type Token<T = unknown> = Constructor<T> | string | symbol;
11
+ /**
12
+ * Constructor type for class-based tokens.
13
+ */
14
+ type Constructor<T = unknown> = new (...args: unknown[]) => T;
15
+ /**
16
+ * Provider that uses a class constructor.
17
+ */
18
+ interface ClassProvider<T> {
19
+ useClass: Constructor<T>;
20
+ }
21
+ /**
22
+ * Provider that uses a factory function.
23
+ *
24
+ * Without `inject`: factory receives the container.
25
+ * With `inject`: factory receives resolved dependencies in order.
26
+ *
27
+ * @example Without inject
28
+ * ```typescript
29
+ * container.register('DB', {
30
+ * useFactory: (c) => new Database(c.resolve(ConfigService).dbUrl),
31
+ * });
32
+ * ```
33
+ *
34
+ * @example With inject
35
+ * ```typescript
36
+ * container.register('DB', {
37
+ * useFactory: (config: ConfigService) => new Database(config.dbUrl),
38
+ * inject: [ConfigService],
39
+ * });
40
+ * ```
41
+ *
42
+ * @example Async factory (auto-resolved by bootstrap)
43
+ * ```typescript
44
+ * container.register('DB', {
45
+ * useFactory: async (config: ConfigService) => {
46
+ * const db = new Database(config.dbUrl);
47
+ * await db.connect();
48
+ * return db;
49
+ * },
50
+ * inject: [ConfigService],
51
+ * });
52
+ * // Controllers plugin calls bootstrap() automatically.
53
+ * // For functional mode: await container.bootstrap()
54
+ * ```
55
+ */
56
+ interface FactoryProvider<T> {
57
+ useFactory: (...args: unknown[]) => T | Promise<T>;
58
+ inject?: Token[];
59
+ }
60
+ /**
61
+ * Provider that uses a constant value.
62
+ */
63
+ interface ValueProvider<T> {
64
+ useValue: T;
65
+ }
66
+ /**
67
+ * Union of all provider types.
68
+ */
69
+ type Provider<T> = ClassProvider<T> | FactoryProvider<T> | ValueProvider<T>;
70
+ /**
71
+ * Lifecycle scope for registered services.
72
+ */
73
+ type Scope = 'singleton' | 'transient';
74
+ /**
75
+ * Options for service registration.
76
+ */
77
+ interface ServiceOptions {
78
+ scope?: Scope;
79
+ }
80
+ /**
81
+ * Container interface for dependency injection operations.
82
+ */
83
+ interface ContainerInterface {
84
+ /**
85
+ * Register a dependency with the container.
86
+ *
87
+ * @param token - The token to register
88
+ * @param provider - The provider (class, value, or factory)
89
+ * @param options - Optional registration options
90
+ */
91
+ register<T>(token: Token<T>, provider: Provider<T>, options?: RegisterOptions): void;
92
+ /**
93
+ * Resolve a dependency from the container (synchronous).
94
+ */
95
+ resolve<T>(token: Token<T>): T;
96
+ /**
97
+ * Resolve a dependency that may have been registered with an async factory.
98
+ * Awaits the result if the factory returns a Promise.
99
+ *
100
+ * Prefer `bootstrap()` over calling this for each async provider.
101
+ */
102
+ resolveAsync<T>(token: Token<T>): Promise<T>;
103
+ /**
104
+ * Bootstrap all factory providers.
105
+ *
106
+ * Resolves every factory-registered provider, awaiting any Promises,
107
+ * and caches results for synchronous access via `resolve()`.
108
+ *
109
+ * Called automatically by the controllers plugin during `install()`.
110
+ * For functional mode, call once before handling requests.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * container.register('DB', {
115
+ * useFactory: async () => { const db = new Database(); await db.connect(); return db; },
116
+ * });
117
+ * await container.bootstrap(); // All async providers resolved
118
+ * container.resolve('DB'); // Works synchronously now
119
+ * ```
120
+ */
121
+ bootstrap(): Promise<void>;
122
+ /**
123
+ * Resolve all dependencies registered under a token.
124
+ */
125
+ resolveAll<T>(token: Token<T>): T[];
126
+ /**
127
+ * Check if a token is registered.
128
+ */
129
+ isRegistered<T>(token: Token<T>): boolean;
130
+ /**
131
+ * Clear all registered instances (useful for testing).
132
+ */
133
+ clearInstances(): void;
134
+ /**
135
+ * Reset the container completely (useful for testing).
136
+ */
137
+ reset(): void;
138
+ /**
139
+ * Create a child container with isolated scope.
140
+ */
141
+ createChild(): ContainerInterface;
142
+ }
143
+ /**
144
+ * Metadata key constants.
145
+ */
146
+ declare const METADATA_KEYS: {
147
+ readonly SERVICE_TYPE: "di:type";
148
+ readonly SERVICE_SCOPE: "di:scope";
149
+ readonly INJECT_TOKEN: "di:inject";
150
+ readonly PARAM_TYPES: "design:paramtypes";
151
+ readonly CONFIG_PREFIX: "di:config:prefix";
152
+ readonly OPTIONAL_PARAMS: "di:optional";
153
+ };
154
+ /**
155
+ * Options for @Config decorator.
156
+ */
157
+ interface ConfigOptions {
158
+ /**
159
+ * Environment variable prefix.
160
+ *
161
+ * When set, the configuration class can be used with prefix-based env loading.
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * @Config({ prefix: 'DB' })
166
+ * class DatabaseConfig {
167
+ * readonly host = 'localhost'; // reads DB_HOST
168
+ * readonly port = 5432; // reads DB_PORT
169
+ * }
170
+ * ```
171
+ */
172
+ prefix?: string;
173
+ }
174
+ /**
175
+ * Registration options for container.register().
176
+ */
177
+ interface RegisterOptions {
178
+ /** Lifecycle scope — defaults to 'transient' if not specified */
179
+ scope?: Scope;
180
+ }
181
+
182
+ /**
183
+ * @nextrush/di - Container
184
+ *
185
+ * Lightweight wrapper around tsyringe with enhanced error handling.
186
+ */
187
+
188
+ /**
189
+ * The global DI container instance.
190
+ *
191
+ * Use this for registering and resolving dependencies throughout your application.
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * // Register a service
196
+ * container.register(UserService, { useClass: UserService });
197
+ *
198
+ * // Resolve a service
199
+ * const userService = container.resolve(UserService);
200
+ *
201
+ * // Register with value
202
+ * container.register('CONFIG', { useValue: { port: 3000 } });
203
+ * ```
204
+ */
205
+ declare const container: ContainerInterface;
206
+ /**
207
+ * Create a new isolated container.
208
+ *
209
+ * Useful for testing or creating scoped containers.
210
+ * Note: Creates a child container from the global tsyringe container.
211
+ * For truly isolated containers, reset the child before use.
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const testContainer = createContainer();
216
+ * testContainer.register(UserService, { useClass: MockUserService });
217
+ * ```
218
+ */
219
+ declare function createContainer(): ContainerInterface;
220
+
221
+ /** Decorator-compatible constructor type. Uses `never[]` so any class is structurally assignable. */
222
+ type DecoratorTarget = new (...args: never[]) => unknown;
223
+ /**
224
+ * Mark a class as injectable service.
225
+ *
226
+ * Services are singletons by default - one instance shared across the application.
227
+ *
228
+ * @param options - Optional configuration for the service
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * @Service()
233
+ * export class UserService {
234
+ * async findAll() {
235
+ * return [{ id: 1, name: 'Alice' }];
236
+ * }
237
+ * }
238
+ *
239
+ * // With transient scope (new instance each time)
240
+ * @Service({ scope: 'transient' })
241
+ * export class RequestLogger {
242
+ * constructor() {
243
+ * this.timestamp = Date.now();
244
+ * }
245
+ * }
246
+ * ```
247
+ */
248
+ declare function Service(options?: ServiceOptions): (target: DecoratorTarget) => void;
249
+ /**
250
+ * Mark a class as a repository (data access layer).
251
+ *
252
+ * Semantically identical to @Service(), but indicates the class is for data access.
253
+ * Repositories are singletons by default.
254
+ *
255
+ * @param options - Optional configuration for the repository
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * @Repository()
260
+ * export class UserRepository {
261
+ * async findById(id: string) {
262
+ * return db.users.findUnique({ where: { id } });
263
+ * }
264
+ * }
265
+ * ```
266
+ */
267
+ declare function Repository(options?: ServiceOptions): (target: DecoratorTarget) => void;
268
+ /**
269
+ * Explicitly inject a dependency by token.
270
+ *
271
+ * Use this when:
272
+ * - Injecting an interface (TypeScript can't infer interface types at runtime)
273
+ * - Using string or symbol tokens
274
+ * - The automatic type inference doesn't work
275
+ *
276
+ * @param token - The token to inject (class, string, or symbol)
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * @Service()
281
+ * export class UserService {
282
+ * constructor(
283
+ * // Inject by interface token
284
+ * @inject('IUserRepository') private repo: IUserRepository,
285
+ *
286
+ * // Inject by string token
287
+ * @inject('DATABASE_URL') private dbUrl: string
288
+ * ) {}
289
+ * }
290
+ * ```
291
+ */
292
+ declare function inject(token: unknown): ParameterDecorator;
293
+ /**
294
+ * Make a class injectable without singleton scope.
295
+ *
296
+ * Unlike @Service() which defaults to singleton, @Injectable() creates
297
+ * a transient registration that can be resolved from the container.
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * @Injectable()
302
+ * export class FeatureService {
303
+ * constructor(private logger: Logger) {}
304
+ * }
305
+ *
306
+ * // Resolve from container
307
+ * const service = container.resolve(FeatureService);
308
+ * ```
309
+ */
310
+ declare function Injectable(): (target: DecoratorTarget) => void;
311
+ /**
312
+ * @deprecated Use `@Injectable()` instead. Will be removed in v4.
313
+ */
314
+ declare const AutoInjectable: typeof Injectable;
315
+ /**
316
+ * Make a class resolvable by the DI container without adding service metadata.
317
+ *
318
+ * Used internally by `@Controller()` to enable constructor injection
319
+ * without marking the class as a service. This is the abstraction boundary
320
+ * that prevents `@nextrush/decorators` from depending directly on tsyringe.
321
+ *
322
+ * @param target - The class constructor to make injectable
323
+ *
324
+ * @internal
325
+ */
326
+ declare function markInjectable(target: Constructor): void;
327
+ /**
328
+ * Delay resolution of a dependency.
329
+ *
330
+ * Use this to break circular dependencies by lazily resolving the dependency.
331
+ *
332
+ * @param token - Factory function returning the class to inject
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * @Service()
337
+ * class UserService {
338
+ * constructor(
339
+ * @inject(delay(() => OrderService)) private orderService: OrderService
340
+ * ) {}
341
+ * }
342
+ *
343
+ * @Service()
344
+ * class OrderService {
345
+ * constructor(
346
+ * @inject(delay(() => UserService)) private userService: UserService
347
+ * ) {}
348
+ * }
349
+ * ```
350
+ */
351
+ declare function delay(tokenFactory: () => Constructor): unknown;
352
+ /**
353
+ * Check if a class has DI metadata (is decorated with @Service or @Repository).
354
+ *
355
+ * @param target - The class to check
356
+ * @returns True if the class has DI metadata
357
+ */
358
+ declare function hasServiceMetadata(target: object): boolean;
359
+ /**
360
+ * Get the service type from a decorated class.
361
+ *
362
+ * @param target - The class to check
363
+ * @returns The service type ('service' | 'repository') or undefined
364
+ */
365
+ declare function getServiceType(target: object): string | undefined;
366
+ /**
367
+ * Get the scope from a decorated class.
368
+ *
369
+ * @param target - The class to check
370
+ * @returns The scope ('singleton' | 'transient') or undefined
371
+ */
372
+ declare function getServiceScope(target: object): Scope | undefined;
373
+ /**
374
+ * Mark a class as a configuration holder.
375
+ *
376
+ * Configuration classes are always singletons and serve as centralized
377
+ * configuration containers for your application. Similar to Spring Boot's
378
+ * `@Configuration` / `@ConfigurationProperties`.
379
+ *
380
+ * @param options - Optional configuration (e.g., env prefix)
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * // Simple configuration class
385
+ * @Config()
386
+ * export class AppConfig {
387
+ * readonly port = Number(process.env.PORT ?? 3000);
388
+ * readonly host = process.env.HOST ?? 'localhost';
389
+ * }
390
+ *
391
+ * // With env prefix — documents that this config reads DB_* vars
392
+ * @Config({ prefix: 'DB' })
393
+ * export class DatabaseConfig {
394
+ * readonly host = process.env.DB_HOST ?? 'localhost';
395
+ * readonly port = Number(process.env.DB_PORT ?? 5432);
396
+ * readonly name = process.env.DB_NAME ?? 'mydb';
397
+ * }
398
+ *
399
+ * // Inject into services
400
+ * @Service()
401
+ * export class UserService {
402
+ * constructor(private config: DatabaseConfig) {}
403
+ *
404
+ * getConnectionString() {
405
+ * return `postgres://${this.config.host}:${this.config.port}/${this.config.name}`;
406
+ * }
407
+ * }
408
+ * ```
409
+ */
410
+ declare function Config(options?: ConfigOptions): (target: DecoratorTarget) => void;
411
+ /**
412
+ * Get the config prefix from a @Config-decorated class.
413
+ *
414
+ * @param target - The class to check
415
+ * @returns The prefix string or undefined
416
+ */
417
+ declare function getConfigPrefix(target: object): string | undefined;
418
+ /**
419
+ * Mark a constructor parameter as optional.
420
+ *
421
+ * When a dependency marked `@Optional()` cannot be resolved, the container
422
+ * injects `undefined` instead of throwing a `DependencyResolutionError`.
423
+ *
424
+ * @example
425
+ * ```typescript
426
+ * // Optional dependency - if 'MAILER' is not registered, `mailer` will be undefined
427
+ * @Service()
428
+ * class NotificationService {
429
+ * constructor(
430
+ * @Optional() @inject('MAILER') private mailer?: MailerService,
431
+ * ) {}
432
+ *
433
+ * send(message: string) {
434
+ * if (this.mailer) {
435
+ * this.mailer.send(message);
436
+ * } else {
437
+ * console.log('No mailer configured, skipping:', message);
438
+ * }
439
+ * }
440
+ * }
441
+ * ```
442
+ */
443
+ declare function Optional(): ParameterDecorator;
444
+ /**
445
+ * Check whether a specific constructor parameter is marked `@Optional()`.
446
+ */
447
+ declare function isParameterOptional(target: object, parameterIndex: number): boolean;
448
+ /**
449
+ * Get the set of optional parameter indices for a class constructor.
450
+ */
451
+ declare function getOptionalParams(target: object): ReadonlySet<number>;
452
+
453
+ /**
454
+ * @nextrush/di - Errors
455
+ *
456
+ * Production-grade error classes with actionable messages.
457
+ */
458
+ /**
459
+ * Base error class for all DI-related errors.
460
+ */
461
+ declare class DIError extends Error {
462
+ constructor(message: string);
463
+ }
464
+ /**
465
+ * Error thrown when a dependency cannot be resolved.
466
+ *
467
+ * Provides clear guidance on how to fix the issue.
468
+ */
469
+ declare class DependencyResolutionError extends DIError {
470
+ readonly chain: string[];
471
+ readonly missingDependency: string;
472
+ constructor(chain: string[], missing: string);
473
+ }
474
+ /**
475
+ * Error thrown when a circular dependency is detected.
476
+ *
477
+ * Provides strategies to break the cycle.
478
+ */
479
+ declare class CircularDependencyError extends DIError {
480
+ readonly cycle: string[];
481
+ constructor(cycle: string[]);
482
+ }
483
+ /**
484
+ * Error thrown when TypeScript cannot infer constructor parameter types.
485
+ *
486
+ * Usually caused by missing type annotations or incorrect tsconfig.
487
+ */
488
+ declare class TypeInferenceError extends DIError {
489
+ readonly className: string;
490
+ readonly parameterIndex: number;
491
+ constructor(className: string, parameterIndex: number);
492
+ }
493
+ /**
494
+ * Error thrown when an invalid provider configuration is given.
495
+ */
496
+ declare class InvalidProviderError extends DIError {
497
+ readonly token: string;
498
+ constructor(token: string);
499
+ }
500
+ /**
501
+ * Error thrown when trying to resolve from a disposed container.
502
+ */
503
+ declare class ContainerDisposedError extends DIError {
504
+ constructor();
505
+ }
506
+ /**
507
+ * Error thrown when a required dependency is not found.
508
+ *
509
+ * @deprecated Use {@link DependencyResolutionError} instead — it provides
510
+ * the same information plus the resolution chain context.
511
+ */
512
+ declare class MissingDependencyError extends DIError {
513
+ readonly token: string;
514
+ constructor(token: string);
515
+ }
516
+
517
+ export { AutoInjectable, CircularDependencyError, type ClassProvider, Config, type ConfigOptions, type Constructor, ContainerDisposedError, type ContainerInterface, DIError, DependencyResolutionError, type FactoryProvider, Injectable, InvalidProviderError, METADATA_KEYS, MissingDependencyError, Optional, type Provider, type RegisterOptions, Repository, type Scope, Service, type ServiceOptions, type Token, TypeInferenceError, type ValueProvider, container, createContainer, delay, getConfigPrefix, getOptionalParams, getServiceScope, getServiceType, hasServiceMetadata, inject, isParameterOptional, markInjectable };