sandly 0.1.0 → 0.2.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.
11
- *
12
- * The phantom property is optional to allow normal runtime values to be assignable.
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.
13
10
  *
14
- * @template T - A ValueTag type
15
- * @returns The value type with optional phantom tag metadata for dependency inference
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'.
16
13
  *
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 symbol used to identify the type of a tagged type within the dependency injection system.
20
+ * This symbol 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: unique symbol;
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;
315
+ isTag: (tag: unknown) => tag is AnyTag;
339
316
  };
317
+ /**
318
+ * Unique symbol 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: unique symbol;
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 c = 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 c = 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 c = Container.empty().register(
558
582
  * DatabaseConnection,
559
583
  * async () => {
560
584
  * const conn = new DatabaseConnection();
@@ -568,7 +592,10 @@ 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
+ };
572
599
  /**
573
600
  * Cache of instantiated dependencies as promises.
574
601
  * Ensures singleton behavior and supports concurrent access.
@@ -590,12 +617,13 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
590
617
  * @internal
591
618
  */
592
619
  protected isDestroyed: boolean;
620
+ static empty(): Container<never>;
593
621
  /**
594
622
  * Registers a dependency in the container with a factory function and optional finalizer.
595
623
  *
596
624
  * The factory function receives the current container instance and must return the
597
625
  * service instance (or a Promise of it). The container tracks the registration at
598
- * the type level, ensuring type safety for subsequent `.get()` calls.
626
+ * the type level, ensuring type safety for subsequent `.resolve()` calls.
599
627
  *
600
628
  * If a dependency is already registered, this method will override it unless the
601
629
  * dependency has already been instantiated, in which case it will throw an error.
@@ -610,11 +638,11 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
610
638
  *
611
639
  * @example Registering a simple service
612
640
  * ```typescript
613
- * class LoggerService extends Tag.Class('LoggerService') {
641
+ * class LoggerService extends Tag.Service('LoggerService') {
614
642
  * log(message: string) { console.log(message); }
615
643
  * }
616
644
  *
617
- * const c = container().register(
645
+ * const c = Container.empty().register(
618
646
  * LoggerService,
619
647
  * () => new LoggerService()
620
648
  * );
@@ -622,24 +650,24 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
622
650
  *
623
651
  * @example Registering with dependencies
624
652
  * ```typescript
625
- * class UserService extends Tag.Class('UserService') {
653
+ * class UserService extends Tag.Service('UserService') {
626
654
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
627
655
  * }
628
656
  *
629
- * const c = container()
657
+ * const c = Container.empty()
630
658
  * .register(DatabaseService, () => new DatabaseService())
631
659
  * .register(LoggerService, () => new LoggerService())
632
660
  * .register(UserService, async (ctx) =>
633
661
  * new UserService(
634
- * await ctx.get(DatabaseService),
635
- * await ctx.get(LoggerService)
662
+ * await ctx.resolve(DatabaseService),
663
+ * await ctx.resolve(LoggerService)
636
664
  * )
637
665
  * );
638
666
  * ```
639
667
  *
640
668
  * @example Overriding a dependency
641
669
  * ```typescript
642
- * const c = container()
670
+ * const c = Container.empty()
643
671
  * .register(DatabaseService, () => new DatabaseService())
644
672
  * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
645
673
  * ```
@@ -648,7 +676,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
648
676
  * ```typescript
649
677
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
650
678
  *
651
- * const c = container().register(
679
+ * const c = Container.empty().register(
652
680
  * ConfigTag,
653
681
  * () => ({ apiUrl: 'https://api.example.com' })
654
682
  * );
@@ -656,12 +684,12 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
656
684
  *
657
685
  * @example With finalizer for cleanup
658
686
  * ```typescript
659
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
687
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
660
688
  * async connect() { return; }
661
689
  * async close() { return; }
662
690
  * }
663
691
  *
664
- * const c = container().register(
692
+ * const c = Container.empty().register(
665
693
  * DatabaseConnection,
666
694
  * async () => {
667
695
  * const conn = new DatabaseConnection();
@@ -684,7 +712,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
684
712
  *
685
713
  * @example
686
714
  * ```typescript
687
- * const c = container().register(DatabaseService, () => new DatabaseService());
715
+ * const c = Container.empty().register(DatabaseService, () => new DatabaseService());
688
716
  * console.log(c.has(DatabaseService)); // true
689
717
  * ```
690
718
  */
