sandly 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,50 +1,27 @@
1
1
  //#region src/tag.d.ts
2
2
  /**
3
- * Unique symbol used to store the original ValueTag in Inject<T> types.
4
- * This prevents property name collisions while allowing type-level extraction.
3
+ * Type representing a tag identifier (string or symbol).
4
+ * @internal
5
5
  */
6
- declare const InjectSource: unique symbol;
6
+ type TagId = string | symbol;
7
7
  /**
8
- * Helper type for injecting ValueTag dependencies in constructor parameters.
9
- * This allows clean specification of ValueTag dependencies while preserving
10
- * the original tag information for dependency inference.
8
+ * Symbol used to identify tagged types within the dependency injection system.
9
+ * This symbol is used as a property key to attach metadata to both value tags and service tags.
11
10
  *
12
- * The phantom property is optional to allow normal runtime values to be assignable.
11
+ * Note: We can't use a symbol here becuase it produced the following TS error:
12
+ * error TS4020: 'extends' clause of exported class 'NotificationService' has or is using private name 'TagIdKey'.
13
13
  *
14
- * @template T - A ValueTag type
15
- * @returns The value type with optional phantom tag metadata for dependency inference
16
- *
17
- * @example
18
- * ```typescript
19
- * const ApiKeyTag = Tag.of('apiKey')<string>();
20
- *
21
- * class UserService extends Tag.Class('UserService') {
22
- * constructor(
23
- * private db: DatabaseService, // ClassTag - works automatically
24
- * private apiKey: Inject<typeof ApiKeyTag> // ValueTag - type is string, tag preserved
25
- * ) {
26
- * super();
27
- * }
28
- * }
29
- * ```
30
- */
31
- type Inject<T extends ValueTag<unknown, string | symbol>> = T extends ValueTag<infer V, string | symbol> ? V & {
32
- readonly [InjectSource]?: T;
33
- } : never;
34
- /**
35
- * Helper type to extract the original ValueTag from an Inject<T> type.
36
- * Since InjectSource is optional, we need to check for both presence and absence.
37
14
  * @internal
38
15
  */
39
- type ExtractInjectTag<T> = T extends {
40
- readonly [InjectSource]?: infer U;
41
- } ? U : never;
16
+ declare const ValueTagIdKey = "sandly/ValueTagIdKey";
17
+ declare const ServiceTagIdKey = "sandly/ServiceTagIdKey";
42
18
  /**
43
- * Internal symbol used to identify tagged types within the dependency injection system.
44
- * This symbol is used as a property key to attach metadata to both value tags and class tags.
19
+ * Internal string used to identify the type of a tagged type within the dependency injection system.
20
+ * This string is used as a property key to attach metadata to both value tags and service tags.
21
+ * It is used to carry the type of the tagged type and should not be used directly.
45
22
  * @internal
46
23
  */
47
- declare const TagId: "__tag_id__";
24
+ declare const TagTypeKey = "sandly/TagTypeKey";
48
25
  /**
49
26
  * Type representing a value-based dependency tag.
50
27
  *
@@ -58,31 +35,30 @@ declare const TagId: "__tag_id__";
58
35
  * @example
59
36
  * ```typescript
60
37
  * // Creates a value tag for string configuration
61
- * const ApiKeyTag: ValueTag<string, 'apiKey'> = Tag.of('apiKey')<string>();
38
+ * const ApiKeyTag: ValueTag<'apiKey', string> = Tag.of('apiKey')<string>();
62
39
  *
63
40
  * // Register in container
64
41
  * container.register(ApiKeyTag, () => 'my-secret-key');
65
42
  * ```
66
43
  */
67
- interface ValueTag<T, Id extends string | symbol> {
68
- readonly [TagId]: Id;
69
- /** @internal Phantom type to carry T */
70
- readonly __type: T;
44
+ interface ValueTag<Id extends TagId, T> {
45
+ readonly [ValueTagIdKey]: Id;
46
+ readonly [TagTypeKey]: T;
71
47
  }
72
48
  /**
73
49
  * Type representing a class-based dependency tag.
74
50
  *
75
- * Tagged classes are created by Tag.Class() and serve as both the dependency identifier
51
+ * Tagged classes are created by Tag.Service() and serve as both the dependency identifier
76
52
  * and the constructor for the service. They extend regular classes with tag metadata
77
53
  * that the DI system uses for identification and type safety.
78
54
  *
79
- * @template T - The type of instances created by this tagged class
80
55
  * @template Id - The unique identifier for this tag (string or symbol)
56
+ * @template T - The type of instances created by this tagged class
81
57
  *
82
58
  * @example
83
59
  * ```typescript
84
60
  * // Creates a tagged class
85
- * class UserService extends Tag.Class('UserService') {
61
+ * class UserService extends Tag.Service('UserService') {
86
62
  * getUsers() { return []; }
87
63
  * }
88
64
  *
@@ -90,13 +66,13 @@ interface ValueTag<T, Id extends string | symbol> {
90
66
  * container.register(UserService, () => new UserService());
91
67
  * ```
92
68
  *
93
- * @internal - Users should use Tag.Class() instead of working with this type directly
69
+ * @internal - Users should use Tag.Service() instead of working with this type directly
94
70
  */
95
- interface ClassTag<T, Id extends string | symbol> {
71
+ interface ServiceTag<Id extends TagId, T> {
96
72
  new (...args: any[]): T & {
97
- readonly [TagId]: Id;
73
+ readonly [ServiceTagIdKey]?: Id;
98
74
  };
99
- readonly [TagId]: Id;
75
+ readonly [ServiceTagIdKey]?: Id;
100
76
  }
101
77
  /**
102
78
  * Utility type that extracts the service type from any dependency tag.
@@ -105,7 +81,7 @@ interface ClassTag<T, Id extends string | symbol> {
105
81
  * the container and layers to automatically determine what type of service
106
82
  * a given tag represents without manual type annotations.
107
83
  *
108
- * @template T - Any dependency tag (ValueTag or ClassTag)
84
+ * @template T - Any dependency tag (ValueTag or ServiceTag)
109
85
  * @returns The service type that the tag represents
110
86
  *
111
87
  * @example With value tags
@@ -117,9 +93,9 @@ interface ClassTag<T, Id extends string | symbol> {
117
93
  * type ConfigService = TagType<typeof ConfigTag>; // { apiKey: string }
118
94
  * ```
119
95
  *
120
- * @example With class tags
96
+ * @example With service tags
121
97
  * ```typescript
122
- * class UserService extends Tag.Class('UserService') {
98
+ * class UserService extends Tag.Service('UserService') {
123
99
  * getUsers() { return []; }
124
100
  * }
125
101
  *
@@ -132,11 +108,11 @@ interface ClassTag<T, Id extends string | symbol> {
132
108
  * container.register(StringTag, () => 'hello'); // Factory must return string
133
109
  * container.register(UserService, () => new UserService()); // Factory must return UserService
134
110
  *
135
- * const str: string = await container.get(StringTag); // Automatically typed as string
136
- * const user: UserService = await container.get(UserService); // Automatically typed as UserService
111
+ * const str: string = await container.resolve(StringTag); // Automatically typed as string
112
+ * const user: UserService = await container.resolve(UserService); // Automatically typed as UserService
137
113
  * ```
138
114
  */
139
- type TagType<T> = T extends ValueTag<infer V, string | symbol> ? V : T extends ClassTag<infer V, string | symbol> ? V : never;
115
+ type TagType<TTag extends AnyTag> = TTag extends ValueTag<any, infer T> ? T : TTag extends ServiceTag<any, infer T> ? T : never;
140
116
  /**
141
117
  * Union type representing any valid dependency tag in the system.
142
118
  *
@@ -152,15 +128,15 @@ type TagType<T> = T extends ValueTag<infer V, string | symbol> ? V : T extends C
152
128
  *
153
129
  * @example Class tag
154
130
  * ```typescript
155
- * class DatabaseService extends Tag.Class('DatabaseService') {}
131
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
156
132
  * // DatabaseService satisfies AnyTag
157
133
  * ```
158
134
  */
159
- type AnyTag = ValueTag<any, string | symbol> | ClassTag<any, string | symbol>;
135
+ type AnyTag = ValueTag<TagId, any> | ServiceTag<TagId, any>;
160
136
  /**
161
137
  * Utility object containing factory functions for creating dependency tags.
162
138
  *
163
- * The Tag object provides the primary API for creating both value tags and class tags
139
+ * The Tag object provides the primary API for creating both value tags and service tags
164
140
  * used throughout the dependency injection system. It's the main entry point for
165
141
  * defining dependencies in a type-safe way.
166
142
  */
@@ -220,7 +196,7 @@ declare const Tag: {
220
196
  * }));
