sandly 0.0.2

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,1255 @@
1
+ //#region src/tag.d.ts
2
+ /**
3
+ * Internal symbol used to identify tagged types within the dependency injection system.
4
+ * This symbol is used as a property key to attach metadata to both value tags and class tags.
5
+ * @internal
6
+ */
7
+ declare const TagId: "__tag_id__";
8
+ /**
9
+ * Type representing a value-based dependency tag.
10
+ *
11
+ * Value tags are used to represent non-class dependencies like configuration objects,
12
+ * strings, numbers, or any other values. They use phantom types to maintain type safety
13
+ * while being distinguishable at runtime through their unique identifiers.
14
+ *
15
+ * @template T - The type of the value this tag represents
16
+ * @template Id - The unique identifier for this tag (string or symbol)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Creates a value tag for string configuration
21
+ * const ApiKeyTag: ValueTag<string, 'apiKey'> = Tag.of('apiKey')<string>();
22
+ *
23
+ * // Register in container
24
+ * container.register(ApiKeyTag, () => 'my-secret-key');
25
+ * ```
26
+ */
27
+ type ValueTag<T, Id extends string | symbol> = Readonly<{
28
+ readonly [TagId]: Id;
29
+ /** @internal Phantom type to carry T */
30
+ readonly __type: T;
31
+ }>;
32
+ /**
33
+ * Type representing a class-based dependency tag.
34
+ *
35
+ * Tagged classes are created by Tag.Class() and serve as both the dependency identifier
36
+ * and the constructor for the service. They extend regular classes with tag metadata
37
+ * that the DI system uses for identification and type safety.
38
+ *
39
+ * @template T - The type of instances created by this tagged class
40
+ * @template Id - The unique identifier for this tag (string or symbol)
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // Creates a tagged class
45
+ * class UserService extends Tag.Class('UserService') {
46
+ * getUsers() { return []; }
47
+ * }
48
+ *
49
+ * // Register in container
50
+ * container.register(UserService, () => new UserService());
51
+ * ```
52
+ *
53
+ * @internal - Users should use Tag.Class() instead of working with this type directly
54
+ */
55
+ type TaggedClass<T, Id extends string | symbol> = {
56
+ new (...args: any[]): T & {
57
+ readonly [TagId]: Id;
58
+ };
59
+ readonly [TagId]: Id;
60
+ };
61
+ /**
62
+ * Type representing a class-based dependency tag.
63
+ *
64
+ * This type is a shortcut for TaggedClass<T, string | symbol>.
65
+ *
66
+ * @template T - The type of instances created by this tagged class
67
+ * @returns A tagged class with a string or symbol identifier
68
+ *
69
+ * @internal - Users should use Tag.Class() instead of working with this type directly
70
+ */
71
+ type ClassTag<T> = TaggedClass<T, string | symbol>;
72
+ /**
73
+ * Union type representing any valid dependency tag in the system.
74
+ *
75
+ * A tag can be either a value tag (for non-class dependencies) or a tagged class
76
+ * (for service classes). This type is used throughout the DI system to constrain
77
+ * what can be used as a dependency identifier.
78
+ *
79
+ * @example Value tag
80
+ * ```typescript
81
+ * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
82
+ * // ConfigTag satisfies AnyTag
83
+ * ```
84
+ *
85
+ * @example Class tag
86
+ * ```typescript
87
+ * class DatabaseService extends Tag.Class('DatabaseService') {}
88
+ * // DatabaseService satisfies AnyTag
89
+ * ```
90
+ */
91
+ type AnyTag = ValueTag<any, string | symbol> | TaggedClass<any, string | symbol>;
92
+ /**
93
+ * Utility object containing factory functions for creating dependency tags.
94
+ *
95
+ * The Tag object provides the primary API for creating both value tags and class tags
96
+ * used throughout the dependency injection system. It's the main entry point for
97
+ * defining dependencies in a type-safe way.
98
+ */
99
+ declare const Tag: {
100
+ /**
101
+ * Creates a value tag factory for dependencies that are not classes.
102
+ *
103
+ * This method returns a factory function that, when called with a type parameter,
104
+ * creates a value tag for that type. The tag has a string or symbol-based identifier
105
+ * that must be unique within your application.
106
+ *
107
+ * @template Id - The string or symbol identifier for this tag (must be unique)
108
+ * @param id - The unique string or symbol identifier for this tag
109
+ * @returns A factory function that creates value tags for the specified type
110
+ *
111
+ * @example Basic usage with strings
112
+ * ```typescript
113
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
114
+ * const ConfigTag = Tag.of('config')<{ dbUrl: string; port: number }>();
115
+ *
116
+ * container
117
+ * .register(ApiKeyTag, () => process.env.API_KEY!)
118
+ * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost', port: 5432 }));
119
+ * ```
120
+ *
121
+ * @example Usage with symbols
122
+ * ```typescript
123
+ * const DB_CONFIG_SYM = Symbol('database-config');
124
+ * const ConfigTag = Tag.of(DB_CONFIG_SYM)<DatabaseConfig>();
125
+ *
126
+ * container.register(ConfigTag, () => ({ host: 'localhost', port: 5432 }));
127
+ * ```
128
+ *
129
+ * @example Primitive values
130
+ * ```typescript
131
+ * const PortTag = Tag.of('port')<number>();
132
+ * const EnabledTag = Tag.of('enabled')<boolean>();
133
+ *
134
+ * container
135
+ * .register(PortTag, () => 3000)
136
+ * .register(EnabledTag, () => true);
137
+ * ```
138
+ *
139
+ * @example Complex objects
140
+ * ```typescript
141
+ * interface DatabaseConfig {
142
+ * host: string;
143
+ * port: number;
144
+ * database: string;
145
+ * }
146
+ *
147
+ * const DbConfigTag = Tag.of('database-config')<DatabaseConfig>();
148
+ * container.register(DbConfigTag, () => ({
149
+ * host: 'localhost',
150
+ * port: 5432,
151
+ * database: 'myapp'
152
+ * }));
153
+ * ```
154
+ */
155
+ of: <Id extends string | symbol>(id: Id) => <T>() => ValueTag<T, Id>;
156
+ /**
157
+ * Creates an anonymous value tag with a unique symbol identifier.
158
+ *
159
+ * This is useful when you want a tag that's guaranteed to be unique but don't
160
+ * need a human-readable identifier. Each call creates a new unique symbol,
161
+ * making it impossible to accidentally create duplicate tags.
162
+ *
163
+ * @template T - The type that this tag represents
164
+ * @returns A value tag with a unique symbol identifier
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * interface InternalConfig {
169
+ * secretKey: string;
170
+ * }
171
+ *
172
+ * const InternalConfigTag = Tag.for<InternalConfig>();
173
+ *
174
+ * // This tag is guaranteed to be unique - no chance of conflicts
175
+ * container.register(InternalConfigTag, () => ({
176
+ * secretKey: generateSecret()
177
+ * }));
178
+ * ```
179
+ *
180
+ * @example Multiple anonymous tags
181
+ * ```typescript
182
+ * const ConfigA = Tag.for<string>();
183
+ * const ConfigB = Tag.for<string>();
184
+ *
185
+ * // These are different tags even though they have the same type
186
+ * console.log(ConfigA === ConfigB); // false
187
+ * ```
188
+ */
189
+ for: <T>() => ValueTag<T, symbol>;
190
+ /**
191
+ * Creates a base class that can be extended to create service classes with dependency tags.
192
+ *
193
+ * This is the primary way to define service classes in the dependency injection system.
194
+ * Classes that extend the returned base class become both the dependency identifier
195
+ * and the implementation, providing type safety and clear semantics.
196
+ *
197
+ * @template Id - The unique identifier for this service class
198
+ * @param id - The unique identifier (string or symbol) for this service
199
+ * @returns A base class that can be extended to create tagged service classes
200
+ *
201
+ * @example Basic service class
202
+ * ```typescript
203
+ * class UserService extends Tag.Class('UserService') {
204
+ * getUsers() {
205
+ * return ['alice', 'bob'];
206
+ * }
207
+ * }
208
+ *
209
+ * container.register(UserService, () => new UserService());
210
+ * ```
211
+ *
212
+ * @example Service with dependencies
213
+ * ```typescript
214
+ * class DatabaseService extends Tag.Class('DatabaseService') {
215
+ * query(sql: string) { return []; }
216
+ * }
217
+ *
218
+ * class UserRepository extends Tag.Class('UserRepository') {
219
+ * constructor(private db: DatabaseService) {
220
+ * super();
221
+ * }
222
+ *
223
+ * findUser(id: string) {
224
+ * return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
225
+ * }
226
+ * }
227
+ *
228
+ * container
229
+ * .register(DatabaseService, () => new DatabaseService())
230
+ * .register(UserRepository, async (c) =>
231
+ * new UserRepository(await c.get(DatabaseService))
232
+ * );
233
+ * ```
234
+ *
235
+ * @example With symbol identifiers
236
+ * ```typescript
237
+ * const SERVICE_ID = Symbol('InternalService');
238
+ *
239
+ * class InternalService extends Tag.Class(SERVICE_ID) {
240
+ * doInternalWork() { return 'work'; }
241
+ * }
242
+ * ```
243
+ */
244
+ Class: <Id extends string | symbol>(id: Id) => TaggedClass<{
245
+ /** @internal */
246
+ readonly __type: unknown;
247
+ readonly __tag_id__: Id;
248
+ }, Id>;
249
+ /**
250
+ * Extracts the string representation of a tag's identifier.
251
+ *
252
+ * This utility function returns a human-readable string for any tag's identifier,
253
+ * whether it's a string-based or symbol-based tag. Primarily used internally
254
+ * for error messages and debugging.
255
+ *
256
+ * @param tag - Any valid dependency tag (value tag or class tag)
257
+ * @returns String representation of the tag's identifier
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * const StringTag = Tag.of('myString')<string>();
262
+ * const SymbolTag = Tag.for<number>();
263
+ * class ServiceClass extends Tag.Class('MyService') {}
264
+ *
265
+ * console.log(Tag.id(StringTag)); // "myString"
266
+ * console.log(Tag.id(SymbolTag)); // "Symbol()"
267
+ * console.log(Tag.id(ServiceClass)); // "MyService"
268
+ * ```
269
+ *
270
+ * @internal - Primarily for internal use in error messages and debugging
271
+ */
272
+ id: (tag: AnyTag) => string;
273
+ };
274
+ /**
275
+ * Utility type that extracts the service type from any dependency tag.
276
+ *
277
+ * This type is essential for type inference throughout the DI system, allowing
278
+ * the container and layers to automatically determine what type of service
279
+ * a given tag represents without manual type annotations.
280
+ *
281
+ * @template T - Any dependency tag (ValueTag or TaggedClass)
282
+ * @returns The service type that the tag represents
283
+ *
284
+ * @example With value tags
285
+ * ```typescript
286
+ * const StringTag = Tag.of('myString')<string>();
287
+ * const ConfigTag = Tag.of('config')<{ apiKey: string }>();
288
+ *
289
+ * type StringService = ServiceOf<typeof StringTag>; // string
290
+ * type ConfigService = ServiceOf<typeof ConfigTag>; // { apiKey: string }
291
+ * ```
292
+ *
293
+ * @example With class tags
294
+ * ```typescript
295
+ * class UserService extends Tag.Class('UserService') {
296
+ * getUsers() { return []; }
297
+ * }
298
+ *
299
+ * type UserServiceType = ServiceOf<typeof UserService>; // UserService
300
+ * ```
301
+ *
302
+ * @example Used in container methods
303
+ * ```typescript
304
+ * // The container uses ServiceOf internally for type inference
305
+ * container.register(StringTag, () => 'hello'); // Factory must return string
306
+ * container.register(UserService, () => new UserService()); // Factory must return UserService
307
+ *
308
+ * const str: string = await container.get(StringTag); // Automatically typed as string
309
+ * const user: UserService = await container.get(UserService); // Automatically typed as UserService
310
+ * ```
311
+ */
312
+ type ServiceOf<T> = T extends ValueTag<infer S, string | symbol> ? S : T extends ClassTag<infer S> ? S : never;
313
+ //#endregion
314
+ //#region src/types.d.ts
315
+ type PromiseOrValue<T> = T | Promise<T>;
316
+ /**
317
+ * Unique symbol used to store the original ValueTag in Inject<T> types.
318
+ * This prevents property name collisions while allowing type-level extraction.
319
+ */
320
+ declare const InjectSource: unique symbol;
321
+ /**
322
+ * Generic interface representing a class constructor.
323
+ *
324
+ * This is primarily used internally for type constraints and validations.
325
+ * Most users should use Tag.Class() instead of working with raw constructors.
326
+ *
327
+ * @template T - The type that the constructor creates
328
+ * @internal
329
+ */
330
+
331
+ /**
332
+ * Type representing a factory function used to create dependency instances.
333
+ *
334
+ * Factory functions are the core mechanism for dependency creation in the DI system.
335
+ * They receive a dependency container and can use it to resolve other dependencies
336
+ * that the service being created needs.
337
+ *
338
+ * The factory can be either synchronous (returning T directly) or asynchronous
339
+ * (returning Promise<T>). The container handles both cases transparently.
340
+ *
341
+ * @template T - The type of the service instance being created
342
+ * @template TReg - Union type of all dependencies available in the container
343
+ *
344
+ * @example Synchronous factory
345
+ * ```typescript
346
+ * const factory: Factory<DatabaseService, never> = (container) => {
347
+ * return new DatabaseService('sqlite://memory');
348
+ * };
349
+ * ```
350
+ *
351
+ * @example Asynchronous factory with dependencies
352
+ * ```typescript
353
+ * const factory: Factory<UserService, typeof ConfigTag | typeof DatabaseService> = async (container) => {
354
+ * const [config, db] = await Promise.all([
355
+ * container.get(ConfigTag),
356
+ * container.get(DatabaseService)
357
+ * ]);
358
+ * return new UserService(config, db);
359
+ * };
360
+ * ```
361
+ */
362
+ type Factory<T, TReg extends AnyTag, TScope extends Scope> = (container: IContainer<TReg, TScope>) => PromiseOrValue<T>;
363
+ /**
364
+ * Helper type for injecting ValueTag dependencies in constructor parameters.
365
+ * This allows clean specification of ValueTag dependencies while preserving
366
+ * the original tag information for dependency inference.
367
+ *
368
+ * The phantom property is optional to allow normal runtime values to be assignable.
369
+ *
370
+ * @template T - A ValueTag type
371
+ * @returns The value type with optional phantom tag metadata for dependency inference
372
+ *
373
+ * @example
374
+ * ```typescript
375
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
376
+ *
377
+ * class UserService extends Tag.Class('UserService') {
378
+ * constructor(
379
+ * private db: DatabaseService, // ClassTag - works automatically
380
+ * private apiKey: Inject<typeof ApiKeyTag> // ValueTag - type is string, tag preserved
381
+ * ) {
382
+ * super();
383
+ * }
384
+ * }
385
+ * ```
386
+ */
387
+ type Inject<T extends ValueTag<unknown, string | symbol>> = T extends ValueTag<infer V, string | symbol> ? V & {
388
+ readonly [InjectSource]?: T;
389
+ } : never;
390
+ /**
391
+ * Helper type to extract the original ValueTag from an Inject<T> type.
392
+ * Since InjectSource is optional, we need to check for both presence and absence.
393
+ * @internal
394
+ */
395
+ type ExtractInjectTag<T> = T extends {
396
+ readonly [InjectSource]?: infer U;
397
+ } ? U : never;
398
+ /**
399
+ * Type representing a finalizer function used to clean up dependency instances.
400
+ *
401
+ * Finalizers are optional cleanup functions that are called when the container
402
+ * is destroyed via `container.destroy()`. They receive the created instance
403
+ * and should perform any necessary cleanup (closing connections, releasing resources, etc.).
404
+ *
405
+ * Like factories, finalizers can be either synchronous or asynchronous.
406
+ * All finalizers are called concurrently during container destruction.
407
+ *
408
+ * @template T - The type of the service instance being finalized
409
+ *
410
+ * @example Synchronous finalizer
411
+ * ```typescript
412
+ * const finalizer: Finalizer<FileHandle> = (fileHandle) => {
413
+ * fileHandle.close();
414
+ * };
415
+ * ```
416
+ *
417
+ * @example Asynchronous finalizer
418
+ * ```typescript
419
+ * const finalizer: Finalizer<DatabaseConnection> = async (connection) => {
420
+ * await connection.disconnect();
421
+ * };
422
+ * ```
423
+ *
424
+ * @example Resilient finalizer
425
+ * ```typescript
426
+ * const finalizer: Finalizer<HttpServer> = async (server) => {
427
+ * try {
428
+ * await server.close();
429
+ * } catch (error) {
430
+ * if (!error.message.includes('already closed')) {
431
+ * throw error; // Re-throw unexpected errors
432
+ * }
433
+ * // Ignore "already closed" errors
434
+ * }
435
+ * };
436
+ * ```
437
+ */
438
+ type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
439
+ type Scope = string | symbol;
440
+ declare const DefaultScope: unique symbol;
441
+ type DefaultScope = typeof DefaultScope;
442
+ //#endregion
443
+ //#region src/container.d.ts
444
+ type DependencyLifecycle<T extends AnyTag, TReg extends AnyTag, TScope extends Scope> = {
445
+ factory: Factory<ServiceOf<T>, TReg, TScope>;
446
+ finalizer: Finalizer<ServiceOf<T>>;
447
+ };
448
+ interface IContainer<TReg extends AnyTag, TScope extends Scope = DefaultScope> {
449
+ register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, TScope> | DependencyLifecycle<T, TReg, TScope>, scope?: TScope): IContainer<TReg | T, TScope>;
450
+ has(tag: AnyTag): boolean;
451
+ get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
452
+ destroy(): Promise<void>;
453
+ }
454
+ /**
455
+ * A type-safe dependency injection container that manages service instantiation,
456
+ * caching, and lifecycle management with support for async dependencies and
457
+ * circular dependency detection.
458
+ *
459
+ * The container maintains complete type safety by tracking registered dependencies
460
+ * at the type level, ensuring that only registered dependencies can be retrieved
461
+ * and preventing runtime errors.
462
+ *
463
+ * @template TReg - Union type of all registered dependency tags in this container
464
+ *
465
+ * @example Basic usage with class tags
466
+ * ```typescript
467
+ * import { container, Tag } from 'sandl';
468
+ *
469
+ * class DatabaseService extends Tag.Class('DatabaseService') {
470
+ * query() { return 'data'; }
471
+ * }
472
+ *
473
+ * class UserService extends Tag.Class('UserService') {
474
+ * constructor(private db: DatabaseService) {}
475
+ * getUser() { return this.db.query(); }
476
+ * }
477
+ *
478
+ * const c = container()
479
+ * .register(DatabaseService, () => new DatabaseService())
480
+ * .register(UserService, async (container) =>
481
+ * new UserService(await container.get(DatabaseService))
482
+ * );
483
+ *
484
+ * const userService = await c.get(UserService);
485
+ * ```
486
+ *
487
+ * @example Usage with value tags
488
+ * ```typescript
489
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
490
+ * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
491
+ *
492
+ * const c = container()
493
+ * .register(ApiKeyTag, () => process.env.API_KEY!)
494
+ * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
495
+ *
496
+ * const apiKey = await c.get(ApiKeyTag);
497
+ * const config = await c.get(ConfigTag);
498
+ * ```
499
+ *
500
+ * @example With finalizers for cleanup
501
+ * ```typescript
502
+ * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
503
+ * async connect() { return; }
504
+ * async disconnect() { return; }
505
+ * }
506
+ *
507
+ * const c = container().register(
508
+ * DatabaseConnection,
509
+ * async () => {
510
+ * const conn = new DatabaseConnection();
511
+ * await conn.connect();
512
+ * return conn;
513
+ * },
514
+ * async (conn) => conn.disconnect() // Finalizer for cleanup
515
+ * );
516
+ *
517
+ * // Later...
518
+ * await c.destroy(); // Calls all finalizers
519
+ * ```
520
+ */
521
+ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
522
+ /**
523
+ * Cache of instantiated dependencies as promises.
524
+ * Ensures singleton behavior and supports concurrent access.
525
+ * @internal
526
+ */
527
+ private readonly cache;
528
+ /**
529
+ * Factory functions for creating dependency instances.
530
+ * @internal
531
+ */
532
+ private readonly factories;
533
+ /**
534
+ * Finalizer functions for cleaning up dependencies when the container is destroyed.
535
+ * @internal
536
+ */
537
+ private readonly finalizers;
538
+ /**
539
+ * Registers a dependency in the container with a factory function and optional finalizer.
540
+ *
541
+ * The factory function receives the current container instance and must return the
542
+ * service instance (or a Promise of it). The container tracks the registration at
543
+ * the type level, ensuring type safety for subsequent `.get()` calls.
544
+ *
545
+ * @template T - The dependency tag being registered
546
+ * @param tag - The dependency tag (class or value tag)
547
+ * @param factory - Function that creates the service instance, receives container for dependency injection
548
+ * @param finalizer - Optional cleanup function called when container is destroyed
549
+ * @returns A new container instance with the dependency registered
550
+ * @throws {DependencyContainerError} If the dependency is already registered
551
+ *
552
+ * @example Registering a simple service
553
+ * ```typescript
554
+ * class LoggerService extends Tag.Class('LoggerService') {
555
+ * log(message: string) { console.log(message); }
556
+ * }
557
+ *
558
+ * const c = container().register(
559
+ * LoggerService,
560
+ * () => new LoggerService()
561
+ * );
562
+ * ```
563
+ *
564
+ * @example Registering with dependencies
565
+ * ```typescript
566
+ * class UserService extends Tag.Class('UserService') {
567
+ * constructor(private db: DatabaseService, private logger: LoggerService) {}
568
+ * }
569
+ *
570
+ * const c = container()
571
+ * .register(DatabaseService, () => new DatabaseService())
572
+ * .register(LoggerService, () => new LoggerService())
573
+ * .register(UserService, async (container) =>
574
+ * new UserService(
575
+ * await container.get(DatabaseService),
576
+ * await container.get(LoggerService)
577
+ * )
578
+ * );
579
+ * ```
580
+ *
581
+ * @example Using value tags
582
+ * ```typescript
583
+ * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
584
+ *
585
+ * const c = container().register(
586
+ * ConfigTag,
587
+ * () => ({ apiUrl: 'https://api.example.com' })
588
+ * );
589
+ * ```
590
+ *
591
+ * @example With finalizer for cleanup
592
+ * ```typescript
593
+ * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
594
+ * async connect() { return; }
595
+ * async close() { return; }
596
+ * }
597
+ *
598
+ * const c = container().register(
599
+ * DatabaseConnection,
600
+ * async () => {
601
+ * const conn = new DatabaseConnection();
602
+ * await conn.connect();
603
+ * return conn;
604
+ * },
605
+ * (conn) => conn.close() // Called during container.destroy()
606
+ * );
607
+ * ```
608
+ */
609
+ register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, DefaultScope> | DependencyLifecycle<T, TReg, DefaultScope>): IContainer<TReg | T>;
610
+ /**
611
+ * Checks if a dependency has been instantiated (cached) in the container.
612
+ *
613
+ * Note: This returns `true` only after the dependency has been created via `.get()`.
614
+ * A registered but not-yet-instantiated dependency will return `false`.
615
+ *
616
+ * @param tag - The dependency tag to check
617
+ * @returns `true` if the dependency has been instantiated and cached, `false` otherwise
618
+ *
619
+ * @example
620
+ * ```typescript
621
+ * const c = container().register(DatabaseService, () => new DatabaseService());
622
+ *
623
+ * console.log(c.has(DatabaseService)); // false - not instantiated yet
624
+ *
625
+ * await c.get(DatabaseService);
626
+ * console.log(c.has(DatabaseService)); // true - now instantiated and cached
627
+ * ```
628
+ */
629
+ has(tag: AnyTag): boolean;
630
+ /**
631
+ * Retrieves a dependency instance from the container, creating it if necessary.
632
+ *
633
+ * This method ensures singleton behavior - each dependency is created only once
634
+ * and cached for subsequent calls. The method is async-safe and handles concurrent
635
+ * requests for the same dependency correctly.
636
+ *
637
+ * The method performs circular dependency detection using AsyncLocalStorage to track
638
+ * the resolution chain across async boundaries.
639
+ *
640
+ * @template T - The dependency tag type (must be registered in this container)
641
+ * @param tag - The dependency tag to retrieve
642
+ * @returns Promise resolving to the service instance
643
+ * @throws {UnknownDependencyError} If the dependency is not registered
644
+ * @throws {CircularDependencyError} If a circular dependency is detected
645
+ * @throws {DependencyCreationError} If the factory function throws an error
646
+ *
647
+ * @example Basic usage
648
+ * ```typescript
649
+ * const c = container()
650
+ * .register(DatabaseService, () => new DatabaseService());
651
+ *
652
+ * const db = await c.get(DatabaseService);
653
+ * db.query('SELECT * FROM users');
654
+ * ```
655
+ *
656
+ * @example Concurrent access (singleton behavior)
657
+ * ```typescript
658
+ * // All three calls will receive the same instance
659
+ * const [db1, db2, db3] = await Promise.all([
660
+ * c.get(DatabaseService),
661
+ * c.get(DatabaseService),
662
+ * c.get(DatabaseService)
663
+ * ]);
664
+ *
665
+ * console.log(db1 === db2 === db3); // true
666
+ * ```
667
+ *
668
+ * @example Dependency injection in factories
669
+ * ```typescript
670
+ * const c = container()
671
+ * .register(DatabaseService, () => new DatabaseService())
672
+ * .register(UserService, async (container) => {
673
+ * const db = await container.get(DatabaseService);
674
+ * return new UserService(db);
675
+ * });
676
+ *
677
+ * const userService = await c.get(UserService);
678
+ * ```
679
+ */
680
+ get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
681
+ /**
682
+ * Destroys all instantiated dependencies by calling their finalizers, then clears the instance cache.
683
+ *
684
+ * **Important: This method preserves the container structure (factories and finalizers) for reuse.**
685
+ * The container can be used again after destruction to create fresh instances following the same
686
+ * dependency patterns.
687
+ *
688
+ * All finalizers for instantiated dependencies are called concurrently using Promise.allSettled()
689
+ * for maximum cleanup performance.
690
+ * If any finalizers fail, all errors are collected and a DependencyContainerFinalizationError
691
+ * is thrown containing details of all failures.
692
+ *
693
+ * **Finalizer Concurrency:** Finalizers run concurrently, so there are no ordering guarantees.
694
+ * Services should be designed to handle cleanup gracefully regardless of the order in which their
695
+ * dependencies are cleaned up.
696
+ *
697
+ * @returns Promise that resolves when all cleanup is complete
698
+ * @throws {DependencyContainerFinalizationError} If any finalizers fail during cleanup
699
+ *
700
+ * @example Basic cleanup and reuse
701
+ * ```typescript
702
+ * const c = container()
703
+ * .register(DatabaseConnection,
704
+ * async () => {
705
+ * const conn = new DatabaseConnection();
706
+ * await conn.connect();
707
+ * return conn;
708
+ * },
709
+ * (conn) => conn.disconnect() // Finalizer
710
+ * );
711
+ *
712
+ * // First use cycle
713
+ * const db1 = await c.get(DatabaseConnection);
714
+ * await c.destroy(); // Calls conn.disconnect(), clears cache
715
+ *
716
+ * // Container can be reused - creates fresh instances
717
+ * const db2 = await c.get(DatabaseConnection); // New connection
718
+ * expect(db2).not.toBe(db1); // Different instances
719
+ * ```
720
+ *
721
+ * @example Multiple destroy/reuse cycles
722
+ * ```typescript
723
+ * const c = container().register(UserService, () => new UserService());
724
+ *
725
+ * for (let i = 0; i < 5; i++) {
726
+ * const user = await c.get(UserService);
727
+ * // ... use service ...
728
+ * await c.destroy(); // Clean up, ready for next cycle
729
+ * }
730
+ * ```
731
+ *
732
+ * @example Handling cleanup errors
733
+ * ```typescript
734
+ * try {
735
+ * await container.destroy();
736
+ * } catch (error) {
737
+ * if (error instanceof DependencyContainerFinalizationError) {
738
+ * console.error('Some dependencies failed to clean up:', error.detail.errors);
739
+ * }
740
+ * }
741
+ * // Container is still reusable even after finalizer errors
742
+ * ```
743
+ */
744
+ destroy(): Promise<void>;
745
+ }
746
+ declare class ScopedContainer<TReg extends AnyTag, TScope extends Scope> implements IContainer<TReg, TScope> {
747
+ private readonly scope;
748
+ private readonly parent;
749
+ private readonly children;
750
+ /**
751
+ * Cache of instantiated dependencies as promises for this scope.
752
+ * @internal
753
+ */
754
+ private readonly cache;
755
+ /**
756
+ * Factory functions for creating dependency instances in this scope.
757
+ * @internal
758
+ */
759
+ private readonly factories;
760
+ /**
761
+ * Finalizer functions for cleaning up dependencies when this scope is destroyed.
762
+ * @internal
763
+ */
764
+ private readonly finalizers;
765
+ constructor(parent: IContainer<any, any> | null, scope: TScope);
766
+ /**
767
+ * Registers a dependency in the specified scope within this container's scope chain.
768
+ *
769
+ * If no scope is specified, registers in the current (leaf) scope. If a scope is specified,
770
+ * delegates to the parent container if the target scope doesn't match the current scope.
771
+ *
772
+ * This allows registering dependencies at different scope levels from any container
773
+ * in the scope chain, providing flexibility for dependency organization.
774
+ *
775
+ * @param tag - The dependency tag to register
776
+ * @param factory - Factory function to create the dependency
777
+ * @param finalizer - Optional cleanup function
778
+ * @param scope - Target scope for registration (defaults to current scope)
779
+ * @returns This container with updated type information
780
+ *
781
+ * @example Registering in different scopes
782
+ * ```typescript
783
+ * const runtime = scopedContainer('runtime');
784
+ * const request = runtime.child('request');
785
+ *
786
+ * // Register in current (request) scope
787
+ * request.register(RequestService, () => new RequestService());
788
+ *
789
+ * // Register in runtime scope from request container - delegates to parent
790
+ * request.register(DatabaseService, () => new DatabaseService(), undefined, 'runtime');
791
+ * ```
792
+ */
793
+ register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, TScope> | DependencyLifecycle<T, TReg, TScope>, scope?: TScope): ScopedContainer<TReg | T, TScope>;
794
+ /**
795
+ * Checks if a dependency has been instantiated in this scope or any parent scope.
796
+ *
797
+ * This method checks the current scope first, then walks up the parent chain.
798
+ * Returns true only if the dependency has been created and cached somewhere in the scope hierarchy.
799
+ */
800
+ has(tag: AnyTag): boolean;
801
+ /**
802
+ * Retrieves a dependency instance, resolving from the current scope or parent scopes.
803
+ *
804
+ * Resolution strategy:
805
+ * 1. Check cache in current scope
806
+ * 2. Check if factory exists in current scope - if so, create instance here
807
+ * 3. Otherwise, delegate to parent scope
808
+ * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
809
+ */
810
+ get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
811
+ /**
812
+ * Destroys this scoped container and its children, preserving the container structure for reuse.
813
+ *
814
+ * This method ensures proper cleanup order while maintaining reusability:
815
+ * 1. Destroys all child scopes first (they may depend on parent scope dependencies)
816
+ * 2. Then calls finalizers for dependencies created in this scope
817
+ * 3. Clears only instance caches - preserves factories, finalizers, and child structure
818
+ *
819
+ * Child destruction happens first to ensure dependencies don't get cleaned up
820
+ * before their dependents.
821
+ */
822
+ destroy(): Promise<void>;
823
+ /**
824
+ * Creates a child scoped container.
825
+ *
826
+ * Child containers inherit access to parent dependencies but maintain
827
+ * their own scope for new registrations and instance caching.
828
+ */
829
+ child<TChildScope extends Scope>(scope: TChildScope): ScopedContainer<TReg, TScope | TChildScope>;
830
+ }
831
+ /**
832
+ * Creates a new empty dependency injection container.
833
+ *
834
+ * This is a convenience factory function that creates a new DependencyContainer instance.
835
+ * The returned container starts with no registered dependencies and the type parameter
836
+ * defaults to `never`, indicating no dependencies are available for retrieval yet.
837
+ *
838
+ * @returns A new empty DependencyContainer instance
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * import { container, Tag } from 'sandl';
843
+ *
844
+ * class DatabaseService extends Tag.Class('DatabaseService') {}
845
+ * class UserService extends Tag.Class('UserService') {}
846
+ *
847
+ * const c = container()
848
+ * .register(DatabaseService, () => new DatabaseService())
849
+ * .register(UserService, async (container) =>
850
+ * new UserService(await container.get(DatabaseService))
851
+ * );
852
+ *
853
+ * const userService = await c.get(UserService);
854
+ * ```
855
+ */
856
+ declare function container(): Container<never>;
857
+ declare function scopedContainer<TScope extends Scope>(scope: TScope): ScopedContainer<never, TScope>;
858
+ //#endregion
859
+ //#region src/layer.d.ts
860
+ /**
861
+ * A dependency layer represents a reusable, composable unit of dependency registrations.
862
+ * Layers allow you to organize your dependency injection setup into logical groups
863
+ * that can be combined and reused across different contexts.
864
+ *
865
+ * @template TRequires - The union of tags this layer requires to be satisfied by other layers
866
+ * @template TProvides - The union of tags this layer provides/registers
867
+ *
868
+ * @example Basic layer usage
869
+ * ```typescript
870
+ * import { layer, Tag, container } from 'sandl';
871
+ *
872
+ * class DatabaseService extends Tag.Class('DatabaseService') {
873
+ * query() { return 'data'; }
874
+ * }
875
+ *
876
+ * // Create a layer that provides DatabaseService
877
+ * const databaseLayer = layer<never, typeof DatabaseService>((container) =>
878
+ * container.register(DatabaseService, () => new DatabaseService())
879
+ * );
880
+ *
881
+ * // Apply the layer to a container
882
+ * const c = container();
883
+ * const finalContainer = databaseLayer().register(c);
884
+ *
885
+ * const db = await finalContainer.get(DatabaseService);
886
+ * ```
887
+ *
888
+ * @example Layer composition
889
+ * ```typescript
890
+ * // Layer that requires DatabaseService and provides UserService
891
+ * const userLayer = layer<typeof DatabaseService, typeof UserService>((container) =>
892
+ * container.register(UserService, async (c) =>
893
+ * new UserService(await c.get(DatabaseService))
894
+ * )
895
+ * );
896
+ *
897
+ * // Compose layers: database layer provides what user layer needs
898
+ * const appLayer = databaseLayer().to(userLayer());
899
+ * ```
900
+ */
901
+ interface Layer<TRequires extends AnyTag = never, TProvides extends AnyTag = never> {
902
+ /**
903
+ * Applies this layer's registrations to the given container.
904
+ *
905
+ * @param container - The container to register dependencies into
906
+ * @returns A new container with this layer's dependencies registered
907
+ *
908
+ * @example
909
+ * ```typescript
910
+ * const container = container();
911
+ * const updatedContainer = myLayer.register(container);
912
+ * ```
913
+ */
914
+ register: <TScope extends Scope>(container: IContainer<TRequires, TScope>) => IContainer<TRequires | TProvides, TScope>;
915
+ /**
916
+ * Composes this layer with a target layer, creating a pipeline where this layer's
917
+ * provisions satisfy the target layer's requirements. This creates a dependency
918
+ * flow from source → target.
919
+ *
920
+ * Type-safe: The target layer's requirements must be satisfiable by this layer's
921
+ * provisions and any remaining external requirements.
922
+ *
923
+ * @template TTargetRequires - What the target layer requires
924
+ * @template TTargetProvides - What the target layer provides
925
+ * @param target - The layer to compose with
926
+ * @returns A new composed layer
927
+ *
928
+ * @example Simple composition
929
+ * ```typescript
930
+ * const configLayer = layer<never, typeof ConfigTag>(...);
931
+ * const dbLayer = layer<typeof ConfigTag, typeof DatabaseService>(...);
932
+ *
933
+ * // Config provides what database needs
934
+ * const infraLayer = configLayer().to(dbLayer());
935
+ * ```
936
+ *
937
+ * @example Multi-level composition
938
+ * ```typescript
939
+ * const appLayer = configLayer()
940
+ * .to(databaseLayer())
941
+ * .to(serviceLayer())
942
+ * .to(apiLayer());
943
+ * ```
944
+ */
945
+ to: <TTargetRequires extends AnyTag, TTargetProvides extends AnyTag>(target: Layer<TTargetRequires, TTargetProvides>) => Layer<TRequires | Exclude<TTargetRequires, TProvides>, TProvides | TTargetProvides>;
946
+ /**
947
+ * Merges this layer with another layer, combining their requirements and provisions.
948
+ * This is useful for combining independent layers that don't have a dependency
949
+ * relationship.
950
+ *
951
+ * @template TOtherRequires - What the other layer requires
952
+ * @template TOtherProvides - What the other layer provides
953
+ * @param other - The layer to merge with
954
+ * @returns A new merged layer requiring both layers' requirements and providing both layers' provisions
955
+ *
956
+ * @example Merging independent layers
957
+ * ```typescript
958
+ * const persistenceLayer = layer<never, typeof DatabaseService | typeof CacheService>(...);
959
+ * const loggingLayer = layer<never, typeof LoggerService>(...);
960
+ *
961
+ * // Combine infrastructure layers
962
+ * const infraLayer = persistenceLayer().and(loggingLayer());
963
+ * ```
964
+ *
965
+ * @example Building complex layer combinations
966
+ * ```typescript
967
+ * const appInfraLayer = persistenceLayer()
968
+ * .and(messagingLayer())
969
+ * .and(observabilityLayer());
970
+ * ```
971
+ */
972
+ and: <TOtherRequires extends AnyTag, TOtherProvides extends AnyTag>(other: Layer<TOtherRequires, TOtherProvides>) => Layer<TRequires | TOtherRequires, TProvides | TOtherProvides>;
973
+ }
974
+ /**
975
+ * A factory function for creating layers.
976
+ *
977
+ * @template TRequires - The union of tags this layer requires
978
+ * @template TProvides - The union of tags this layer provides
979
+ * @template TParams - Optional parameters that can be passed to configure the layer
980
+ */
981
+ type LayerFactory<TRequires extends AnyTag, TProvides extends AnyTag, TParams = undefined> = TParams extends undefined ? () => Layer<TRequires, TProvides> : (params: TParams) => Layer<TRequires, TProvides>;
982
+ /**
983
+ * Creates a new dependency layer that encapsulates a set of dependency registrations.
984
+ * Layers are the primary building blocks for organizing and composing dependency injection setups.
985
+ *
986
+ * @template TRequires - The union of dependency tags this layer requires from other layers or external setup
987
+ * @template TProvides - The union of dependency tags this layer registers/provides
988
+ * @template TParams - Optional parameters that can be passed to configure the layer
989
+ *
990
+ * @param register - Function that performs the dependency registrations. Receives a container and optional params.
991
+ * @returns A layer factory function. If TParams is undefined, returns a parameterless function. Otherwise returns a function that takes TParams.
992
+ *
993
+ * @example Simple layer without parameters
994
+ * ```typescript
995
+ * import { layer, Tag } from '@/di/layer.js';
996
+ *
997
+ * class DatabaseService extends Tag.Class('DatabaseService') {
998
+ * constructor(private url: string = 'sqlite://memory') {}
999
+ * query() { return 'data'; }
1000
+ * }
1001
+ *
1002
+ * // Layer that provides DatabaseService, requires nothing
1003
+ * const databaseLayer = layer<never, typeof DatabaseService>((container) =>
1004
+ * container.register(DatabaseService, () => new DatabaseService())
1005
+ * );
1006
+ *
1007
+ * // Usage
1008
+ * const dbLayerInstance = databaseLayer(); // No parameters needed
1009
+ * ```
1010
+ *
1011
+ * @example Layer with dependencies
1012
+ * ```typescript
1013
+ * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
1014
+ *
1015
+ * // Layer that requires ConfigTag and provides DatabaseService
1016
+ * const databaseLayer = layer<typeof ConfigTag, typeof DatabaseService>((container) =>
1017
+ * container.register(DatabaseService, async (c) => {
1018
+ * const config = await c.get(ConfigTag);
1019
+ * return new DatabaseService(config.dbUrl);
1020
+ * })
1021
+ * );
1022
+ * ```
1023
+ *
1024
+ * @example Parameterized layer
1025
+ * ```typescript
1026
+ * interface DatabaseConfig {
1027
+ * host: string;
1028
+ * port: number;
1029
+ * }
1030
+ *
1031
+ * // Layer that takes configuration parameters
1032
+ * const databaseLayer = layer<never, typeof DatabaseService, DatabaseConfig>(
1033
+ * (container, config) =>
1034
+ * container.register(DatabaseService, () => new DatabaseService(config))
1035
+ * );
1036
+ *
1037
+ * // Usage with parameters
1038
+ * const dbLayerInstance = databaseLayer({ host: 'localhost', port: 5432 });
1039
+ * ```
1040
+ *
1041
+ * @example Complex application layer structure
1042
+ * ```typescript
1043
+ * // Configuration layer
1044
+ * const configLayer = layer<never, typeof ConfigTag>((container) =>
1045
+ * container.register(ConfigTag, () => loadConfig())
1046
+ * );
1047
+ *
1048
+ * // Infrastructure layer (requires config)
1049
+ * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
1050
+ * (container) =>
1051
+ * container
1052
+ * .register(DatabaseService, async (c) => new DatabaseService(await c.get(ConfigTag)))
1053
+ * .register(CacheService, async (c) => new CacheService(await c.get(ConfigTag)))
1054
+ * );
1055
+ *
1056
+ * // Service layer (requires infrastructure)
1057
+ * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
1058
+ * (container) =>
1059
+ * container.register(UserService, async (c) =>
1060
+ * new UserService(await c.get(DatabaseService), await c.get(CacheService))
1061
+ * )
1062
+ * );
1063
+ *
1064
+ * // Compose the complete application
1065
+ * const appLayer = configLayer().to(infraLayer()).to(serviceLayer());
1066
+ * ```
1067
+ */
1068
+ declare function layer<TRequires extends AnyTag = never, TProvides extends AnyTag = never, TParams = undefined>(register: <TScope extends Scope>(container: IContainer<TRequires, TScope>, params: TParams) => IContainer<TRequires | TProvides, TScope>): LayerFactory<TRequires, TProvides, TParams>;
1069
+ /**
1070
+ * Helper type that extracts the union of all requirements from an array of layers.
1071
+ * Used by Layer.merge() to compute the correct requirement type for the merged layer.
1072
+ *
1073
+ * @internal
1074
+ */
1075
+ type UnionOfRequires<T extends readonly Layer<AnyTag, AnyTag>[]> = { [K in keyof T]: T[K] extends Layer<infer R, AnyTag> ? R : never }[number];
1076
+ /**
1077
+ * Helper type that extracts the union of all provisions from an array of layers.
1078
+ * Used by Layer.merge() to compute the correct provision type for the merged layer.
1079
+ *
1080
+ * @internal
1081
+ */
1082
+ type UnionOfProvides<T extends readonly Layer<AnyTag, AnyTag>[]> = { [K in keyof T]: T[K] extends Layer<AnyTag, infer P> ? P : never }[number];
1083
+ /**
1084
+ * Utility object containing helper functions for working with layers.
1085
+ */
1086
+ declare const Layer: {
1087
+ /**
1088
+ * Creates an empty layer that provides no dependencies and requires no dependencies.
1089
+ * This is useful as a base layer or for testing.
1090
+ *
1091
+ * @returns An empty layer that can be used as a starting point for layer composition
1092
+ *
1093
+ * @example
1094
+ * ```typescript
1095
+ * import { Layer } from 'sandl';
1096
+ *
1097
+ * const baseLayer = Layer.empty();
1098
+ * const appLayer = baseLayer
1099
+ * .and(configLayer())
1100
+ * .and(serviceLayer());
1101
+ * ```
1102
+ */
1103
+ empty(): Layer;
1104
+ /**
1105
+ * Merges multiple layers at once in a type-safe way.
1106
+ * This is equivalent to chaining `.and()` calls but more convenient for multiple layers.
1107
+ *
1108
+ * All layers are merged in order, combining their requirements and provisions.
1109
+ * The resulting layer requires the union of all input layer requirements and
1110
+ * provides the union of all input layer provisions.
1111
+ *
1112
+ * @template T - The tuple type of layers to merge
1113
+ * @param layers - At least 2 layers to merge together
1114
+ * @returns A new layer that combines all input layers
1115
+ *
1116
+ * @example Basic usage
1117
+ * ```typescript
1118
+ * import { Layer } from 'sandl';
1119
+ *
1120
+ * const infraLayer = Layer.merge(
1121
+ * databaseLayer(),
1122
+ * cacheLayer(),
1123
+ * loggingLayer()
1124
+ * );
1125
+ * ```
1126
+ *
1127
+ * @example Equivalent to chaining .and()
1128
+ * ```typescript
1129
+ * // These are equivalent:
1130
+ * const layer1 = Layer.merge(layerA(), layerB(), layerC());
1131
+ * const layer2 = layerA().and(layerB()).and(layerC());
1132
+ * ```
1133
+ *
1134
+ * @example Building infrastructure layers
1135
+ * ```typescript
1136
+ * const persistenceLayer = layer<never, typeof DatabaseService | typeof CacheService>(...);
1137
+ * const messagingLayer = layer<never, typeof MessageQueue>(...);
1138
+ * const observabilityLayer = layer<never, typeof Logger | typeof Metrics>(...);
1139
+ *
1140
+ * // Merge all infrastructure concerns into one layer
1141
+ * const infraLayer = Layer.merge(
1142
+ * persistenceLayer(),
1143
+ * messagingLayer(),
1144
+ * observabilityLayer()
1145
+ * );
1146
+ *
1147
+ * // Now infraLayer provides: DatabaseService | CacheService | MessageQueue | Logger | Metrics
1148
+ * ```
1149
+ */
1150
+ merge<T extends readonly [Layer<AnyTag, AnyTag>, Layer<AnyTag, AnyTag>, ...Layer<AnyTag, AnyTag>[]]>(...layers: T): Layer<UnionOfRequires<T>, UnionOfProvides<T>>;
1151
+ };
1152
+ //#endregion
1153
+ //#region src/service.d.ts
1154
+ /**
1155
+ * Extracts constructor parameter types from a TaggedClass.
1156
+ * Only parameters that extend AnyTag are considered as dependencies.
1157
+ */
1158
+ type ConstructorParams<T extends ClassTag<unknown>> = T extends (new (...args: infer A) => unknown) ? A : never;
1159
+ /**
1160
+ * Helper to convert a tagged instance type back to its constructor type.
1161
+ * This uses the fact that tagged classes have a specific structure with TagId property.
1162
+ */
1163
+ type InstanceToConstructorType<T> = T extends {
1164
+ readonly [TagId]: infer Id;
1165
+ } ? Id extends string | symbol ? TaggedClass<T, Id> : never : never;
1166
+ /**
1167
+ * Extracts constructor-typed dependencies from constructor parameters.
1168
+ * Converts instance types to their corresponding constructor types.
1169
+ * Handles both ClassTag dependencies (automatic) and ValueTag dependencies (via Inject helper).
1170
+ */
1171
+ type FilterTags<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1172
+ readonly [TagId]: string | symbol;
1173
+ } ? InstanceToConstructorType<T[K]> : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
1174
+ /**
1175
+ * Extracts the instance type that a TaggedClass constructor creates.
1176
+ */
1177
+
1178
+ /**
1179
+ * Extracts only the dependency tags from a constructor's parameters for ClassTag services,
1180
+ * or returns never for ValueTag services (which have no constructor dependencies).
1181
+ * This is used to determine what dependencies a service requires.
1182
+ */
1183
+ type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown> ? FilterTags<ConstructorParams<T>> extends AnyTag ? FilterTags<ConstructorParams<T>> : never : never;
1184
+ /**
1185
+ * Represents a service layer that can be created from any tag type.
1186
+ * For ClassTag services, dependencies are automatically inferred from constructor parameters.
1187
+ * For ValueTag services, there are no dependencies since they don't have constructors.
1188
+ */
1189
+ interface Service<T extends AnyTag> extends Layer<ServiceDependencies<T>, T> {
1190
+ /**
1191
+ * The tag that this service represents (ClassTag or ValueTag)
1192
+ */
1193
+ readonly serviceClass: T;
1194
+ }
1195
+ /**
1196
+ * Creates a service layer from any tag type (ClassTag or ValueTag) with optional parameters.
1197
+ *
1198
+ * For ClassTag services:
1199
+ * - Dependencies are automatically inferred from constructor parameters
1200
+ * - The factory function must handle dependency injection by resolving dependencies from the container
1201
+ *
1202
+ * For ValueTag services:
1203
+ * - No constructor dependencies are needed since they don't have constructors
1204
+ *
1205
+ * @template T - The tag representing the service (ClassTag or ValueTag)
1206
+ * @template TParams - Optional parameters for service configuration
1207
+ * @param serviceClass - The tag (ClassTag or ValueTag)
1208
+ * @param factory - Factory function for service instantiation with container and optional params
1209
+ * @returns A factory function that creates a service layer
1210
+ *
1211
+ * @example Simple service without dependencies
1212
+ * ```typescript
1213
+ * class LoggerService extends Tag.Class('LoggerService') {
1214
+ * log(message: string) { console.log(message); }
1215
+ * }
1216
+ *
1217
+ * const loggerService = service(LoggerService, () => new LoggerService());
1218
+ * ```
1219
+ *
1220
+ * @example Service with dependencies
1221
+ * ```typescript
1222
+ * class DatabaseService extends Tag.Class('DatabaseService') {
1223
+ * query() { return []; }
1224
+ * }
1225
+ *
1226
+ * class UserService extends Tag.Class('UserService') {
1227
+ * constructor(private db: DatabaseService) {
1228
+ * super();
1229
+ * }
1230
+ *
1231
+ * getUsers() { return this.db.query(); }
1232
+ * }
1233
+ *
1234
+ * const userService = service(UserService, async (container) =>
1235
+ * new UserService(await container.get(DatabaseService))
1236
+ * );
1237
+ * ```
1238
+ *
1239
+ * @example Service with configuration parameters
1240
+ * ```typescript
1241
+ * class DatabaseService extends Tag.Class('DatabaseService') {
1242
+ * constructor(private config: { dbUrl: string }) {
1243
+ * super();
1244
+ * }
1245
+ * }
1246
+ *
1247
+ * const dbService = service(
1248
+ * DatabaseService,
1249
+ * (container, params: { dbUrl: string }) => new DatabaseService(params)
1250
+ * );
1251
+ * ```
1252
+ */
1253
+ declare function service<T extends AnyTag, TParams = undefined>(serviceClass: T, factory: (container: IContainer<ServiceDependencies<T>>, params: TParams) => PromiseOrValue<ServiceOf<T>>): TParams extends undefined ? () => Service<T> : (params: TParams) => Service<T>;
1254
+ //#endregion
1255
+ export { type Inject, Layer, type Scope, type Service, type ServiceOf, Tag, type TaggedClass, type ValueTag, container, layer, scopedContainer, service };