@@ -715,10 +743,10 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
715
743
  *
716
744
  * @example Basic usage
717
745
  * ```typescript
718
- * const c = container()
746
+ * const c = Container.empty()
719
747
  * .register(DatabaseService, () => new DatabaseService());
720
748
  *
721
- * const db = await c.get(DatabaseService);
749
+ * const db = await c.resolve(DatabaseService);
722
750
  * db.query('SELECT * FROM users');
723
751
  * ```
724
752
  *
@@ -726,9 +754,9 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
726
754
  * ```typescript
727
755
  * // All three calls will receive the same instance
728
756
  * const [db1, db2, db3] = await Promise.all([
729
- * c.get(DatabaseService),
730
- * c.get(DatabaseService),
731
- * c.get(DatabaseService)
757
+ * c.resolve(DatabaseService),
758
+ * c.resolve(DatabaseService),
759
+ * c.resolve(DatabaseService)
732
760
  * ]);
733
761
  *
734
762
  * console.log(db1 === db2 === db3); // true
@@ -736,17 +764,58 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
736
764
  *
737
765
  * @example Dependency injection in factories
738
766
  * ```typescript
739
- * const c = container()
767
+ * const c = Container.empty()
740
768
  * .register(DatabaseService, () => new DatabaseService())
741
769
  * .register(UserService, async (ctx) => {
742
- * const db = await ctx.get(DatabaseService);
770
+ * const db = await ctx.resolve(DatabaseService);
743
771
  * return new UserService(db);
744
772
  * });
745
773
  *
746
- * const userService = await c.get(UserService);
774
+ * const userService = await c.resolve(UserService);
747
775
  * ```
748
776
  */
749
- get<T extends TReg>(tag: T): Promise<TagType<T>>;
777
+ resolve<T extends TReg>(tag: T): Promise<TagType<T>>;
778
+ /**
779
+ * Resolves multiple dependencies concurrently using Promise.all.
780
+ *
781
+ * This method takes a variable number of dependency tags and resolves all of them concurrently,
782
+ * returning a tuple with the resolved instances in the same order as the input tags.
783
+ * The method maintains all the same guarantees as the individual resolve method:
784
+ * singleton behavior, circular dependency detection, and proper error handling.
785
+ *
786
+ * @template T - The tuple type of dependency tags to resolve
787
+ * @param tags - Variable number of dependency tags to resolve
788
+ * @returns Promise resolving to a tuple of service instances in the same order
789
+ * @throws {ContainerDestroyedError} If the container has been destroyed
790
+ * @throws {UnknownDependencyError} If any dependency is not registered
791
+ * @throws {CircularDependencyError} If a circular dependency is detected
792
+ * @throws {DependencyCreationError} If any factory function throws an error
793
+ *
794
+ * @example Basic usage
795
+ * ```typescript
796
+ * const c = Container.empty()
797
+ * .register(DatabaseService, () => new DatabaseService())
798
+ * .register(LoggerService, () => new LoggerService());
799
+ *
800
+ * const [db, logger] = await c.resolveAll(DatabaseService, LoggerService);
801
+ * ```
802
+ *
803
+ * @example Mixed tag types
804
+ * ```typescript
805
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
806
+ * const c = Container.empty()
807
+ * .register(ApiKeyTag, () => 'secret-key')
808
+ * .register(UserService, () => new UserService());
809
+ *
810
+ * const [apiKey, userService] = await c.resolveAll(ApiKeyTag, UserService);
811
+ * ```
812
+ *
813
+ * @example Empty array
814
+ * ```typescript
815
+ * const results = await c.resolveAll(); // Returns empty array
816
+ * ```
817
+ */
818
+ resolveAll<const T extends readonly TReg[]>(...tags: T): Promise<{ [K in keyof T]: TagType<T[K]> }>;
750
819
  /**
751
820
  * Copies all registrations from this container to a target container.
752
821
  *
@@ -771,10 +840,10 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
771
840
  *
772
841
  * @example Merging containers
773
842
  * ```typescript