221
197
  * ```
222
198
  */
223
- of: <Id extends string | symbol>(id: Id) => <T>() => ValueTag<T, Id>;
199
+ of: <Id extends TagId>(id: Id) => <T>() => ValueTag<Id, T>;
224
200
  /**
225
201
  * Creates an anonymous value tag with a unique symbol identifier.
226
202
  *
@@ -254,7 +230,7 @@ declare const Tag: {
254
230
  * console.log(ConfigA === ConfigB); // false
255
231
  * ```
256
232
  */
257
- for: <T>() => ValueTag<T, symbol>;
233
+ for: <T>() => ValueTag<symbol, T>;
258
234
  /**
259
235
  * Creates a base class that can be extended to create service classes with dependency tags.
260
236
  *
@@ -268,7 +244,7 @@ declare const Tag: {
268
244
  *
269
245
  * @example Basic service class
270
246
  * ```typescript
271
- * class UserService extends Tag.Class('UserService') {
247
+ * class UserService extends Tag.Service('UserService') {
272
248
  * getUsers() {
273
249
  * return ['alice', 'bob'];
274
250
  * }
@@ -279,11 +255,11 @@ declare const Tag: {
279
255
  *
280
256
  * @example Service with dependencies
281
257
  * ```typescript
282
- * class DatabaseService extends Tag.Class('DatabaseService') {
258
+ * class DatabaseService extends Tag.Service('DatabaseService') {
283
259
  * query(sql: string) { return []; }
284
260
  * }
285
261
  *
286
- * class UserRepository extends Tag.Class('UserRepository') {
262
+ * class UserRepository extends Tag.Service('UserRepository') {
287
263
  * constructor(private db: DatabaseService) {
288
264
  * super();
289
265
  * }
@@ -296,22 +272,22 @@ declare const Tag: {
296
272
  * container
297
273
  * .register(DatabaseService, () => new DatabaseService())
298
274
  * .register(UserRepository, async (ctx) =>
299
- * new UserRepository(await ctx.get(DatabaseService))
275
+ * new UserRepository(await ctx.resolve(DatabaseService))
300
276
  * );
301
277
  * ```
302
278
  *
303
279
  * @example With symbol identifiers
304
280
  * ```typescript
305
- * const SERVICE_ID = Symbol('InternalService');
281
+ * const ServiceId = Symbol('InternalService');
306
282
  *
307
- * class InternalService extends Tag.Class(SERVICE_ID) {
283
+ * class InternalService extends Tag.Service(ServiceId) {
308
284
  * doInternalWork() { return 'work'; }
309
285
  * }
310
286
  * ```
311
287
  */
312
- Class: <Id extends string | symbol>(id: Id) => ClassTag<{
313
- readonly __tag_id__: Id;
314
- }, Id>;
288
+ Service: <Id extends TagId>(id: Id) => ServiceTag<Id, {
289
+ readonly "sandly/ServiceTagIdKey"?: Id;
290
+ }>;
315
291
  /**
316
292
  * Extracts the string representation of a tag's identifier.
317
293
  *
@@ -319,14 +295,14 @@ declare const Tag: {
319
295
  * whether it's a string-based or symbol-based tag. Primarily used internally
320
296
  * for error messages and debugging.
321
297
  *
322
- * @param tag - Any valid dependency tag (value tag or class tag)
298
+ * @param tag - Any valid dependency tag (value tag or service tag)
323
299
  * @returns String representation of the tag's identifier
324
300
  *
325
301
  * @example
326
302
  * ```typescript
327
303
  * const StringTag = Tag.of('myString')<string>();
328
304
  * const SymbolTag = Tag.for<number>();
329
- * class ServiceClass extends Tag.Class('MyService') {}
305
+ * class ServiceClass extends Tag.Service('MyService') {}
330
306
  *
331
307
  * console.log(Tag.id(StringTag)); // "myString"
332
308
  * console.log(Tag.id(SymbolTag)); // "Symbol()"
@@ -335,11 +311,54 @@ declare const Tag: {
335
311
  *
336
312
  * @internal - Primarily for internal use in error messages and debugging
337
313
  */
338
- id: (tag: AnyTag) => string;
314
+ id: (tag: AnyTag) => TagId | undefined;
315
+ isTag: (tag: unknown) => tag is AnyTag;
339
316
  };
317
+ /**
318
+ * String used to store the original ValueTag in Inject<T> types.
319
+ * This prevents property name collisions while allowing type-level extraction.
320
+ */
321
+ declare const InjectSource = "sandly/InjectSource";
322
+ /**
323
+ * Helper type for injecting ValueTag dependencies in constructor parameters.
324
+ * This allows clean specification of ValueTag dependencies while preserving
325
+ * the original tag information for dependency inference.
326
+ *
327
+ * The phantom property is optional to allow normal runtime values to be assignable.
328
+ *
329
+ * @template T - A ValueTag type
330
+ * @returns The value type with optional phantom tag metadata for dependency inference
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
335
+ *
336
+ * class UserService extends Tag.Service('UserService') {
337
+ * constructor(
338
+ * private db: DatabaseService, // ServiceTag - works automatically
339
+ * private apiKey: Inject<typeof ApiKeyTag> // ValueTag - type is string, tag preserved
340
+ * ) {
341
+ * super();
342
+ * }
343
+ * }
344
+ * ```
345
+ */
346
+ type Inject<T extends ValueTag<TagId, unknown>> = T extends ValueTag<any, infer V> ? V & {
347
+ readonly [InjectSource]?: T;
348
+ } : never;
349
+ /**
350
+ * Helper type to extract the original ValueTag from an Inject<T> type.
351
+ * Since InjectSource is optional, we need to check for both presence and absence.
352
+ * @internal
353
+ */
354
+ type ExtractInjectTag<T> = T extends {
355
+ readonly [InjectSource]?: infer U;
356
+ } ? U : never;
340
357
  //#endregion