774
- * const container1 = container()
843
+ * const container1 = Container.empty()
775
844
  * .register(DatabaseService, () => new DatabaseService());
776
845
  *
777
- * const container2 = container()
846
+ * const container2 = Container.empty()
778
847
  * .register(UserService, () => new UserService());
779
848
  *
780
849
  * const merged = container1.merge(container2);
@@ -803,7 +872,7 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
803
872
  *
804
873
  * @example Basic cleanup
805
874
  * ```typescript
806
- * const c = container()
875
+ * const c = Container.empty()
807
876
  * .register(DatabaseConnection,
808
877
  * async () => {
809
878
  * const conn = new DatabaseConnection();
@@ -813,12 +882,12 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
813
882
  * (conn) => conn.disconnect() // Finalizer
814
883
  * );
815
884
  *
816
- * const db = await c.get(DatabaseConnection);
885
+ * const db = await c.resolve(DatabaseConnection);
817
886
  * await c.destroy(); // Calls conn.disconnect(), container becomes unusable
818
887
  *
819
888
  * // This will throw an error
820
889
  * try {
821
- * await c.get(DatabaseConnection);
890
+ * await c.resolve(DatabaseConnection);
822
891
  * } catch (error) {
823
892
  * console.log(error.message); // "Cannot resolve dependencies from a destroyed container"
824
893
  * }
@@ -826,9 +895,9 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
826
895
  *
827
896
  * @example Application shutdown
828
897
  * ```typescript
829
- * const appContainer = container()
898
+ * const appContainer Container.empty
830
899
  * .register(DatabaseService, () => new DatabaseService())
831
- * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.get(DatabaseService)));
900
+ * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.resolve(DatabaseService)));
832
901
  *
833
902
  * // During application shutdown