341
358
  //#region src/types.d.ts
342
359
  type PromiseOrValue<T> = T | Promise<T>;
360
+ type Contravariant<A> = (_: A) => void;
361
+ type Covariant<A> = (_: never) => A;
343
362
  //#endregion
344
363
  //#region src/container.d.ts
345
364
  /**
@@ -366,8 +385,8 @@ type PromiseOrValue<T> = T | Promise<T>;
366
385
  * ```typescript
367
386
  * const factory: Factory<UserService, typeof ConfigTag | typeof DatabaseService> = async (ctx) => {
368
387
  * const [config, db] = await Promise.all([
369
- * ctx.get(ConfigTag),
370
- * ctx.get(DatabaseService)
388
+ * ctx.resolve(ConfigTag),
389
+ * ctx.resolve(DatabaseService)
371
390
  * ]);
372
391
  * return new UserService(config, db);
373
392
  * };
@@ -427,7 +446,7 @@ type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
427
446
  *
428
447
  * @example Using DependencyLifecycle for registration
429
448
  * ```typescript
430
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
449
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
431
450
  * async connect() { return; }
432
451
  * async disconnect() { return; }
433
452
  * }
@@ -443,7 +462,7 @@ type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
443
462
  * }
444
463
  * };
445
464
  *
446
- * container().register(DatabaseConnection, lifecycle);
465
+ * Container.empty().register(DatabaseConnection, lifecycle);
447
466
  * ```
448
467
  */
449
468
  type DependencyLifecycle<T, TReg extends AnyTag> = {
@@ -465,7 +484,7 @@ type DependencyLifecycle<T, TReg extends AnyTag> = {
465
484
  * const spec: DependencySpec<typeof UserService, never> =
466
485
  * () => new UserService();
467
486
  *
468
- * container().register(UserService, spec);
487
+ * Container.empty().register(UserService, spec);
469
488
  * ```
470
489
  *
471
490
  * @example Lifecycle registration
@@ -475,29 +494,34 @@ type DependencyLifecycle<T, TReg extends AnyTag> = {
475
494
  * finalizer: (conn) => conn.close()
476
495
  * };
477
496
  *
478
- * container().register(DatabaseConnection, spec);
497
+ * Container.empty().register(DatabaseConnection, spec);
479
498
  * ```
480
499
  */
481
500
  type DependencySpec<T extends AnyTag, TReg extends AnyTag> = Factory<TagType<T>, TReg> | DependencyLifecycle<TagType<T>, TReg>;
482
501
  /**
483
502
  * Type representing the context available to factory functions during dependency resolution.
484
503
  *
485
- * This type contains only the `get` method from the container, which is used to retrieve
504
+ * This type contains only the `resolve` and `resolveAll` methods from the container, which are used to retrieve
486
505
  * other dependencies during the creation of a service.
487
506
  *
488
507
  * @template TReg - Union type of all dependencies available in the container
489
508
  */
490
- type ResolutionContext<TReg extends AnyTag> = Pick<IContainer<TReg>, 'get'>;
509
+ type ResolutionContext<TReg extends AnyTag> = Pick<IContainer<TReg>, 'resolve' | 'resolveAll'>;
510
+ declare const ContainerTypeId: unique symbol;
491
511
  /**
492
512
  * Interface representing a container that can register and retrieve dependencies.
493
513
  *
494
514
  * @template TReg - Union type of all dependencies available in the container
495
515
  */
496
- interface IContainer<in TReg extends AnyTag> {
497
- register<T extends AnyTag>(tag: T, spec: DependencySpec<T, TReg>): IContainer<TReg | T>;
516
+ interface IContainer<TReg extends AnyTag = never> {
517
+ readonly [ContainerTypeId]: {
518
+ readonly _TReg: Contravariant<TReg>;
519
+ };
520
+ register: <T extends AnyTag>(tag: T, spec: DependencySpec<T, TReg>) => IContainer<TReg | T>;
498
521
  has(tag: AnyTag): boolean;
499
522
  exists(tag: AnyTag): boolean;
500
- get<T extends TReg>(tag: T): Promise<TagType<T>>;
523
+ resolve: <T extends TReg>(tag: T) => Promise<TagType<T>>;
524
+ resolveAll: <const T extends readonly TReg[]>(...tags: T) => Promise<{ [K in keyof T]: TagType<T[K]> }>;
501
525
  merge<TTarget extends AnyTag>(other: IContainer<TTarget>): IContainer<TReg | TTarget>;
502
526
  destroy(): Promise<void>;
503
527
  }
@@ -512,26 +536,26 @@ interface IContainer<in TReg extends AnyTag> {
512
536
  *
513
537
  * @template TReg - Union type of all registered dependency tags in this container
514
538
  *
515
- * @example Basic usage with class tags
539
+ * @example Basic usage with service tags
516
540
  * ```typescript
517
541
  * import { container, Tag } from 'sandly';
518
542
  *
519
- * class DatabaseService extends Tag.Class('DatabaseService') {
543
+ * class DatabaseService extends Tag.Service('DatabaseService') {
520
544
  * query() { return 'data'; }
521
545
  * }
522
546
  *
523
- * class UserService extends Tag.Class('UserService') {
547
+ * class UserService extends Tag.Service('UserService') {
524
548
  * constructor(private db: DatabaseService) {}
525
549
  * getUser() { return this.db.query(); }
526
550
  * }
527
551
  *
528
- * const c = container()
552
+ * const container = Container.empty()
529
553
  * .register(DatabaseService, () => new DatabaseService())
530
554
  * .register(UserService, async (ctx) =>
531
- * new UserService(await ctx.get(DatabaseService))
555
+ * new UserService(await ctx.resolve(DatabaseService))
532
556
  * );
533
557
  *
534
- * const userService = await c.get(UserService);
558
+ * const userService = await c.resolve(UserService);
535
559
  * ```
536
560
  *
537
561
  * @example Usage with value tags
@@ -539,22 +563,22 @@ interface IContainer<in TReg extends AnyTag> {
539
563
  * const ApiKeyTag = Tag.of('apiKey')<string>();
540
564
  * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
541
565
  *
542
- * const c = container()
566
+ * const container = Container.empty()
543
567
  * .register(ApiKeyTag, () => process.env.API_KEY!)
544
568
  * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
545
569
  *
546
- * const apiKey = await c.get(ApiKeyTag);
547
- * const config = await c.get(ConfigTag);
570
+ * const apiKey = await c.resolve(ApiKeyTag);
571
+ * const config = await c.resolve(ConfigTag);
548
572
  * ```
549
573
  *
550
574
  * @example With finalizers for cleanup
551
575
  * ```typescript
552
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
576
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
553
577
  * async connect() { return; }
554
578
  * async disconnect() { return; }
555
579
  * }
556
580
  *
557
- * const c = container().register(
581
+ * const container = Container.empty().register(
558
582
  * DatabaseConnection,
559
583
  * async () => {
560
584
  * const conn = new DatabaseConnection();
@@ -568,7 +592,11 @@ interface IContainer<in TReg extends AnyTag> {
568
592
  * await c.destroy(); // Calls all finalizers
569
593
  * ```
570
594
  */
571
- declare class Container<in TReg extends AnyTag = never> implements IContainer<TReg> {
595
+ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
596
+ readonly [ContainerTypeId]: {
597
+ readonly _TReg: Contravariant<TReg>;
598
+ };
599
+ protected constructor();
572
600
  /**
573
601
  * Cache of instantiated dependencies as promises.
574
602
  * Ensures singleton behavior and supports concurrent access.
@@ -590,12 +618,17 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
590
618
  * @internal
591
619
  */
592
620
  protected isDestroyed: boolean;
621
+ /**
622
+ * Creates a new empty container instance.
623
+ * @returns A new empty Container instance with no registered dependencies.
624
+ */
625
+ static empty(): Container<never>;
593
626
  /**
594
627
  * Registers a dependency in the container with a factory function and optional finalizer.
595
628
  *
596
629
  * The factory function receives the current container instance and must return the
597
630
  * service instance (or a Promise of it). The container tracks the registration at
598
- * the type level, ensuring type safety for subsequent `.get()` calls.
631
+ * the type level, ensuring type safety for subsequent `.resolve()` calls.
599
632
  *
600
633
  * If a dependency is already registered, this method will override it unless the
601
634
  * dependency has already been instantiated, in which case it will throw an error.
@@ -610,11 +643,11 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
610
643
  *
611
644
  * @example Registering a simple service
612
645
  * ```typescript
613
- * class LoggerService extends Tag.Class('LoggerService') {
646
+ * class LoggerService extends Tag.Service('LoggerService') {
614
647
  * log(message: string) { console.log(message); }
615
648
  * }
616
649
  *
617
- * const c = container().register(
650
+ * const container = Container.empty().register(
618
651
  * LoggerService,
619
652
  * () => new LoggerService()
620
653
  * );
@@ -622,24 +655,24 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
622
655
  *
623
656
  * @example Registering with dependencies
624
657
  * ```typescript
625
- * class UserService extends Tag.Class('UserService') {
658
+ * class UserService extends Tag.Service('UserService') {
626
659
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
627
660
  * }
628
661
  *
629
- * const c = container()
662
+ * const container = Container.empty()
630
663
  * .register(DatabaseService, () => new DatabaseService())
631
664
  * .register(LoggerService, () => new LoggerService())
632
665
  * .register(UserService, async (ctx) =>
633
666
  * new UserService(
634
- * await ctx.get(DatabaseService),
635
- * await ctx.get(LoggerService)
667
+ * await ctx.resolve(DatabaseService),
668
+ * await ctx.resolve(LoggerService)
636
669
  * )
637
670
  * );
638
671
  * ```
639
672
  *
640
673
  * @example Overriding a dependency
641
674
  * ```typescript
642
- * const c = container()
675
+ * const container = Container.empty()
643
676
  * .register(DatabaseService, () => new DatabaseService())
644
677
  * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
645
678
  * ```
@@ -648,7 +681,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
648
681
  * ```typescript
649
682
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
650
683
  *
651
- * const c = container().register(
684
+ * const container = Container.empty().register(
652
685
  * ConfigTag,
653
686
  * () => ({ apiUrl: 'https://api.example.com' })
654
687
  * );
@@ -656,12 +689,12 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
656
689
  *
657
690
  * @example With finalizer for cleanup
658
691
  * ```typescript
659
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
692
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
660
693
  * async connect() { return; }
661
694
  * async close() { return; }
662
695
  * }
663
696
  *
664
- * const c = container().register(
697
+ * const container = Container.empty().register(
665
698
  * DatabaseConnection,
666
699
  * async () => {
667
700
  * const conn = new DatabaseConnection();
@@ -684,7 +717,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
684
717
  *
685
718
  * @example
686
719
  * ```typescript
687
- * const c = container().register(DatabaseService, () => new DatabaseService());
720
+ * const container = Container.empty().register(DatabaseService, () => new DatabaseService());
688
721
  * console.log(c.has(DatabaseService)); // true
689
722
  * ```
690
723
  */
@@ -715,10 +748,10 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
715
748
  *
716
749
  * @example Basic usage
717
750
  * ```typescript
718
- * const c = container()
751
+ * const container = Container.empty()
719
752
  * .register(DatabaseService, () => new DatabaseService());
720
753
  *
721
- * const db = await c.get(DatabaseService);
754
+ * const db = await c.resolve(DatabaseService);
722
755
  * db.query('SELECT * FROM users');
723
756
  * ```
724
757
  *
@@ -726,9 +759,9 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
726
759
  * ```typescript
727
760
  * // All three calls will receive the same instance
728
761
  * const [db1, db2, db3] = await Promise.all([
729
- * c.get(DatabaseService),
730
- * c.get(DatabaseService),
731
- * c.get(DatabaseService)
762
+ * c.resolve(DatabaseService),
763
+ * c.resolve(DatabaseService),
764
+ * c.resolve(DatabaseService)
732
765
  * ]);
733
766
  *
734
767
  * console.log(db1 === db2 === db3); // true
@@ -736,17 +769,58 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
736
769
  *
737
770
  * @example Dependency injection in factories
738
771
  * ```typescript
739
- * const c = container()
772
+ * const container = Container.empty()
740
773
  * .register(DatabaseService, () => new DatabaseService())
741
774
  * .register(UserService, async (ctx) => {
742
- * const db = await ctx.get(DatabaseService);
775
+ * const db = await ctx.resolve(DatabaseService);
743
776
  * return new UserService(db);
744
777
  * });
745
778
  *
746
- * const userService = await c.get(UserService);
779
+ * const userService = await c.resolve(UserService);
780
+ * ```
781
+ */
782
+ resolve<T extends TReg>(tag: T): Promise<TagType<T>>;
783
+ /**
784
+ * Resolves multiple dependencies concurrently using Promise.all.
785
+ *
786
+ * This method takes a variable number of dependency tags and resolves all of them concurrently,
787
+ * returning a tuple with the resolved instances in the same order as the input tags.
788
+ * The method maintains all the same guarantees as the individual resolve method:
789
+ * singleton behavior, circular dependency detection, and proper error handling.
790
+ *
791
+ * @template T - The tuple type of dependency tags to resolve
792
+ * @param tags - Variable number of dependency tags to resolve
793
+ * @returns Promise resolving to a tuple of service instances in the same order
794
+ * @throws {ContainerDestroyedError} If the container has been destroyed
795
+ * @throws {UnknownDependencyError} If any dependency is not registered
796
+ * @throws {CircularDependencyError} If a circular dependency is detected
797
+ * @throws {DependencyCreationError} If any factory function throws an error
798
+ *
799
+ * @example Basic usage
800
+ * ```typescript
801
+ * const container = Container.empty()
802
+ * .register(DatabaseService, () => new DatabaseService())
803
+ * .register(LoggerService, () => new LoggerService());
804
+ *
805
+ * const [db, logger] = await c.resolveAll(DatabaseService, LoggerService);
806
+ * ```
807
+ *
808
+ * @example Mixed tag types
809
+ * ```typescript
810
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
811
+ * const container = Container.empty()
812
+ * .register(ApiKeyTag, () => 'secret-key')
813
+ * .register(UserService, () => new UserService());
814
+ *
815
+ * const [apiKey, userService] = await c.resolveAll(ApiKeyTag, UserService);
816
+ * ```
817
+ *
818
+ * @example Empty array
819
+ * ```typescript
820
+ * const results = await c.resolveAll(); // Returns empty array
747
821
  * ```
748
822
  */
749
- get<T extends TReg>(tag: T): Promise<TagType<T>>;
823
+ resolveAll<const T extends readonly TReg[]>(...tags: T): Promise<{ [K in keyof T]: TagType<T[K]> }>;
750
824
  /**
751
825
  * Copies all registrations from this container to a target container.
752
826
  *
@@ -771,10 +845,10 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
771
845
  *
772
846
  * @example Merging containers
773
847
  * ```typescript
774
- * const container1 = container()
848
+ * const container1 = Container.empty()
775
849
  * .register(DatabaseService, () => new DatabaseService());
776
850
  *
777
- * const container2 = container()
851
+ * const container2 = Container.empty()
778
852
  * .register(UserService, () => new UserService());
779
853
  *
780
854
  * const merged = container1.merge(container2);
@@ -803,7 +877,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
803
877
  *
804
878
  * @example Basic cleanup
805
879
  * ```typescript
806
- * const c = container()
880
+ * const container = Container.empty()
807
881
  * .register(DatabaseConnection,
808
882
  * async () => {
809
883
  * const conn = new DatabaseConnection();
@@ -813,12 +887,12 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
813
887
  * (conn) => conn.disconnect() // Finalizer
814
888
  * );
815
889
  *
816
- * const db = await c.get(DatabaseConnection);
890
+ * const db = await c.resolve(DatabaseConnection);
817
891
  * await c.destroy(); // Calls conn.disconnect(), container becomes unusable
818
892
  *
819
893
  * // This will throw an error
820
894
  * try {
821
- * await c.get(DatabaseConnection);
895
+ * await c.resolve(DatabaseConnection);
822
896
  * } catch (error) {
823
897
  * console.log(error.message); // "Cannot resolve dependencies from a destroyed container"
824
898
  * }
@@ -826,9 +900,9 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
826
900
  *
827
901
  * @example Application shutdown
828
902
  * ```typescript
829
- * const appContainer = container()
903
+ * const appContainer Container.empty
830
904
  * .register(DatabaseService, () => new DatabaseService())
831
- * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.get(DatabaseService)));
905
+ * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.resolve(DatabaseService)));
832
906
  *
833
907
  * // During application shutdown
834
908
  * process.on('SIGTERM', async () => {
@@ -855,32 +929,6 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
855
929
  */
856
930
  destroy(): Promise<void>;
857
931
  }
858
- /**
859
- * Creates a new empty dependency injection container.
860
- *
861
- * This is a convenience factory function that creates a new DependencyContainer instance.
862
- * The returned container starts with no registered dependencies and the type parameter
863
- * defaults to `never`, indicating no dependencies are available for retrieval yet.
864
- *
865
- * @returns A new empty DependencyContainer instance
866
- *
867
- * @example
868
- * ```typescript
869
- * import { container, Tag } from 'sandly';
870
- *
871
- * class DatabaseService extends Tag.Class('DatabaseService') {}
872
- * class UserService extends Tag.Class('UserService') {}
873
- *
874
- * const c = container()
875
- * .register(DatabaseService, () => new DatabaseService())
876
- * .register(UserService, async (ctx) =>
877
- * new UserService(await ctx.get(DatabaseService))
878
- * );
879
- *
880
- * const userService = await c.get(UserService);
881
- * ```
882
- */
883
- declare function container(): Container;
884
932
  //#endregion
885
933
  //#region src/errors.d.ts
886
934
  type ErrorProps = {
@@ -917,7 +965,7 @@ declare class BaseError extends Error {
917
965
  * @example Catching DI errors
918
966
  * ```typescript
919
967
  * try {
920
- * await container.get(SomeService);
968
+ * await container.resolve(SomeService);
921
969
  * } catch (error) {
922
970
  * if (error instanceof ContainerError) {
923
971
  * console.error('DI Error:', error.message);
@@ -938,7 +986,7 @@ declare class DependencyAlreadyInstantiatedError extends ContainerError {}
938
986
  /**
939
987
  * Error thrown when attempting to use a container that has been destroyed.
940
988
  *
941
- * This error occurs when calling `container.get()`, `container.register()`, or `container.destroy()`
989
+ * This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
942
990
  * on a container that has already been destroyed. It indicates a programming error where the container
943
991
  * is being used after it has been destroyed.
944
992
  */
@@ -946,16 +994,16 @@ declare class ContainerDestroyedError extends ContainerError {}
946
994
  /**
947
995
  * Error thrown when attempting to retrieve a dependency that hasn't been registered.
948
996
  *
949
- * This error occurs when calling `container.get(Tag)` for a tag that was never
997
+ * This error occurs when calling `container.resolve(Tag)` for a tag that was never
950
998
  * registered via `container.register()`. It indicates a programming error where
951
999
  * the dependency setup is incomplete.
952
1000
  *
953
1001
  * @example
954
1002
  * ```typescript
955
- * const c = container(); // Empty container
1003
+ * const container = Container.empty(); // Empty container
956
1004
  *
957
1005
  * try {
958
- * await c.get(UnregisteredService); // This will throw
1006
+ * await c.resolve(UnregisteredService); // This will throw
959
1007
  * } catch (error) {
960
1008
  * if (error instanceof UnknownDependencyError) {
961
1009
  * console.error('Missing dependency:', error.message);
@@ -981,19 +1029,19 @@ declare class UnknownDependencyError extends ContainerError {
981
1029
  *
982
1030
  * @example Circular dependency scenario
983
1031
  * ```typescript
984
- * class ServiceA extends Tag.Class('ServiceA') {}
985
- * class ServiceB extends Tag.Class('ServiceB') {}
1032
+ * class ServiceA extends Tag.Service('ServiceA') {}
1033
+ * class ServiceB extends Tag.Service('ServiceB') {}
986
1034
  *
987
- * const c = container()
1035
+ * const container = Container.empty()
988
1036
  * .register(ServiceA, async (ctx) =>
989
- * new ServiceA(await ctx.get(ServiceB)) // Depends on B
1037
+ * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
990
1038
  * )
991
1039
  * .register(ServiceB, async (ctx) =>
992
- * new ServiceB(await ctx.get(ServiceA)) // Depends on A - CIRCULAR!
1040
+ * new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
993
1041
  * );
994
1042
  *
995
1043
  * try {
996
- * await c.get(ServiceA);
1044
+ * await c.resolve(ServiceA);
997
1045
  * } catch (error) {
998
1046
  * if (error instanceof CircularDependencyError) {
999
1047
  * console.error('Circular dependency:', error.message);
@@ -1020,14 +1068,14 @@ declare class CircularDependencyError extends ContainerError {
1020
1068
  *
1021
1069
  * @example Factory throwing error
1022
1070
  * ```typescript
1023
- * class DatabaseService extends Tag.Class('DatabaseService') {}
1071
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
1024
1072
  *
1025
- * const c = container().register(DatabaseService, () => {
1073
+ * const container = Container.empty().register(DatabaseService, () => {
1026
1074
  * throw new Error('Database connection failed');
1027
1075
  * });
1028
1076
  *
1029
1077
  * try {
1030
- * await c.get(DatabaseService);
1078
+ * await c.resolve(DatabaseService);
1031
1079
  * } catch (error) {
1032
1080
  * if (error instanceof DependencyCreationError) {
1033
1081
  * console.error('Failed to create:', error.message);
@@ -1077,18 +1125,13 @@ declare class DependencyFinalizationError extends ContainerError {
1077
1125
  //#endregion
1078
1126
  //#region src/layer.d.ts
1079
1127
  /**
1080
- * The most generic layer type that works with variance - accepts any concrete layer.
1081
- *
1082
- * This type is carefully constructed to work with the Layer interface's variance annotations:
1083
- * - `never` for TRequires (contravariant): Any layer requiring specific dependencies can be
1084
- * assigned to this since requiring something is more restrictive than requiring nothing
1085
- * - `AnyTag` for TProvides (covariant): Any layer providing specific services can be assigned
1086
- * to this since the general AnyTag type can represent any specific tag type
1087
- *
1088
- * Used internally for functions like Layer.mergeAll() that need to accept arrays of layers
1089
- * with different requirement/provision types while preserving type safety through variance.
1128
+ * The most generic layer type that accepts any concrete layer.
1129
+ */
1130
+ type AnyLayer = Layer<any, any>;
1131
+ /**
1132
+ * The type ID for the Layer interface.
1090
1133
  */
1091
- type AnyLayer = Layer<never, AnyTag>;
1134
+ declare const LayerTypeId: unique symbol;
1092
1135
  /**
1093
1136
  * A dependency layer represents a reusable, composable unit of dependency registrations.
1094
1137
  * Layers allow you to organize your dependency injection setup into logical groups
@@ -1098,12 +1141,12 @@ type AnyLayer = Layer<never, AnyTag>;
1098
1141
  *
1099
1142
  * The Layer interface uses TypeScript's variance annotations to enable safe substitutability:
1100
1143
  *
1101
- * ### TRequires (contravariant with `in`)
1144
+ * ### TRequires (covariant)
1102
1145
  * A layer requiring fewer dependencies can substitute one requiring more:
1103
1146
  * - `Layer<never, X>` can be used where `Layer<A | B, X>` is expected
1104
1147
  * - Intuition: A service that needs nothing is more flexible than one that needs specific deps
1105
1148
  *
1106
- * ### TProvides (covariant with `out`)
1149
+ * ### TProvides (contravariant)
1107
1150
  * A layer providing more services can substitute one providing fewer:
1108
1151
  * - `Layer<X, A | B>` can be used where `Layer<X, A>` is expected
1109
1152
  * - Intuition: A service that gives you extra things is compatible with expecting fewer things
@@ -1115,7 +1158,7 @@ type AnyLayer = Layer<never, AnyTag>;
1115
1158
  * ```typescript
1116
1159
  * import { layer, Tag, container } from 'sandly';
1117
1160
  *
1118
- * class DatabaseService extends Tag.Class('DatabaseService') {
1161
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1119
1162
  * query() { return 'data'; }
1120
1163
  * }
1121
1164
  *
@@ -1125,10 +1168,10 @@ type AnyLayer = Layer<never, AnyTag>;
1125
1168
  * );
1126
1169
  *
1127
1170
  * // Apply the layer to a container
1128
- * const c = container();
1171
+ * const container = Container.empty();
1129
1172
  * const finalContainer = databaseLayer.register(c);
1130
1173
  *
1131
- * const db = await finalContainer.get(DatabaseService);
1174
+ * const db = await finalContainer.resolve(DatabaseService);
1132
1175
  * ```
1133
1176
  *
1134
1177
  * @example Layer composition with variance
@@ -1136,18 +1179,19 @@ type AnyLayer = Layer<never, AnyTag>;
1136
1179
  * // Layer that requires DatabaseService and provides UserService
1137
1180
  * const userLayer = layer<typeof DatabaseService, typeof UserService>((container) =>
1138
1181
  * container.register(UserService, async (ctx) =>
1139
- * new UserService(await ctx.get(DatabaseService))
1182
+ * new UserService(await ctx.resolve(DatabaseService))
1140
1183
  * )
1141
1184
  * );
1142
1185
  *
1143
1186
  * // Compose layers: provide database layer to user layer
1144
1187
  * const appLayer = userLayer.provide(databaseLayer);
1145
- *
1146
- * // Thanks to variance, Layer<never, typeof DatabaseService> automatically works
1147
- * // where Layer<typeof DatabaseService, typeof UserService> requires DatabaseService
1148
1188
  * ```
1149
1189
  */
1150
- interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTag = never> {
1190
+ interface Layer<TRequires extends AnyTag, TProvides extends AnyTag> {
1191
+ readonly [LayerTypeId]?: {
1192
+ readonly _TRequires: Covariant<TRequires>;
1193
+ readonly _TProvides: Contravariant<TProvides>;
1194
+ };
1151
1195
  /**
1152
1196
  * Applies this layer's registrations to the given container.
1153
1197
  *
@@ -1165,13 +1209,13 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1165
1209
  *
1166
1210
  * @example Basic usage
1167
1211
  * ```typescript
1168
- * const c = container();
1212
+ * const container = Container.empty();
1169
1213
  * const updatedContainer = myLayer.register(c);
1170
1214
  * ```
1171
1215
  *
1172
1216
  * @example With existing services preserved
1173
1217
  * ```typescript
1174
- * const baseContainer = container()
1218
+ * const baseContainer = Container.empty()
1175
1219
  * .register(ExistingService, () => new ExistingService());
1176
1220
  *
1177
1221
  * const enhanced = myLayer.register(baseContainer);
@@ -1286,7 +1330,7 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1286
1330
  * ```typescript
1287
1331
  * import { layer, Tag } from 'sandly';
1288
1332
  *
1289
- * class DatabaseService extends Tag.Class('DatabaseService') {
1333
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1290
1334
  * constructor(private url: string = 'sqlite://memory') {}
1291
1335
  * query() { return 'data'; }
1292
1336
  * }
@@ -1311,15 +1355,15 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1311
1355
  * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
1312
1356
  * (container) =>
1313
1357
  * container
1314
- * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.get(ConfigTag)))
1315
- * .register(CacheService, async (ctx) => new CacheService(await ctx.get(ConfigTag)))
1358
+ * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
1359
+ * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
1316
1360
  * );
1317
1361
  *
1318
1362
  * // Service layer (requires infrastructure)
1319
1363
  * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
1320
1364
  * (container) =>
1321
1365
  * container.register(UserService, async (ctx) =>
1322
- * new UserService(await ctx.get(DatabaseService), await ctx.get(CacheService))
1366
+ * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
1323
1367
  * )
1324
1368
  * );
1325
1369
  *
@@ -1338,7 +1382,7 @@ declare function layer<TRequires extends AnyTag = never, TProvides extends AnyTa
1338
1382
  *
1339
1383
  * @internal
1340
1384
  */
1341
- type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<infer R, AnyTag> ? R : never }[number];
1385
+ type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<infer R, any> ? R : never }[number];
1342
1386
  /**
1343
1387
  * Helper type that extracts the union of all provisions from an array of layers.
1344
1388
  * Used by Layer.mergeAll() to compute the correct provision type for the merged layer.
@@ -1349,7 +1393,7 @@ type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] ext
1349
1393
  *
1350
1394
  * @internal
1351
1395
  */
1352
- type UnionOfProvides<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<never, infer P> ? P : never }[number];
1396
+ type UnionOfProvides<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<any, infer P> ? P : never }[number];
1353
1397
  /**
1354
1398
  * Utility object containing helper functions for working with layers.
1355
1399
  */
@@ -1370,7 +1414,7 @@ declare const Layer: {
1370
1414
  * .merge(serviceLayer);
1371
1415
  * ```
1372
1416
  */
1373
- empty(): Layer;
1417
+ empty(): Layer<never, never>;
1374
1418
  /**
1375
1419
  * Merges multiple layers at once in a type-safe way.
1376
1420
  * This is equivalent to chaining `.merge()` calls but more convenient for multiple layers.
@@ -1461,11 +1505,17 @@ declare const Layer: {
1461
1505
  //#endregion
1462
1506
  //#region src/scoped-container.d.ts
1463
1507
  type Scope = string | symbol;
1464
- declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<TReg> {
1508
+ declare class ScopedContainer<TReg extends AnyTag> extends Container<TReg> {
1465
1509
  readonly scope: Scope;
1466
1510
  private parent;
1467
1511
  private readonly children;
1468
- constructor(parent: IContainer<TReg> | null, scope: Scope);
1512
+ protected constructor(parent: IContainer<TReg> | null, scope: Scope);
1513
+ /**
1514
+ * Creates a new empty scoped container instance.
1515
+ * @param scope - The scope identifier for this container
1516
+ * @returns A new empty ScopedContainer instance with no registered dependencies
1517
+ */
1518
+ static empty(scope: Scope): ScopedContainer<never>;
1469
1519
  /**
1470
1520
  * Registers a dependency in the scoped container.
1471
1521
  *
@@ -1496,7 +1546,7 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1496
1546
  * 3. Otherwise, delegate to parent scope
1497
1547
  * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
1498
1548
  */
1499
- get<T extends TReg>(tag: T): Promise<TagType<T>>;
1549
+ resolve<T extends TReg>(tag: T): Promise<TagType<T>>;
1500
1550
  /**
1501
1551
  * Destroys this scoped container and its children, preserving the container structure for reuse.
1502
1552
  *
@@ -1548,7 +1598,7 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1548
1598
  * ```typescript
1549
1599
  * import { container, scoped } from 'sandly';
1550
1600
  *
1551
- * const appContainer = container()
1601
+ * const appContainer = Container.empty()
1552
1602
  * .register(DatabaseService, () => new DatabaseService())
1553
1603
  * .register(ConfigService, () => new ConfigService());
1554
1604
  *
@@ -1560,10 +1610,10 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1560
1610
  *
1561
1611
  * @example Copying complex registrations
1562
1612
  * ```typescript
1563
- * const baseContainer = container()
1613
+ * const baseContainer = Container.empty()
1564
1614
  * .register(DatabaseService, () => new DatabaseService())
1565
1615
  * .register(UserService, {
1566
- * factory: async (ctx) => new UserService(await ctx.get(DatabaseService)),
1616
+ * factory: async (ctx) => new UserService(await ctx.resolve(DatabaseService)),
1567
1617
  * finalizer: (service) => service.cleanup()
1568
1618
  * });
1569
1619
  *
@@ -1575,49 +1625,60 @@ declare function scoped<TReg extends AnyTag>(container: Container<TReg>, scope:
1575
1625
  //#endregion
1576
1626
  //#region src/service.d.ts
1577
1627
  /**
1578
- * Extracts constructor parameter types from a ClassTag.
1628
+ * Extracts constructor parameter types from a ServiceTag.
1579
1629
  * Only parameters that extend AnyTag are considered as dependencies.
1630
+ * @internal
1580
1631
  */
1581
- type ConstructorParams<T extends ClassTag<unknown, string | symbol>> = T extends (new (...args: infer A) => unknown) ? A : never;
1632
+ type ConstructorParams<T extends ServiceTag<TagId, unknown>> = T extends (new (...args: infer A) => unknown) ? A : never;
1582
1633
  /**
1583
- * Helper to convert a tagged instance type back to its constructor type.
1584
- * This uses the fact that tagged classes have a specific structure with TagId property.
1634
+ * Extracts only dependency tags from a constructor parameter list.
1635
+ * Filters out non‑DI parameters.
1636
+ *
1637
+ * Example:
1638
+ * [DatabaseService, Inject<typeof ConfigTag>, number]
1639
+ * → typeof DatabaseService | typeof ConfigTag
1640
+ * @internal
1641
+ */
1642
+ type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1643
+ readonly [ServiceTagIdKey]?: infer Id;
1644
+ } ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
1645
+ /**
1646
+ * Produces an ordered tuple of constructor parameters
1647
+ * where dependency parameters are replaced with their tag types,
1648
+ * while non‑DI parameters are preserved as‑is.
1649
+ * @internal
1585
1650
  */
1586
- type InstanceToConstructorType<T> = T extends {
1587
- readonly [TagId]: infer Id;
1588
- } ? Id extends string | symbol ? ClassTag<T, Id> : never : never;
1651
+ type InferConstructorDepsTuple<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1652
+ readonly [ServiceTagIdKey]?: infer Id;
1653
+ } ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? T[K] : ExtractInjectTag<T[K]> };
1589
1654
  /**
1590
- * Extracts constructor-typed dependencies from constructor parameters.
1591
- * Converts instance types to their corresponding constructor types.
1592
- * Handles both ClassTag dependencies (automatic) and ValueTag dependencies (via Inject helper).
1655
+ * Union of all dependency tags a ServiceTag constructor requires.
1656
+ * Filters out non‑DI parameters.
1593
1657
  */
1594
- type FilterTags<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1595
- readonly [TagId]: string | symbol;
1596
- } ? InstanceToConstructorType<T[K]> : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
1658
+ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstructorDeps<ConstructorParams<T>> extends AnyTag ? ExtractConstructorDeps<ConstructorParams<T>> : never;
1597
1659
  /**
1598
- * Extracts only the dependency tags from a constructor's parameters for ClassTag services,
1599
- * or returns never for ValueTag services (which have no constructor dependencies).
1600
- * This is used to determine what dependencies a service requires.
1660
+ * Ordered tuple of dependency tags (and other constructor params)
1661
+ * inferred from a ServiceTag’s constructor.
1601
1662
  */
1602
- type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string | symbol> ? FilterTags<ConstructorParams<T>> extends AnyTag ? FilterTags<ConstructorParams<T>> : never : never;
1663
+ type ServiceDepsTuple<T extends ServiceTag<TagId, unknown>> = InferConstructorDepsTuple<ConstructorParams<T>>;
1603
1664
  /**
1604
- * Creates a service layer from any tag type (ClassTag or ValueTag) with optional parameters.
1665
+ * Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
1605
1666
  *
1606
- * For ClassTag services:
1667
+ * For ServiceTag services:
1607
1668
  * - Dependencies are automatically inferred from constructor parameters
1608
1669
  * - The factory function must handle dependency injection by resolving dependencies from the container
1609
1670
  *
1610
1671
  * For ValueTag services:
1611
1672
  * - No constructor dependencies are needed since they don't have constructors
1612
1673
  *
1613
- * @template T - The tag representing the service (ClassTag or ValueTag)
1614
- * @param serviceClass - The tag (ClassTag or ValueTag)
1674
+ * @template T - The tag representing the service (ServiceTag or ValueTag)
1675
+ * @param tag - The tag (ServiceTag or ValueTag)
1615
1676
  * @param factory - Factory function for service instantiation with container
1616
1677
  * @returns The service layer
1617
1678
  *
1618
1679
  * @example Simple service without dependencies
1619
1680
  * ```typescript
1620
- * class LoggerService extends Tag.Class('LoggerService') {
1681
+ * class LoggerService extends Tag.Service('LoggerService') {
1621
1682
  * log(message: string) { console.log(message); }
1622
1683
  * }
1623
1684
  *
@@ -1626,11 +1687,11 @@ type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string
1626
1687
  *
1627
1688
  * @example Service with dependencies
1628
1689
  * ```typescript
1629
- * class DatabaseService extends Tag.Class('DatabaseService') {
1690
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1630
1691
  * query() { return []; }
1631
1692
  * }
1632
1693
  *
1633
- * class UserService extends Tag.Class('UserService') {
1694
+ * class UserService extends Tag.Service('UserService') {
1634
1695
  * constructor(private db: DatabaseService) {
1635
1696
  * super();
1636
1697
  * }
@@ -1639,11 +1700,118 @@ type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string
1639
1700
  * }
1640
1701
  *
1641
1702
  * const userService = service(UserService, async (ctx) =>
1642
- * new UserService(await ctx.get(DatabaseService))
1703
+ * new UserService(await ctx.resolve(DatabaseService))
1704
+ * );
1705
+ * ```
1706
+ */
1707
+ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
1708
+ /**
1709
+ * Specification for autoService.
1710
+ * Can be either a tuple of constructor parameters or an object with dependencies and finalizer.
1711
+ */
1712
+ type AutoServiceSpec<T extends ServiceTag<TagId, unknown>> = ServiceDepsTuple<T> | {
1713
+ dependencies: ServiceDepsTuple<T>;
1714
+ finalizer?: Finalizer<TagType<T>>;
1715
+ };
1716
+ /**
1717
+ * Creates a service layer with automatic dependency injection by inferring constructor parameters.
1718
+ *
1719
+ * This is a convenience function that automatically resolves constructor dependencies and passes
1720
+ * both DI-managed dependencies and static values to the service constructor in the correct order.
1721
+ * It eliminates the need to manually write factory functions for services with constructor dependencies.
1722
+ *
1723
+ * @template T - The ServiceTag representing the service class
1724
+ * @param tag - The service tag (must be a ServiceTag, not a ValueTag)
1725
+ * @param deps - Tuple of constructor parameters in order - mix of dependency tags and static values
1726
+ * @param finalizer - Optional cleanup function called when the container is destroyed
1727
+ * @returns A service layer that automatically handles dependency injection
1728
+ *
1729
+ * @example Simple service with dependencies
1730
+ * ```typescript
1731
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1732
+ * constructor(private url: string) {
1733
+ * super();
1734
+ * }
1735
+ * connect() { return `Connected to ${this.url}`; }
1736
+ * }
1737
+ *
1738
+ * class UserService extends Tag.Service('UserService') {
1739
+ * constructor(private db: DatabaseService, private timeout: number) {
1740
+ * super();
1741
+ * }
1742
+ * getUsers() { return this.db.query('SELECT * FROM users'); }
1743
+ * }
1744
+ *
1745
+ * // Automatically inject DatabaseService and pass static timeout value
1746
+ * const userService = autoService(UserService, [DatabaseService, 5000]);
1747
+ * ```
1748
+ *
1749
+ * @example Mixed dependencies and static values
1750
+ * ```typescript
1751
+ * class NotificationService extends Tag.Service('NotificationService') {
1752
+ * constructor(
1753
+ * private logger: LoggerService,
1754
+ * private apiKey: string,
1755
+ * private retries: number,
1756
+ * private cache: CacheService
1757
+ * ) {
1758
+ * super();
1759
+ * }
1760
+ * }
1761
+ *
1762
+ * // Mix of DI tags and static values in constructor order
1763
+ * const notificationService = autoService(NotificationService, [
1764
+ * LoggerService, // Will be resolved from container
1765
+ * 'secret-api-key', // Static string value
1766
+ * 3, // Static number value
1767
+ * CacheService // Will be resolved from container
1768
+ * ]);
1769
+ * ```
1770
+ *
1771
+ * @example Compared to manual service creation
1772
+ * ```typescript
1773
+ * // Manual approach (more verbose)
1774
+ * const userServiceManual = service(UserService, async (ctx) => {
1775
+ * const db = await ctx.resolve(DatabaseService);
1776
+ * return new UserService(db, 5000);
1777
+ * });
1778
+ *
1779
+ * // Auto approach (concise)
1780
+ * const userServiceAuto = autoService(UserService, [DatabaseService, 5000]);
1781
+ * ```
1782
+ *
1783
+ * @example With finalizer for cleanup
1784
+ * ```typescript
1785
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1786
+ * constructor(private connectionString: string) {
1787
+ * super();
1788
+ * }
1789
+ *
1790
+ * private connection: Connection | null = null;
1791
+ *
1792
+ * async connect() {
1793
+ * this.connection = await createConnection(this.connectionString);
1794
+ * }
1795
+ *
1796
+ * async disconnect() {
1797
+ * if (this.connection) {
1798
+ * await this.connection.close();
1799
+ * this.connection = null;
1800
+ * }
1801
+ * }
1802
+ * }
1803
+ *
1804
+ * // Service with automatic cleanup
1805
+ * const dbService = autoService(
1806
+ * DatabaseService,
1807
+ * {
1808
+ * dependencies: ['postgresql://localhost:5432/mydb'],
1809
+ * finalizer: (service) => service.disconnect() // Finalizer for cleanup
1810
+ * }
1643
1811
  * );
1644
1812
  * ```
1645
1813
  */
1646
- declare function service<T extends AnyTag>(serviceClass: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
1814
+ declare function autoService<T extends ServiceTag<TagId, unknown>>(tag: T, spec: AutoServiceSpec<T>): Layer<ServiceDependencies<T>, T>;
1647
1815
  //#endregion
1648
1816
  //#region src/value.d.ts
1649
1817
  /**
@@ -1664,6 +1832,6 @@ declare function service<T extends AnyTag>(serviceClass: T, spec: DependencySpec
1664
1832
  * const config = Layer.merge(apiKey, dbUrl);
1665
1833
  * ```
1666
1834
  */
1667
- declare function value<T, Id extends string | symbol>(tag: ValueTag<T, Id>, constantValue: T): Layer<never, ValueTag<T, Id>>;
1835
+ declare function value<Id extends TagId, T>(tag: ValueTag<Id, T>, constantValue: T): Layer<never, ValueTag<Id, T>>;
1668
1836
  //#endregion
1669
- export { type AnyLayer, type AnyTag, CircularDependencyError, type ClassTag, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type Factory, type Finalizer, type IContainer, type Inject, Layer, type PromiseOrValue, type ResolutionContext, type Scope, ScopedContainer, Tag, type TagType, UnknownDependencyError, type ValueTag, container, layer, scoped, service, value };
1837
+ export { type AnyLayer, type AnyTag, CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type DependencySpec, type Factory, type Finalizer, type IContainer, type Inject, InjectSource, Layer, type PromiseOrValue, type ResolutionContext, type Scope, ScopedContainer, type ServiceDependencies, type ServiceDepsTuple, type ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, autoService, layer, scoped, service, value };