834
903
  * process.on('SIGTERM', async () => {
@@ -855,32 +924,6 @@ declare class Container<in TReg extends AnyTag = never> implements IContainer<TR
855
924
  */
856
925
  destroy(): Promise<void>;
857
926
  }
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
927
  //#endregion
885
928
  //#region src/errors.d.ts
886
929
  type ErrorProps = {
@@ -917,7 +960,7 @@ declare class BaseError extends Error {
917
960
  * @example Catching DI errors
918
961
  * ```typescript
919
962
  * try {
920
- * await container.get(SomeService);
963
+ * await container.resolve(SomeService);
921
964
  * } catch (error) {
922
965
  * if (error instanceof ContainerError) {
923
966
  * console.error('DI Error:', error.message);
@@ -938,7 +981,7 @@ declare class DependencyAlreadyInstantiatedError extends ContainerError {}
938
981
  /**
939
982
  * Error thrown when attempting to use a container that has been destroyed.
940
983
  *
941
- * This error occurs when calling `container.get()`, `container.register()`, or `container.destroy()`
984
+ * This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
942
985
  * on a container that has already been destroyed. It indicates a programming error where the container
943
986
  * is being used after it has been destroyed.
944
987
  */
@@ -946,16 +989,16 @@ declare class ContainerDestroyedError extends ContainerError {}
946
989
  /**
947
990
  * Error thrown when attempting to retrieve a dependency that hasn't been registered.
948
991
  *
949
- * This error occurs when calling `container.get(Tag)` for a tag that was never
992
+ * This error occurs when calling `container.resolve(Tag)` for a tag that was never
950
993
  * registered via `container.register()`. It indicates a programming error where
951
994
  * the dependency setup is incomplete.
952
995
  *
953
996
  * @example
954
997
  * ```typescript
955
- * const c = container(); // Empty container
998
+ * const c = Container.empty(); // Empty container
956
999
  *
957
1000
  * try {
958
- * await c.get(UnregisteredService); // This will throw
1001
+ * await c.resolve(UnregisteredService); // This will throw
959
1002
  * } catch (error) {
960
1003
  * if (error instanceof UnknownDependencyError) {
961
1004
  * console.error('Missing dependency:', error.message);
@@ -981,19 +1024,19 @@ declare class UnknownDependencyError extends ContainerError {
981
1024
  *
982
1025
  * @example Circular dependency scenario
983
1026
  * ```typescript
984
- * class ServiceA extends Tag.Class('ServiceA') {}
985
- * class ServiceB extends Tag.Class('ServiceB') {}
1027
+ * class ServiceA extends Tag.Service('ServiceA') {}
1028
+ * class ServiceB extends Tag.Service('ServiceB') {}
986
1029
  *
987
- * const c = container()
1030
+ * const c = Container.empty()
988
1031
  * .register(ServiceA, async (ctx) =>
989
- * new ServiceA(await ctx.get(ServiceB)) // Depends on B
1032
+ * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
990
1033
  * )
991
1034
  * .register(ServiceB, async (ctx) =>
992
- * new ServiceB(await ctx.get(ServiceA)) // Depends on A - CIRCULAR!
1035
+ * new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
993
1036
  * );
994
1037
  *
995
1038
  * try {
996
- * await c.get(ServiceA);
1039
+ * await c.resolve(ServiceA);
997
1040
  * } catch (error) {
998
1041
  * if (error instanceof CircularDependencyError) {
999
1042
  * console.error('Circular dependency:', error.message);
@@ -1020,14 +1063,14 @@ declare class CircularDependencyError extends ContainerError {
1020
1063
  *
1021
1064
  * @example Factory throwing error
1022
1065
  * ```typescript
1023
- * class DatabaseService extends Tag.Class('DatabaseService') {}
1066
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
1024
1067
  *
1025
- * const c = container().register(DatabaseService, () => {
1068
+ * const c = Container.empty().register(DatabaseService, () => {
1026
1069
  * throw new Error('Database connection failed');
1027
1070
  * });
1028
1071
  *
1029
1072
  * try {
1030
- * await c.get(DatabaseService);
1073
+ * await c.resolve(DatabaseService);
1031
1074
  * } catch (error) {
1032
1075
  * if (error instanceof DependencyCreationError) {
1033
1076
  * console.error('Failed to create:', error.message);
@@ -1077,18 +1120,13 @@ declare class DependencyFinalizationError extends ContainerError {
1077
1120
  //#endregion
1078
1121
  //#region src/layer.d.ts
1079
1122
  /**
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.
1123
+ * The most generic layer type that accepts any concrete layer.
1090
1124
  */
1091
- type AnyLayer = Layer<never, AnyTag>;
1125
+ type AnyLayer = Layer<any, any>;
1126
+ /**
1127
+ * The type ID for the Layer interface.
1128
+ */
1129
+ declare const LayerTypeId: unique symbol;
1092
1130
  /**
1093
1131
  * A dependency layer represents a reusable, composable unit of dependency registrations.
1094
1132
  * Layers allow you to organize your dependency injection setup into logical groups
@@ -1098,12 +1136,12 @@ type AnyLayer = Layer<never, AnyTag>;
1098
1136
  *
1099
1137
  * The Layer interface uses TypeScript's variance annotations to enable safe substitutability:
1100
1138
  *
1101
- * ### TRequires (contravariant with `in`)
1139
+ * ### TRequires (covariant)
1102
1140
  * A layer requiring fewer dependencies can substitute one requiring more:
1103
1141
  * - `Layer<never, X>` can be used where `Layer<A | B, X>` is expected
1104
1142
  * - Intuition: A service that needs nothing is more flexible than one that needs specific deps
1105
1143
  *
1106
- * ### TProvides (covariant with `out`)
1144
+ * ### TProvides (contravariant)
1107
1145
  * A layer providing more services can substitute one providing fewer:
1108
1146
  * - `Layer<X, A | B>` can be used where `Layer<X, A>` is expected
1109
1147
  * - Intuition: A service that gives you extra things is compatible with expecting fewer things
@@ -1115,7 +1153,7 @@ type AnyLayer = Layer<never, AnyTag>;
1115
1153
  * ```typescript
1116
1154
  * import { layer, Tag, container } from 'sandly';
1117
1155
  *
1118
- * class DatabaseService extends Tag.Class('DatabaseService') {
1156
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1119
1157
  * query() { return 'data'; }
1120
1158
  * }
1121
1159
  *
@@ -1125,10 +1163,10 @@ type AnyLayer = Layer<never, AnyTag>;
1125
1163
  * );
1126
1164
  *
1127
1165
  * // Apply the layer to a container
1128
- * const c = container();
1166
+ * const c = Container.empty();
1129
1167
  * const finalContainer = databaseLayer.register(c);
1130
1168
  *
1131
- * const db = await finalContainer.get(DatabaseService);
1169
+ * const db = await finalContainer.resolve(DatabaseService);
1132
1170
  * ```
1133
1171
  *
1134
1172
  * @example Layer composition with variance
@@ -1136,18 +1174,19 @@ type AnyLayer = Layer<never, AnyTag>;
1136
1174
  * // Layer that requires DatabaseService and provides UserService
1137
1175
  * const userLayer = layer<typeof DatabaseService, typeof UserService>((container) =>
1138
1176
  * container.register(UserService, async (ctx) =>
1139
- * new UserService(await ctx.get(DatabaseService))
1177
+ * new UserService(await ctx.resolve(DatabaseService))
1140
1178
  * )
1141
1179
  * );
1142
1180
  *
1143
1181
  * // Compose layers: provide database layer to user layer
1144
1182
  * 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
1183
  * ```
1149
1184
  */
1150
- interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTag = never> {
1185
+ interface Layer<TRequires extends AnyTag, TProvides extends AnyTag> {
1186
+ readonly [LayerTypeId]?: {
1187
+ readonly _TRequires: Covariant<TRequires>;
1188
+ readonly _TProvides: Contravariant<TProvides>;
1189
+ };
1151
1190
  /**
1152
1191
  * Applies this layer's registrations to the given container.
1153
1192
  *
@@ -1165,13 +1204,13 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1165
1204
  *
1166
1205
  * @example Basic usage
1167
1206
  * ```typescript
1168
- * const c = container();
1207
+ * const c = Container.empty();
1169
1208
  * const updatedContainer = myLayer.register(c);
1170
1209
  * ```
1171
1210
  *
1172
1211
  * @example With existing services preserved
1173
1212
  * ```typescript
1174
- * const baseContainer = container()
1213
+ * const baseContainer = Container.empty()
1175
1214
  * .register(ExistingService, () => new ExistingService());
1176
1215
  *
1177
1216
  * const enhanced = myLayer.register(baseContainer);
@@ -1286,7 +1325,7 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1286
1325
  * ```typescript
1287
1326
  * import { layer, Tag } from 'sandly';
1288
1327
  *
1289
- * class DatabaseService extends Tag.Class('DatabaseService') {
1328
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1290
1329
  * constructor(private url: string = 'sqlite://memory') {}
1291
1330
  * query() { return 'data'; }
1292
1331
  * }
@@ -1311,15 +1350,15 @@ interface Layer<in TRequires extends AnyTag = never, out TProvides extends AnyTa
1311
1350
  * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
1312
1351
  * (container) =>
1313
1352
  * container
1314
- * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.get(ConfigTag)))
1315
- * .register(CacheService, async (ctx) => new CacheService(await ctx.get(ConfigTag)))
1353
+ * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
1354
+ * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
1316
1355
  * );
1317
1356
  *
1318
1357
  * // Service layer (requires infrastructure)
1319
1358
  * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
1320
1359
  * (container) =>
1321
1360
  * container.register(UserService, async (ctx) =>
1322
- * new UserService(await ctx.get(DatabaseService), await ctx.get(CacheService))
1361
+ * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
1323
1362
  * )
1324
1363
  * );
1325
1364
  *
@@ -1338,7 +1377,7 @@ declare function layer<TRequires extends AnyTag = never, TProvides extends AnyTa
1338
1377
  *
1339
1378
  * @internal
1340
1379
  */
1341
- type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<infer R, AnyTag> ? R : never }[number];
1380
+ type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<infer R, any> ? R : never }[number];
1342
1381
  /**
1343
1382
  * Helper type that extracts the union of all provisions from an array of layers.
1344
1383
  * Used by Layer.mergeAll() to compute the correct provision type for the merged layer.
@@ -1349,7 +1388,7 @@ type UnionOfRequires<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] ext
1349
1388
  *
1350
1389
  * @internal
1351
1390
  */
1352
- type UnionOfProvides<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<never, infer P> ? P : never }[number];
1391
+ type UnionOfProvides<T extends readonly AnyLayer[]> = { [K in keyof T]: T[K] extends Layer<any, infer P> ? P : never }[number];
1353
1392
  /**
1354
1393
  * Utility object containing helper functions for working with layers.
1355
1394
  */
@@ -1370,7 +1409,7 @@ declare const Layer: {
1370
1409
  * .merge(serviceLayer);
1371
1410
  * ```
1372
1411
  */
1373
- empty(): Layer;
1412
+ empty(): Layer<never, never>;
1374
1413
  /**
1375
1414
  * Merges multiple layers at once in a type-safe way.
1376
1415
  * This is equivalent to chaining `.merge()` calls but more convenient for multiple layers.
@@ -1461,11 +1500,12 @@ declare const Layer: {
1461
1500
  //#endregion
1462
1501
  //#region src/scoped-container.d.ts
1463
1502
  type Scope = string | symbol;
1464
- declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<TReg> {
1503
+ declare class ScopedContainer<TReg extends AnyTag> extends Container<TReg> {
1465
1504
  readonly scope: Scope;
1466
1505
  private parent;
1467
1506
  private readonly children;
1468
- constructor(parent: IContainer<TReg> | null, scope: Scope);
1507
+ protected constructor(parent: IContainer<TReg> | null, scope: Scope);
1508
+ static empty(scope: Scope): ScopedContainer<never>;
1469
1509
  /**
1470
1510
  * Registers a dependency in the scoped container.
1471
1511
  *
@@ -1496,7 +1536,7 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1496
1536
  * 3. Otherwise, delegate to parent scope
1497
1537
  * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
1498
1538
  */
1499
- get<T extends TReg>(tag: T): Promise<TagType<T>>;
1539
+ resolve<T extends TReg>(tag: T): Promise<TagType<T>>;
1500
1540
  /**
1501
1541
  * Destroys this scoped container and its children, preserving the container structure for reuse.
1502
1542
  *
@@ -1548,7 +1588,7 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1548
1588
  * ```typescript
1549
1589
  * import { container, scoped } from 'sandly';
1550
1590
  *
1551
- * const appContainer = container()
1591
+ * const appContainer = Container.empty()
1552
1592
  * .register(DatabaseService, () => new DatabaseService())
1553
1593
  * .register(ConfigService, () => new ConfigService());
1554
1594
  *
@@ -1560,10 +1600,10 @@ declare class ScopedContainer<in TReg extends AnyTag = never> extends Container<
1560
1600
  *
1561
1601
  * @example Copying complex registrations
1562
1602
  * ```typescript
1563
- * const baseContainer = container()
1603
+ * const baseContainer = Container.empty()
1564
1604
  * .register(DatabaseService, () => new DatabaseService())
1565
1605
  * .register(UserService, {
1566
- * factory: async (ctx) => new UserService(await ctx.get(DatabaseService)),
1606
+ * factory: async (ctx) => new UserService(await ctx.resolve(DatabaseService)),
1567
1607
  * finalizer: (service) => service.cleanup()
1568
1608
  * });
1569
1609
  *
@@ -1575,49 +1615,58 @@ declare function scoped<TReg extends AnyTag>(container: Container<TReg>, scope:
1575
1615
  //#endregion
1576
1616
  //#region src/service.d.ts
1577
1617
  /**
1578
- * Extracts constructor parameter types from a ClassTag.
1618
+ * Extracts constructor parameter types from a ServiceTag.
1579
1619
  * Only parameters that extend AnyTag are considered as dependencies.
1620
+ * @internal
1621
+ */
1622
+ type ConstructorParams<T extends ServiceTag<TagId, unknown>> = T extends (new (...args: infer A) => unknown) ? A : never;
1623
+ /**
1624
+ * Extracts only dependency tags from a constructor parameter list.
1625
+ * Filters out non‑DI parameters.
1626
+ *
1627
+ * Example:
1628
+ * [DatabaseService, Inject<typeof ConfigTag>, number]
1629
+ * → typeof DatabaseService | typeof ConfigTag
1630
+ * @internal
1580
1631
  */
1581
- type ConstructorParams<T extends ClassTag<unknown, string | symbol>> = T extends (new (...args: infer A) => unknown) ? A : never;
1632
+ type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1633
+ readonly [ServiceTagIdKey]: infer Id;
1634
+ } ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
1582
1635
  /**
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.
1636
+ * Produces an ordered tuple of constructor parameters
1637
+ * where dependency parameters are replaced with their tag types,
1638
+ * while non‑DI parameters are preserved as‑is.
1639
+ * @internal
1585
1640
  */
1586
- type InstanceToConstructorType<T> = T extends {
1587
- readonly [TagId]: infer Id;
1588
- } ? Id extends string | symbol ? ClassTag<T, Id> : never : never;
1641
+
1589
1642
  /**
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).
1643
+ * Union of all dependency tags a ServiceTag constructor requires.
1644
+ * Filters out non‑DI parameters.
1593
1645
  */
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];
1646
+ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstructorDeps<ConstructorParams<T>> extends AnyTag ? ExtractConstructorDeps<ConstructorParams<T>> : never;
1597
1647
  /**
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.
1648
+ * Ordered tuple of dependency tags (and other constructor params)
1649
+ * inferred from a ServiceTag’s constructor.
1601
1650
  */
1602
- type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string | symbol> ? FilterTags<ConstructorParams<T>> extends AnyTag ? FilterTags<ConstructorParams<T>> : never : never;
1651
+
1603
1652
  /**
1604
- * Creates a service layer from any tag type (ClassTag or ValueTag) with optional parameters.
1653
+ * Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
1605
1654
  *
1606
- * For ClassTag services:
1655
+ * For ServiceTag services:
1607
1656
  * - Dependencies are automatically inferred from constructor parameters
1608
1657
  * - The factory function must handle dependency injection by resolving dependencies from the container
1609
1658
  *
1610
1659
  * For ValueTag services:
1611
1660
  * - No constructor dependencies are needed since they don't have constructors
1612
1661
  *
1613
- * @template T - The tag representing the service (ClassTag or ValueTag)
1614
- * @param serviceClass - The tag (ClassTag or ValueTag)
1662
+ * @template T - The tag representing the service (ServiceTag or ValueTag)
1663
+ * @param tag - The tag (ServiceTag or ValueTag)
1615
1664
  * @param factory - Factory function for service instantiation with container
1616
1665
  * @returns The service layer
1617
1666
  *
1618
1667
  * @example Simple service without dependencies
1619
1668
  * ```typescript
1620
- * class LoggerService extends Tag.Class('LoggerService') {
1669
+ * class LoggerService extends Tag.Service('LoggerService') {
1621
1670
  * log(message: string) { console.log(message); }
1622
1671
  * }
1623
1672
  *
@@ -1626,11 +1675,11 @@ type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string
1626
1675
  *
1627
1676
  * @example Service with dependencies
1628
1677
  * ```typescript
1629
- * class DatabaseService extends Tag.Class('DatabaseService') {
1678
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1630
1679
  * query() { return []; }
1631
1680
  * }
1632
1681
  *
1633
- * class UserService extends Tag.Class('UserService') {
1682
+ * class UserService extends Tag.Service('UserService') {
1634
1683
  * constructor(private db: DatabaseService) {
1635
1684
  * super();
1636
1685
  * }
@@ -1639,11 +1688,107 @@ type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown, string
1639
1688
  * }
1640
1689
  *
1641
1690
  * const userService = service(UserService, async (ctx) =>
1642
- * new UserService(await ctx.get(DatabaseService))
1691
+ * new UserService(await ctx.resolve(DatabaseService))
1692
+ * );
1693
+ * ```
1694
+ */
1695
+ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
1696
+ /**
1697
+ * Creates a service layer with automatic dependency injection by inferring constructor parameters.
1698
+ *
1699
+ * This is a convenience function that automatically resolves constructor dependencies and passes
1700
+ * both DI-managed dependencies and static values to the service constructor in the correct order.
1701
+ * It eliminates the need to manually write factory functions for services with constructor dependencies.
1702
+ *
1703
+ * @template T - The ServiceTag representing the service class
1704
+ * @param tag - The service tag (must be a ServiceTag, not a ValueTag)
1705
+ * @param deps - Tuple of constructor parameters in order - mix of dependency tags and static values
1706
+ * @param finalizer - Optional cleanup function called when the container is destroyed
1707
+ * @returns A service layer that automatically handles dependency injection
1708
+ *
1709
+ * @example Simple service with dependencies
1710
+ * ```typescript
1711
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1712
+ * constructor(private url: string) {
1713
+ * super();
1714
+ * }
1715
+ * connect() { return `Connected to ${this.url}`; }
1716
+ * }
1717
+ *
1718
+ * class UserService extends Tag.Service('UserService') {
1719
+ * constructor(private db: DatabaseService, private timeout: number) {
1720
+ * super();
1721
+ * }
1722
+ * getUsers() { return this.db.query('SELECT * FROM users'); }
1723
+ * }
1724
+ *
1725
+ * // Automatically inject DatabaseService and pass static timeout value
1726
+ * const userService = autoService(UserService, [DatabaseService, 5000]);
1727
+ * ```
1728
+ *
1729
+ * @example Mixed dependencies and static values
1730
+ * ```typescript
1731
+ * class NotificationService extends Tag.Service('NotificationService') {
1732
+ * constructor(
1733
+ * private logger: LoggerService,
1734
+ * private apiKey: string,
1735
+ * private retries: number,
1736
+ * private cache: CacheService
1737
+ * ) {
1738
+ * super();
1739
+ * }
1740
+ * }
1741
+ *
1742
+ * // Mix of DI tags and static values in constructor order
1743
+ * const notificationService = autoService(NotificationService, [
1744
+ * LoggerService, // Will be resolved from container
1745
+ * 'secret-api-key', // Static string value
1746
+ * 3, // Static number value
1747
+ * CacheService // Will be resolved from container
1748
+ * ]);
1749
+ * ```
1750
+ *
1751
+ * @example Compared to manual service creation
1752
+ * ```typescript
1753
+ * // Manual approach (more verbose)
1754
+ * const userServiceManual = service(UserService, async (ctx) => {
1755
+ * const db = await ctx.resolve(DatabaseService);
1756
+ * return new UserService(db, 5000);
1757
+ * });
1758
+ *
1759
+ * // Auto approach (concise)
1760
+ * const userServiceAuto = autoService(UserService, [DatabaseService, 5000]);
1761
+ * ```
1762
+ *
1763
+ * @example With finalizer for cleanup
1764
+ * ```typescript
1765
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1766
+ * constructor(private connectionString: string) {
1767
+ * super();
1768
+ * }
1769
+ *
1770
+ * private connection: Connection | null = null;
1771
+ *
1772
+ * async connect() {
1773
+ * this.connection = await createConnection(this.connectionString);
1774
+ * }
1775
+ *
1776
+ * async disconnect() {
1777
+ * if (this.connection) {
1778
+ * await this.connection.close();
1779
+ * this.connection = null;
1780
+ * }
1781
+ * }
1782
+ * }
1783
+ *
1784
+ * // Service with automatic cleanup
1785
+ * const dbService = autoService(
1786
+ * DatabaseService,
1787
+ * ['postgresql://localhost:5432/mydb'],
1788
+ * (service) => service.disconnect() // Finalizer for cleanup
1643
1789
  * );
1644
1790
  * ```
1645
1791
  */
1646
- declare function service<T extends AnyTag>(serviceClass: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
1647
1792
  //#endregion
1648
1793
  //#region src/value.d.ts
1649
1794
  /**
@@ -1664,6 +1809,6 @@ declare function service<T extends AnyTag>(serviceClass: T, spec: DependencySpec
1664
1809
  * const config = Layer.merge(apiKey, dbUrl);
1665
1810
  * ```
1666
1811
  */
1667
- declare function value<T, Id extends string | symbol>(tag: ValueTag<T, Id>, constantValue: T): Layer<never, ValueTag<T, Id>>;
1812
+ declare function value<Id extends TagId, T>(tag: ValueTag<Id, T>, constantValue: T): Layer<never, ValueTag<Id, T>>;
1668
1813
  //#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 };
1814
+ 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 ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, layer, scoped, service, value };