sandly 0.0.2 → 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.
Files changed (4) hide show
  1. package/README.md +19 -16
  2. package/dist/index.d.ts +1029 -470
  3. package/dist/index.js +572 -417
  4. package/package.json +75 -76
package/dist/index.d.ts CHANGED
@@ -1,10 +1,27 @@
1
1
  //#region src/tag.d.ts
2
2
  /**
3
- * Internal symbol used to identify tagged types within the dependency injection system.
4
- * This symbol is used as a property key to attach metadata to both value tags and class tags.
3
+ * Type representing a tag identifier (string or symbol).
5
4
  * @internal
6
5
  */
7
- declare const TagId: "__tag_id__";
6
+ type TagId = string | symbol;
7
+ /**
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.
10
+ *
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
+ *
14
+ * @internal
15
+ */
16
+ declare const ValueTagIdKey = "__sandly/ValueTagIdKey__";
17
+ declare const ServiceTagIdKey = "__sandly/ServiceTagIdKey__";
18
+ /**
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.
22
+ * @internal
23
+ */
24
+ declare const TagTypeKey: unique symbol;
8
25
  /**
9
26
  * Type representing a value-based dependency tag.
10
27
  *
@@ -18,31 +35,30 @@ declare const TagId: "__tag_id__";
18
35
  * @example
19
36
  * ```typescript
20
37
  * // Creates a value tag for string configuration
21
- * const ApiKeyTag: ValueTag<string, 'apiKey'> = Tag.of('apiKey')<string>();
38
+ * const ApiKeyTag: ValueTag<'apiKey', string> = Tag.of('apiKey')<string>();
22
39
  *
23
40
  * // Register in container
24
41
  * container.register(ApiKeyTag, () => 'my-secret-key');
25
42
  * ```
26
43
  */
27
- type ValueTag<T, Id extends string | symbol> = Readonly<{
28
- readonly [TagId]: Id;
29
- /** @internal Phantom type to carry T */
30
- readonly __type: T;
31
- }>;
44
+ interface ValueTag<Id extends TagId, T> {
45
+ readonly [ValueTagIdKey]: Id;
46
+ readonly [TagTypeKey]: T;
47
+ }
32
48
  /**
33
49
  * Type representing a class-based dependency tag.
34
50
  *
35
- * 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
36
52
  * and the constructor for the service. They extend regular classes with tag metadata
37
53
  * that the DI system uses for identification and type safety.
38
54
  *
39
- * @template T - The type of instances created by this tagged class
40
55
  * @template Id - The unique identifier for this tag (string or symbol)
56
+ * @template T - The type of instances created by this tagged class
41
57
  *
42
58
  * @example
43
59
  * ```typescript
44
60
  * // Creates a tagged class
45
- * class UserService extends Tag.Class('UserService') {
61
+ * class UserService extends Tag.Service('UserService') {
46
62
  * getUsers() { return []; }
47
63
  * }
48
64
  *
@@ -50,25 +66,53 @@ type ValueTag<T, Id extends string | symbol> = Readonly<{
50
66
  * container.register(UserService, () => new UserService());
51
67
  * ```
52
68
  *
53
- * @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
54
70
  */
55
- type TaggedClass<T, Id extends string | symbol> = {
71
+ interface ServiceTag<Id extends TagId, T> {
56
72
  new (...args: any[]): T & {
57
- readonly [TagId]: Id;
73
+ readonly [ServiceTagIdKey]: Id;
58
74
  };
59
- readonly [TagId]: Id;
60
- };
75
+ readonly [ServiceTagIdKey]: Id;
76
+ }
61
77
  /**
62
- * Type representing a class-based dependency tag.
78
+ * Utility type that extracts the service type from any dependency tag.
79
+ *
80
+ * This type is essential for type inference throughout the DI system, allowing
81
+ * the container and layers to automatically determine what type of service
82
+ * a given tag represents without manual type annotations.
63
83
  *
64
- * This type is a shortcut for TaggedClass<T, string | symbol>.
84
+ * @template T - Any dependency tag (ValueTag or ServiceTag)
85
+ * @returns The service type that the tag represents
65
86
  *
66
- * @template T - The type of instances created by this tagged class
67
- * @returns A tagged class with a string or symbol identifier
87
+ * @example With value tags
88
+ * ```typescript
89
+ * const StringTag = Tag.of('myString')<string>();
90
+ * const ConfigTag = Tag.of('config')<{ apiKey: string }>();
91
+ *
92
+ * type StringService = TagType<typeof StringTag>; // string
93
+ * type ConfigService = TagType<typeof ConfigTag>; // { apiKey: string }
94
+ * ```
95
+ *
96
+ * @example With service tags
97
+ * ```typescript
98
+ * class UserService extends Tag.Service('UserService') {
99
+ * getUsers() { return []; }
100
+ * }
101
+ *
102
+ * type UserServiceType = TagType<typeof UserService>; // UserService
103
+ * ```
104
+ *
105
+ * @example Used in container methods
106
+ * ```typescript
107
+ * // The container uses TagType internally for type inference
108
+ * container.register(StringTag, () => 'hello'); // Factory must return string
109
+ * container.register(UserService, () => new UserService()); // Factory must return UserService
68
110
  *
69
- * @internal - Users should use Tag.Class() instead of working with this type directly
111
+ * const str: string = await container.resolve(StringTag); // Automatically typed as string
112
+ * const user: UserService = await container.resolve(UserService); // Automatically typed as UserService
113
+ * ```
70
114
  */
71
- type ClassTag<T> = TaggedClass<T, string | symbol>;
115
+ type TagType<TTag extends AnyTag> = TTag extends ValueTag<any, infer T> ? T : TTag extends ServiceTag<any, infer T> ? T : never;
72
116
  /**
73
117
  * Union type representing any valid dependency tag in the system.
74
118
  *
@@ -84,15 +128,15 @@ type ClassTag<T> = TaggedClass<T, string | symbol>;
84
128
  *
85
129
  * @example Class tag
86
130
  * ```typescript
87
- * class DatabaseService extends Tag.Class('DatabaseService') {}
131
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
88
132
  * // DatabaseService satisfies AnyTag
89
133
  * ```
90
134
  */
91
- type AnyTag = ValueTag<any, string | symbol> | TaggedClass<any, string | symbol>;
135
+ type AnyTag = ValueTag<TagId, any> | ServiceTag<TagId, any>;
92
136
  /**
93
137
  * Utility object containing factory functions for creating dependency tags.
94
138
  *
95
- * 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
96
140
  * used throughout the dependency injection system. It's the main entry point for
97
141
  * defining dependencies in a type-safe way.
98
142
  */
@@ -152,7 +196,7 @@ declare const Tag: {
152
196
  * }));
153
197
  * ```
154
198
  */
155
- of: <Id extends string | symbol>(id: Id) => <T>() => ValueTag<T, Id>;
199
+ of: <Id extends TagId>(id: Id) => <T>() => ValueTag<Id, T>;
156
200
  /**
157
201
  * Creates an anonymous value tag with a unique symbol identifier.
158
202
  *
@@ -186,7 +230,7 @@ declare const Tag: {
186
230
  * console.log(ConfigA === ConfigB); // false
187
231
  * ```
188
232
  */
189
- for: <T>() => ValueTag<T, symbol>;
233
+ for: <T>() => ValueTag<symbol, T>;
190
234
  /**
191
235
  * Creates a base class that can be extended to create service classes with dependency tags.
192
236
  *
@@ -200,7 +244,7 @@ declare const Tag: {
200
244
  *
201
245
  * @example Basic service class
202
246
  * ```typescript
203
- * class UserService extends Tag.Class('UserService') {
247
+ * class UserService extends Tag.Service('UserService') {
204
248
  * getUsers() {
205
249
  * return ['alice', 'bob'];
206
250
  * }
@@ -211,11 +255,11 @@ declare const Tag: {
211
255
  *
212
256
  * @example Service with dependencies
213
257
  * ```typescript
214
- * class DatabaseService extends Tag.Class('DatabaseService') {
258
+ * class DatabaseService extends Tag.Service('DatabaseService') {
215
259
  * query(sql: string) { return []; }
216
260
  * }
217
261
  *
218
- * class UserRepository extends Tag.Class('UserRepository') {
262
+ * class UserRepository extends Tag.Service('UserRepository') {
219
263
  * constructor(private db: DatabaseService) {
220
264
  * super();
221
265
  * }
@@ -227,25 +271,23 @@ declare const Tag: {
227
271
  *
228
272
  * container
229
273
  * .register(DatabaseService, () => new DatabaseService())
230
- * .register(UserRepository, async (c) =>
231
- * new UserRepository(await c.get(DatabaseService))
274
+ * .register(UserRepository, async (ctx) =>
275
+ * new UserRepository(await ctx.resolve(DatabaseService))
232
276
  * );
233
277
  * ```
234
278
  *
235
279
  * @example With symbol identifiers
236
280
  * ```typescript
237
- * const SERVICE_ID = Symbol('InternalService');
281
+ * const ServiceId = Symbol('InternalService');
238
282
  *
239
- * class InternalService extends Tag.Class(SERVICE_ID) {
283
+ * class InternalService extends Tag.Service(ServiceId) {
240
284
  * doInternalWork() { return 'work'; }
241
285
  * }
242
286
  * ```
243
287
  */
244
- Class: <Id extends string | symbol>(id: Id) => TaggedClass<{
245
- /** @internal */
246
- readonly __type: unknown;
247
- readonly __tag_id__: Id;
248
- }, Id>;
288
+ Service: <Id extends TagId>(id: Id) => ServiceTag<Id, {
289
+ readonly "__sandly/ServiceTagIdKey__": Id;
290
+ }>;
249
291
  /**
250
292
  * Extracts the string representation of a tag's identifier.
251
293
  *
@@ -253,14 +295,14 @@ declare const Tag: {
253
295
  * whether it's a string-based or symbol-based tag. Primarily used internally
254
296
  * for error messages and debugging.
255
297
  *
256
- * @param tag - Any valid dependency tag (value tag or class tag)
298
+ * @param tag - Any valid dependency tag (value tag or service tag)
257
299
  * @returns String representation of the tag's identifier
258
300
  *
259
301
  * @example
260
302
  * ```typescript
261
303
  * const StringTag = Tag.of('myString')<string>();
262
304
  * const SymbolTag = Tag.for<number>();
263
- * class ServiceClass extends Tag.Class('MyService') {}
305
+ * class ServiceClass extends Tag.Service('MyService') {}
264
306
  *
265
307
  * console.log(Tag.id(StringTag)); // "myString"
266
308
  * console.log(Tag.id(SymbolTag)); // "Symbol()"
@@ -269,65 +311,56 @@ declare const Tag: {
269
311
  *
270
312
  * @internal - Primarily for internal use in error messages and debugging
271
313
  */
272
- id: (tag: AnyTag) => string;
314
+ id: (tag: AnyTag) => TagId;
315
+ isTag: (tag: unknown) => tag is AnyTag;
273
316
  };
274
317
  /**
275
- * Utility type that extracts the service type from any dependency tag.
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.
276
326
  *
277
- * This type is essential for type inference throughout the DI system, allowing
278
- * the container and layers to automatically determine what type of service
279
- * a given tag represents without manual type annotations.
327
+ * The phantom property is optional to allow normal runtime values to be assignable.
280
328
  *
281
- * @template T - Any dependency tag (ValueTag or TaggedClass)
282
- * @returns The service type that the tag represents
329
+ * @template T - A ValueTag type
330
+ * @returns The value type with optional phantom tag metadata for dependency inference
283
331
  *
284
- * @example With value tags
332
+ * @example
285
333
  * ```typescript
286
- * const StringTag = Tag.of('myString')<string>();
287
- * const ConfigTag = Tag.of('config')<{ apiKey: string }>();
288
- *
289
- * type StringService = ServiceOf<typeof StringTag>; // string
290
- * type ConfigService = ServiceOf<typeof ConfigTag>; // { apiKey: string }
291
- * ```
334
+ * const ApiKeyTag = Tag.of('apiKey')<string>();
292
335
  *
293
- * @example With class tags
294
- * ```typescript
295
- * class UserService extends Tag.Class('UserService') {
296
- * getUsers() { return []; }
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
+ * }
297
343
  * }
298
- *
299
- * type UserServiceType = ServiceOf<typeof UserService>; // UserService
300
- * ```
301
- *
302
- * @example Used in container methods
303
- * ```typescript
304
- * // The container uses ServiceOf internally for type inference
305
- * container.register(StringTag, () => 'hello'); // Factory must return string
306
- * container.register(UserService, () => new UserService()); // Factory must return UserService
307
- *
308
- * const str: string = await container.get(StringTag); // Automatically typed as string
309
- * const user: UserService = await container.get(UserService); // Automatically typed as UserService
310
344
  * ```
311
345
  */
312
- type ServiceOf<T> = T extends ValueTag<infer S, string | symbol> ? S : T extends ClassTag<infer S> ? S : never;
313
- //#endregion
314
- //#region src/types.d.ts
315
- type PromiseOrValue<T> = T | Promise<T>;
316
- /**
317
- * Unique symbol used to store the original ValueTag in Inject<T> types.
318
- * This prevents property name collisions while allowing type-level extraction.
319
- */
320
- declare const InjectSource: unique symbol;
346
+ type Inject<T extends ValueTag<TagId, unknown>> = T extends ValueTag<any, infer V> ? V & {
347
+ readonly [InjectSource]?: T;
348
+ } : never;
321
349
  /**
322
- * Generic interface representing a class constructor.
323
- *
324
- * This is primarily used internally for type constraints and validations.
325
- * Most users should use Tag.Class() instead of working with raw constructors.
326
- *
327
- * @template T - The type that the constructor creates
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.
328
352
  * @internal
329
353
  */
330
-
354
+ type ExtractInjectTag<T> = T extends {
355
+ readonly [InjectSource]?: infer U;
356
+ } ? U : never;
357
+ //#endregion
358
+ //#region src/types.d.ts
359
+ type PromiseOrValue<T> = T | Promise<T>;
360
+ type Contravariant<A> = (_: A) => void;
361
+ type Covariant<A> = (_: never) => A;
362
+ //#endregion
363
+ //#region src/container.d.ts
331
364
  /**
332
365
  * Type representing a factory function used to create dependency instances.
333
366
  *
@@ -343,58 +376,23 @@ declare const InjectSource: unique symbol;
343
376
  *
344
377
  * @example Synchronous factory
345
378
  * ```typescript
346
- * const factory: Factory<DatabaseService, never> = (container) => {
379
+ * const factory: Factory<DatabaseService, never> = (ctx) => {
347
380
  * return new DatabaseService('sqlite://memory');
348
381
  * };
349
382
  * ```
350
383
  *
351
384
  * @example Asynchronous factory with dependencies
352
385
  * ```typescript
353
- * const factory: Factory<UserService, typeof ConfigTag | typeof DatabaseService> = async (container) => {
386
+ * const factory: Factory<UserService, typeof ConfigTag | typeof DatabaseService> = async (ctx) => {
354
387
  * const [config, db] = await Promise.all([
355
- * container.get(ConfigTag),
356
- * container.get(DatabaseService)
388
+ * ctx.resolve(ConfigTag),
389
+ * ctx.resolve(DatabaseService)
357
390
  * ]);
358
391
  * return new UserService(config, db);
359
392
  * };
360
393
  * ```
361
394
  */
362
- type Factory<T, TReg extends AnyTag, TScope extends Scope> = (container: IContainer<TReg, TScope>) => PromiseOrValue<T>;
363
- /**
364
- * Helper type for injecting ValueTag dependencies in constructor parameters.
365
- * This allows clean specification of ValueTag dependencies while preserving
366
- * the original tag information for dependency inference.
367
- *
368
- * The phantom property is optional to allow normal runtime values to be assignable.
369
- *
370
- * @template T - A ValueTag type
371
- * @returns The value type with optional phantom tag metadata for dependency inference
372
- *
373
- * @example
374
- * ```typescript
375
- * const ApiKeyTag = Tag.of('apiKey')<string>();
376
- *
377
- * class UserService extends Tag.Class('UserService') {
378
- * constructor(
379
- * private db: DatabaseService, // ClassTag - works automatically
380
- * private apiKey: Inject<typeof ApiKeyTag> // ValueTag - type is string, tag preserved
381
- * ) {
382
- * super();
383
- * }
384
- * }
385
- * ```
386
- */
387
- type Inject<T extends ValueTag<unknown, string | symbol>> = T extends ValueTag<infer V, string | symbol> ? V & {
388
- readonly [InjectSource]?: T;
389
- } : never;
390
- /**
391
- * Helper type to extract the original ValueTag from an Inject<T> type.
392
- * Since InjectSource is optional, we need to check for both presence and absence.
393
- * @internal
394
- */
395
- type ExtractInjectTag<T> = T extends {
396
- readonly [InjectSource]?: infer U;
397
- } ? U : never;
395
+ type Factory<T, TReg extends AnyTag> = (ctx: ResolutionContext<TReg>) => PromiseOrValue<T>;
398
396
  /**
399
397
  * Type representing a finalizer function used to clean up dependency instances.
400
398
  *
@@ -436,19 +434,95 @@ type ExtractInjectTag<T> = T extends {
436
434
  * ```
437
435
  */
438
436
  type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
439
- type Scope = string | symbol;
440
- declare const DefaultScope: unique symbol;
441
- type DefaultScope = typeof DefaultScope;
442
- //#endregion
443
- //#region src/container.d.ts
444
- type DependencyLifecycle<T extends AnyTag, TReg extends AnyTag, TScope extends Scope> = {
445
- factory: Factory<ServiceOf<T>, TReg, TScope>;
446
- finalizer: Finalizer<ServiceOf<T>>;
437
+ /**
438
+ * Type representing a complete dependency lifecycle with both factory and finalizer.
439
+ *
440
+ * This type is used when registering dependencies that need cleanup. Instead of
441
+ * passing separate factory and finalizer parameters, you can pass an object
442
+ * containing both.
443
+ *
444
+ * @template T - The dependency tag type
445
+ * @template TReg - Union type of all dependencies available in the container
446
+ *
447
+ * @example Using DependencyLifecycle for registration
448
+ * ```typescript
449
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
450
+ * async connect() { return; }
451
+ * async disconnect() { return; }
452
+ * }
453
+ *
454
+ * const lifecycle: DependencyLifecycle<typeof DatabaseConnection, never> = {
455
+ * factory: async () => {
456
+ * const conn = new DatabaseConnection();
457
+ * await conn.connect();
458
+ * return conn;
459
+ * },
460
+ * finalizer: async (conn) => {
461
+ * await conn.disconnect();
462
+ * }
463
+ * };
464
+ *
465
+ * Container.empty().register(DatabaseConnection, lifecycle);
466
+ * ```
467
+ */
468
+ type DependencyLifecycle<T, TReg extends AnyTag> = {
469
+ factory: Factory<T, TReg>;
470
+ finalizer: Finalizer<T>;
447
471
  };
448
- interface IContainer<TReg extends AnyTag, TScope extends Scope = DefaultScope> {
449
- register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, TScope> | DependencyLifecycle<T, TReg, TScope>, scope?: TScope): IContainer<TReg | T, TScope>;
472
+ /**
473
+ * Union type representing all valid dependency registration specifications.
474
+ *
475
+ * A dependency can be registered either as:
476
+ * - A simple factory function that creates the dependency
477
+ * - A complete lifecycle object with both factory and finalizer
478
+ *
479
+ * @template T - The dependency tag type
480
+ * @template TReg - Union type of all dependencies available in the container
481
+ *
482
+ * @example Simple factory registration
483
+ * ```typescript
484
+ * const spec: DependencySpec<typeof UserService, never> =
485
+ * () => new UserService();
486
+ *
487
+ * Container.empty().register(UserService, spec);
488
+ * ```
489
+ *
490
+ * @example Lifecycle registration
491
+ * ```typescript
492
+ * const spec: DependencySpec<typeof DatabaseConnection, never> = {
493
+ * factory: () => new DatabaseConnection(),
494
+ * finalizer: (conn) => conn.close()
495
+ * };
496
+ *
497
+ * Container.empty().register(DatabaseConnection, spec);
498
+ * ```
499
+ */
500
+ type DependencySpec<T extends AnyTag, TReg extends AnyTag> = Factory<TagType<T>, TReg> | DependencyLifecycle<TagType<T>, TReg>;
501
+ /**
502
+ * Type representing the context available to factory functions during dependency resolution.
503
+ *
504
+ * This type contains only the `resolve` and `resolveAll` methods from the container, which are used to retrieve
505
+ * other dependencies during the creation of a service.
506
+ *
507
+ * @template TReg - Union type of all dependencies available in the container
508
+ */
509
+ type ResolutionContext<TReg extends AnyTag> = Pick<IContainer<TReg>, 'resolve' | 'resolveAll'>;
510
+ declare const ContainerTypeId: unique symbol;
511
+ /**
512
+ * Interface representing a container that can register and retrieve dependencies.
513
+ *
514
+ * @template TReg - Union type of all dependencies available in the container
515
+ */
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>;
450
521
  has(tag: AnyTag): boolean;
451
- get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
522
+ exists(tag: AnyTag): boolean;
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]> }>;
525
+ merge<TTarget extends AnyTag>(other: IContainer<TTarget>): IContainer<TReg | TTarget>;
452
526
  destroy(): Promise<void>;
453
527
  }
454
528
  /**
@@ -462,26 +536,26 @@ interface IContainer<TReg extends AnyTag, TScope extends Scope = DefaultScope> {
462
536
  *
463
537
  * @template TReg - Union type of all registered dependency tags in this container
464
538
  *
465
- * @example Basic usage with class tags
539
+ * @example Basic usage with service tags
466
540
  * ```typescript
467
- * import { container, Tag } from 'sandl';
541
+ * import { container, Tag } from 'sandly';
468
542
  *
469
- * class DatabaseService extends Tag.Class('DatabaseService') {
543
+ * class DatabaseService extends Tag.Service('DatabaseService') {
470
544
  * query() { return 'data'; }
471
545
  * }
472
546
  *
473
- * class UserService extends Tag.Class('UserService') {
547
+ * class UserService extends Tag.Service('UserService') {
474
548
  * constructor(private db: DatabaseService) {}
475
549
  * getUser() { return this.db.query(); }
476
550
  * }
477
551
  *
478
- * const c = container()
552
+ * const c = Container.empty()
479
553
  * .register(DatabaseService, () => new DatabaseService())
480
- * .register(UserService, async (container) =>
481
- * new UserService(await container.get(DatabaseService))
554
+ * .register(UserService, async (ctx) =>
555
+ * new UserService(await ctx.resolve(DatabaseService))
482
556
  * );
483
557
  *
484
- * const userService = await c.get(UserService);
558
+ * const userService = await c.resolve(UserService);
485
559
  * ```
486
560
  *
487
561
  * @example Usage with value tags
@@ -489,22 +563,22 @@ interface IContainer<TReg extends AnyTag, TScope extends Scope = DefaultScope> {
489
563
  * const ApiKeyTag = Tag.of('apiKey')<string>();
490
564
  * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
491
565
  *
492
- * const c = container()
566
+ * const c = Container.empty()
493
567
  * .register(ApiKeyTag, () => process.env.API_KEY!)
494
568
  * .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
495
569
  *
496
- * const apiKey = await c.get(ApiKeyTag);
497
- * const config = await c.get(ConfigTag);
570
+ * const apiKey = await c.resolve(ApiKeyTag);
571
+ * const config = await c.resolve(ConfigTag);
498
572
  * ```
499
573
  *
500
574
  * @example With finalizers for cleanup
501
575
  * ```typescript
502
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
576
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
503
577
  * async connect() { return; }
504
578
  * async disconnect() { return; }
505
579
  * }
506
580
  *
507
- * const c = container().register(
581
+ * const c = Container.empty().register(
508
582
  * DatabaseConnection,
509
583
  * async () => {
510
584
  * const conn = new DatabaseConnection();
@@ -519,43 +593,56 @@ interface IContainer<TReg extends AnyTag, TScope extends Scope = DefaultScope> {
519
593
  * ```
520
594
  */
521
595
  declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
596
+ readonly [ContainerTypeId]: {
597
+ readonly _TReg: Contravariant<TReg>;
598
+ };
522
599
  /**
523
600
  * Cache of instantiated dependencies as promises.
524
601
  * Ensures singleton behavior and supports concurrent access.
525
602
  * @internal
526
603
  */
527
- private readonly cache;
604
+ protected readonly cache: Map<AnyTag, Promise<unknown>>;
528
605
  /**
529
606
  * Factory functions for creating dependency instances.
530
607
  * @internal
531
608
  */
532
- private readonly factories;
609
+ protected readonly factories: Map<AnyTag, Factory<unknown, TReg>>;
533
610
  /**
534
611
  * Finalizer functions for cleaning up dependencies when the container is destroyed.
535
612
  * @internal
536
613
  */
537
- private readonly finalizers;
614
+ protected readonly finalizers: Map<AnyTag, Finalizer<any>>;
615
+ /**
616
+ * Flag indicating whether this container has been destroyed.
617
+ * @internal
618
+ */
619
+ protected isDestroyed: boolean;
620
+ static empty(): Container<never>;
538
621
  /**
539
622
  * Registers a dependency in the container with a factory function and optional finalizer.
540
623
  *
541
624
  * The factory function receives the current container instance and must return the
542
625
  * service instance (or a Promise of it). The container tracks the registration at
543
- * the type level, ensuring type safety for subsequent `.get()` calls.
626
+ * the type level, ensuring type safety for subsequent `.resolve()` calls.
627
+ *
628
+ * If a dependency is already registered, this method will override it unless the
629
+ * dependency has already been instantiated, in which case it will throw an error.
544
630
  *
545
631
  * @template T - The dependency tag being registered
546
632
  * @param tag - The dependency tag (class or value tag)
547
633
  * @param factory - Function that creates the service instance, receives container for dependency injection
548
634
  * @param finalizer - Optional cleanup function called when container is destroyed
549
635
  * @returns A new container instance with the dependency registered
550
- * @throws {DependencyContainerError} If the dependency is already registered
636
+ * @throws {ContainerDestroyedError} If the container has been destroyed
637
+ * @throws {Error} If the dependency has already been instantiated
551
638
  *
552
639
  * @example Registering a simple service
553
640
  * ```typescript
554
- * class LoggerService extends Tag.Class('LoggerService') {
641
+ * class LoggerService extends Tag.Service('LoggerService') {
555
642
  * log(message: string) { console.log(message); }
556
643
  * }
557
644
  *
558
- * const c = container().register(
645
+ * const c = Container.empty().register(
559
646
  * LoggerService,
560
647
  * () => new LoggerService()
561
648
  * );
@@ -563,26 +650,33 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
563
650
  *
564
651
  * @example Registering with dependencies
565
652
  * ```typescript
566
- * class UserService extends Tag.Class('UserService') {
653
+ * class UserService extends Tag.Service('UserService') {
567
654
  * constructor(private db: DatabaseService, private logger: LoggerService) {}
568
655
  * }
569
656
  *
570
- * const c = container()
657
+ * const c = Container.empty()
571
658
  * .register(DatabaseService, () => new DatabaseService())
572
659
  * .register(LoggerService, () => new LoggerService())
573
- * .register(UserService, async (container) =>
660
+ * .register(UserService, async (ctx) =>
574
661
  * new UserService(
575
- * await container.get(DatabaseService),
576
- * await container.get(LoggerService)
662
+ * await ctx.resolve(DatabaseService),
663
+ * await ctx.resolve(LoggerService)
577
664
  * )
578
665
  * );
579
666
  * ```
580
667
  *
668
+ * @example Overriding a dependency
669
+ * ```typescript
670
+ * const c = Container.empty()
671
+ * .register(DatabaseService, () => new DatabaseService())
672
+ * .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
673
+ * ```
674
+ *
581
675
  * @example Using value tags
582
676
  * ```typescript
583
677
  * const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
584
678
  *
585
- * const c = container().register(
679
+ * const c = Container.empty().register(
586
680
  * ConfigTag,
587
681
  * () => ({ apiUrl: 'https://api.example.com' })
588
682
  * );
@@ -590,12 +684,12 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
590
684
  *
591
685
  * @example With finalizer for cleanup
592
686
  * ```typescript
593
- * class DatabaseConnection extends Tag.Class('DatabaseConnection') {
687
+ * class DatabaseConnection extends Tag.Service('DatabaseConnection') {
594
688
  * async connect() { return; }
595
689
  * async close() { return; }
596
690
  * }
597
691
  *
598
- * const c = container().register(
692
+ * const c = Container.empty().register(
599
693
  * DatabaseConnection,
600
694
  * async () => {
601
695
  * const conn = new DatabaseConnection();
@@ -606,27 +700,30 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
606
700
  * );
607
701
  * ```
608
702
  */
609
- register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, DefaultScope> | DependencyLifecycle<T, TReg, DefaultScope>): IContainer<TReg | T>;
703
+ register<T extends AnyTag>(tag: T, spec: DependencySpec<T, TReg>): Container<TReg | T>;
610
704
  /**
611
- * Checks if a dependency has been instantiated (cached) in the container.
705
+ * Checks if a dependency has been registered in the container.
612
706
  *
613
- * Note: This returns `true` only after the dependency has been created via `.get()`.
614
- * A registered but not-yet-instantiated dependency will return `false`.
707
+ * This returns `true` if the dependency has been registered via `.register()`,
708
+ * regardless of whether it has been instantiated yet.
615
709
  *
616
710
  * @param tag - The dependency tag to check
617
- * @returns `true` if the dependency has been instantiated and cached, `false` otherwise
711
+ * @returns `true` if the dependency has been registered, `false` otherwise
618
712
  *
619
713
  * @example
620
714
  * ```typescript
621
- * const c = container().register(DatabaseService, () => new DatabaseService());
622
- *
623
- * console.log(c.has(DatabaseService)); // false - not instantiated yet
624
- *
625
- * await c.get(DatabaseService);
626
- * console.log(c.has(DatabaseService)); // true - now instantiated and cached
715
+ * const c = Container.empty().register(DatabaseService, () => new DatabaseService());
716
+ * console.log(c.has(DatabaseService)); // true
627
717
  * ```
628
718
  */
629
719
  has(tag: AnyTag): boolean;
720
+ /**
721
+ * Checks if a dependency has been instantiated (cached) in the container.
722
+ *
723
+ * @param tag - The dependency tag to check
724
+ * @returns true if the dependency has been instantiated, false otherwise
725
+ */
726
+ exists(tag: AnyTag): boolean;
630
727
  /**
631
728
  * Retrieves a dependency instance from the container, creating it if necessary.
632
729
  *
@@ -646,10 +743,10 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
646
743
  *
647
744
  * @example Basic usage
648
745
  * ```typescript
649
- * const c = container()
746
+ * const c = Container.empty()
650
747
  * .register(DatabaseService, () => new DatabaseService());
651
748
  *
652
- * const db = await c.get(DatabaseService);
749
+ * const db = await c.resolve(DatabaseService);
653
750
  * db.query('SELECT * FROM users');
654
751
  * ```
655
752
  *
@@ -657,9 +754,9 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
657
754
  * ```typescript
658
755
  * // All three calls will receive the same instance
659
756
  * const [db1, db2, db3] = await Promise.all([
660
- * c.get(DatabaseService),
661
- * c.get(DatabaseService),
662
- * c.get(DatabaseService)
757
+ * c.resolve(DatabaseService),
758
+ * c.resolve(DatabaseService),
759
+ * c.resolve(DatabaseService)
663
760
  * ]);
664
761
  *
665
762
  * console.log(db1 === db2 === db3); // true
@@ -667,23 +764,99 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
667
764
  *
668
765
  * @example Dependency injection in factories
669
766
  * ```typescript
670
- * const c = container()
767
+ * const c = Container.empty()
671
768
  * .register(DatabaseService, () => new DatabaseService())
672
- * .register(UserService, async (container) => {
673
- * const db = await container.get(DatabaseService);
769
+ * .register(UserService, async (ctx) => {
770
+ * const db = await ctx.resolve(DatabaseService);
674
771
  * return new UserService(db);
675
772
  * });
676
773
  *
677
- * const userService = await c.get(UserService);
774
+ * const userService = await c.resolve(UserService);
775
+ * ```
776
+ */
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]> }>;
819
+ /**
820
+ * Copies all registrations from this container to a target container.
821
+ *
822
+ * @internal
823
+ * @param target - The container to copy registrations to
824
+ * @throws {ContainerDestroyedError} If this container has been destroyed
825
+ */
826
+ copyTo<TTarget extends AnyTag>(target: Container<TTarget>): void;
827
+ /**
828
+ * Creates a new container by merging this container's registrations with another container.
829
+ *
830
+ * This method creates a new container that contains all registrations from both containers.
831
+ * If there are conflicts (same dependency registered in both containers), this
832
+ * container's registration will take precedence.
833
+ *
834
+ * **Important**: Only the registrations are copied, not any cached instances.
835
+ * The new container starts with an empty instance cache.
836
+ *
837
+ * @param other - The container to merge with
838
+ * @returns A new container with combined registrations
839
+ * @throws {ContainerDestroyedError} If this container has been destroyed
840
+ *
841
+ * @example Merging containers
842
+ * ```typescript
843
+ * const container1 = Container.empty()
844
+ * .register(DatabaseService, () => new DatabaseService());
845
+ *
846
+ * const container2 = Container.empty()
847
+ * .register(UserService, () => new UserService());
848
+ *
849
+ * const merged = container1.merge(container2);
850
+ * // merged has both DatabaseService and UserService
678
851
  * ```
679
852
  */
680
- get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
853
+ merge<TTarget extends AnyTag>(other: Container<TTarget>): Container<TReg | TTarget>;
681
854
  /**
682
- * Destroys all instantiated dependencies by calling their finalizers, then clears the instance cache.
855
+ * Destroys all instantiated dependencies by calling their finalizers and makes the container unusable.
683
856
  *
684
- * **Important: This method preserves the container structure (factories and finalizers) for reuse.**
685
- * The container can be used again after destruction to create fresh instances following the same
686
- * dependency patterns.
857
+ * **Important: After calling destroy(), the container becomes permanently unusable.**
858
+ * Any subsequent calls to register(), get(), or destroy() will throw a ContainerError.
859
+ * This ensures proper cleanup and prevents runtime errors from accessing destroyed resources.
687
860
  *
688
861
  * All finalizers for instantiated dependencies are called concurrently using Promise.allSettled()
689
862
  * for maximum cleanup performance.
@@ -695,11 +868,11 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
695
868
  * dependencies are cleaned up.
696
869
  *
697
870
  * @returns Promise that resolves when all cleanup is complete
698
- * @throws {DependencyContainerFinalizationError} If any finalizers fail during cleanup
871
+ * @throws {DependencyFinalizationError} If any finalizers fail during cleanup
699
872
  *
700
- * @example Basic cleanup and reuse
873
+ * @example Basic cleanup
701
874
  * ```typescript
702
- * const c = container()
875
+ * const c = Container.empty()
703
876
  * .register(DatabaseConnection,
704
877
  * async () => {
705
878
  * const conn = new DatabaseConnection();
@@ -709,24 +882,32 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
709
882
  * (conn) => conn.disconnect() // Finalizer
710
883
  * );
711
884
  *
712
- * // First use cycle
713
- * const db1 = await c.get(DatabaseConnection);
714
- * await c.destroy(); // Calls conn.disconnect(), clears cache
885
+ * const db = await c.resolve(DatabaseConnection);
886
+ * await c.destroy(); // Calls conn.disconnect(), container becomes unusable
715
887
  *
716
- * // Container can be reused - creates fresh instances
717
- * const db2 = await c.get(DatabaseConnection); // New connection
718
- * expect(db2).not.toBe(db1); // Different instances
888
+ * // This will throw an error
889
+ * try {
890
+ * await c.resolve(DatabaseConnection);
891
+ * } catch (error) {
892
+ * console.log(error.message); // "Cannot resolve dependencies from a destroyed container"
893
+ * }
719
894
  * ```
720
895
  *
721
- * @example Multiple destroy/reuse cycles
896
+ * @example Application shutdown
722
897
  * ```typescript
723
- * const c = container().register(UserService, () => new UserService());
724
- *
725
- * for (let i = 0; i < 5; i++) {
726
- * const user = await c.get(UserService);
727
- * // ... use service ...
728
- * await c.destroy(); // Clean up, ready for next cycle
729
- * }
898
+ * const appContainer Container.empty
899
+ * .register(DatabaseService, () => new DatabaseService())
900
+ * .register(HTTPServer, async (ctx) => new HTTPServer(await ctx.resolve(DatabaseService)));
901
+ *
902
+ * // During application shutdown
903
+ * process.on('SIGTERM', async () => {
904
+ * try {
905
+ * await appContainer.destroy(); // Clean shutdown of all services
906
+ * } catch (error) {
907
+ * console.error('Error during shutdown:', error);
908
+ * }
909
+ * process.exit(0);
910
+ * });
730
911
  * ```
731
912
  *
732
913
  * @example Handling cleanup errors
@@ -738,138 +919,241 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
738
919
  * console.error('Some dependencies failed to clean up:', error.detail.errors);
739
920
  * }
740
921
  * }
741
- * // Container is still reusable even after finalizer errors
922
+ * // Container is destroyed regardless of finalizer errors
742
923
  * ```
743
924
  */
744
925
  destroy(): Promise<void>;
745
926
  }
746
- declare class ScopedContainer<TReg extends AnyTag, TScope extends Scope> implements IContainer<TReg, TScope> {
747
- private readonly scope;
748
- private readonly parent;
749
- private readonly children;
750
- /**
751
- * Cache of instantiated dependencies as promises for this scope.
752
- * @internal
753
- */
754
- private readonly cache;
755
- /**
756
- * Factory functions for creating dependency instances in this scope.
757
- * @internal
758
- */
759
- private readonly factories;
760
- /**
761
- * Finalizer functions for cleaning up dependencies when this scope is destroyed.
762
- * @internal
763
- */
764
- private readonly finalizers;
765
- constructor(parent: IContainer<any, any> | null, scope: TScope);
766
- /**
767
- * Registers a dependency in the specified scope within this container's scope chain.
768
- *
769
- * If no scope is specified, registers in the current (leaf) scope. If a scope is specified,
770
- * delegates to the parent container if the target scope doesn't match the current scope.
771
- *
772
- * This allows registering dependencies at different scope levels from any container
773
- * in the scope chain, providing flexibility for dependency organization.
774
- *
775
- * @param tag - The dependency tag to register
776
- * @param factory - Factory function to create the dependency
777
- * @param finalizer - Optional cleanup function
778
- * @param scope - Target scope for registration (defaults to current scope)
779
- * @returns This container with updated type information
780
- *
781
- * @example Registering in different scopes
782
- * ```typescript
783
- * const runtime = scopedContainer('runtime');
784
- * const request = runtime.child('request');
785
- *
786
- * // Register in current (request) scope
787
- * request.register(RequestService, () => new RequestService());
788
- *
789
- * // Register in runtime scope from request container - delegates to parent
790
- * request.register(DatabaseService, () => new DatabaseService(), undefined, 'runtime');
791
- * ```
792
- */
793
- register<T extends AnyTag>(tag: T, factoryOrLifecycle: Factory<ServiceOf<T>, TReg, TScope> | DependencyLifecycle<T, TReg, TScope>, scope?: TScope): ScopedContainer<TReg | T, TScope>;
794
- /**
795
- * Checks if a dependency has been instantiated in this scope or any parent scope.
796
- *
797
- * This method checks the current scope first, then walks up the parent chain.
798
- * Returns true only if the dependency has been created and cached somewhere in the scope hierarchy.
799
- */
800
- has(tag: AnyTag): boolean;
927
+ //#endregion
928
+ //#region src/errors.d.ts
929
+ type ErrorProps = {
930
+ cause?: unknown;
931
+ detail?: Record<string, unknown>;
932
+ };
933
+ type ErrorDump = {
934
+ name: string;
935
+ message: string;
936
+ stack?: string;
937
+ error: {
938
+ name: string;
939
+ message: string;
940
+ detail: Record<string, unknown>;
941
+ cause?: unknown;
942
+ };
943
+ };
944
+ declare class BaseError extends Error {
945
+ detail: Record<string, unknown> | undefined;
946
+ constructor(message: string, {
947
+ cause,
948
+ detail
949
+ }?: ErrorProps);
950
+ static ensure(error: unknown): BaseError;
951
+ dump(): ErrorDump;
952
+ dumps(): string;
953
+ }
954
+ /**
955
+ * Base error class for all dependency container related errors.
956
+ *
957
+ * This extends the framework's BaseError to provide consistent error handling
958
+ * and structured error information across the dependency injection system.
959
+ *
960
+ * @example Catching DI errors
961
+ * ```typescript
962
+ * try {
963
+ * await container.resolve(SomeService);
964
+ * } catch (error) {
965
+ * if (error instanceof ContainerError) {
966
+ * console.error('DI Error:', error.message);
967
+ * console.error('Details:', error.detail);
968
+ * }
969
+ * }
970
+ * ```
971
+ */
972
+ declare class ContainerError extends BaseError {}
973
+ /**
974
+ * Error thrown when attempting to register a dependency that has already been instantiated.
975
+ *
976
+ * This error occurs when calling `container.register()` for a tag that has already been instantiated.
977
+ * Registration must happen before any instantiation occurs, as cached instances would still be used
978
+ * by existing dependencies.
979
+ */
980
+ declare class DependencyAlreadyInstantiatedError extends ContainerError {}
981
+ /**
982
+ * Error thrown when attempting to use a container that has been destroyed.
983
+ *
984
+ * This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
985
+ * on a container that has already been destroyed. It indicates a programming error where the container
986
+ * is being used after it has been destroyed.
987
+ */
988
+ declare class ContainerDestroyedError extends ContainerError {}
989
+ /**
990
+ * Error thrown when attempting to retrieve a dependency that hasn't been registered.
991
+ *
992
+ * This error occurs when calling `container.resolve(Tag)` for a tag that was never
993
+ * registered via `container.register()`. It indicates a programming error where
994
+ * the dependency setup is incomplete.
995
+ *
996
+ * @example
997
+ * ```typescript
998
+ * const c = Container.empty(); // Empty container
999
+ *
1000
+ * try {
1001
+ * await c.resolve(UnregisteredService); // This will throw
1002
+ * } catch (error) {
1003
+ * if (error instanceof UnknownDependencyError) {
1004
+ * console.error('Missing dependency:', error.message);
1005
+ * }
1006
+ * }
1007
+ * ```
1008
+ */
1009
+ declare class UnknownDependencyError extends ContainerError {
801
1010
  /**
802
- * Retrieves a dependency instance, resolving from the current scope or parent scopes.
1011
+ * @internal
1012
+ * Creates an UnknownDependencyError for the given tag.
803
1013
  *
804
- * Resolution strategy:
805
- * 1. Check cache in current scope
806
- * 2. Check if factory exists in current scope - if so, create instance here
807
- * 3. Otherwise, delegate to parent scope
808
- * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
1014
+ * @param tag - The dependency tag that wasn't found
809
1015
  */
810
- get<T extends TReg>(tag: T): Promise<ServiceOf<T>>;
1016
+ constructor(tag: AnyTag);
1017
+ }
1018
+ /**
1019
+ * Error thrown when a circular dependency is detected during dependency resolution.
1020
+ *
1021
+ * This occurs when service A depends on service B, which depends on service A (directly
1022
+ * or through a chain of dependencies). The error includes the full dependency chain
1023
+ * to help identify the circular reference.
1024
+ *
1025
+ * @example Circular dependency scenario
1026
+ * ```typescript
1027
+ * class ServiceA extends Tag.Service('ServiceA') {}
1028
+ * class ServiceB extends Tag.Service('ServiceB') {}
1029
+ *
1030
+ * const c = Container.empty()
1031
+ * .register(ServiceA, async (ctx) =>
1032
+ * new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
1033
+ * )
1034
+ * .register(ServiceB, async (ctx) =>
1035
+ * new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
1036
+ * );
1037
+ *
1038
+ * try {
1039
+ * await c.resolve(ServiceA);
1040
+ * } catch (error) {
1041
+ * if (error instanceof CircularDependencyError) {
1042
+ * console.error('Circular dependency:', error.message);
1043
+ * // Output: "Circular dependency detected for ServiceA: ServiceA -> ServiceB -> ServiceA"
1044
+ * }
1045
+ * }
1046
+ * ```
1047
+ */
1048
+ declare class CircularDependencyError extends ContainerError {
811
1049
  /**
812
- * Destroys this scoped container and its children, preserving the container structure for reuse.
813
- *
814
- * This method ensures proper cleanup order while maintaining reusability:
815
- * 1. Destroys all child scopes first (they may depend on parent scope dependencies)
816
- * 2. Then calls finalizers for dependencies created in this scope
817
- * 3. Clears only instance caches - preserves factories, finalizers, and child structure
1050
+ * @internal
1051
+ * Creates a CircularDependencyError with the dependency chain information.
818
1052
  *
819
- * Child destruction happens first to ensure dependencies don't get cleaned up
820
- * before their dependents.
1053
+ * @param tag - The tag where the circular dependency was detected
1054
+ * @param dependencyChain - The chain of dependencies that led to the circular reference
821
1055
  */
822
- destroy(): Promise<void>;
1056
+ constructor(tag: AnyTag, dependencyChain: AnyTag[]);
1057
+ }
1058
+ /**
1059
+ * Error thrown when a dependency factory function throws an error during instantiation.
1060
+ *
1061
+ * This wraps the original error with additional context about which dependency
1062
+ * failed to be created. The original error is preserved as the `cause` property.
1063
+ *
1064
+ * @example Factory throwing error
1065
+ * ```typescript
1066
+ * class DatabaseService extends Tag.Service('DatabaseService') {}
1067
+ *
1068
+ * const c = Container.empty().register(DatabaseService, () => {
1069
+ * throw new Error('Database connection failed');
1070
+ * });
1071
+ *
1072
+ * try {
1073
+ * await c.resolve(DatabaseService);
1074
+ * } catch (error) {
1075
+ * if (error instanceof DependencyCreationError) {
1076
+ * console.error('Failed to create:', error.message);
1077
+ * console.error('Original error:', error.cause);
1078
+ * }
1079
+ * }
1080
+ * ```
1081
+ */
1082
+ declare class DependencyCreationError extends ContainerError {
823
1083
  /**
824
- * Creates a child scoped container.
1084
+ * @internal
1085
+ * Creates a DependencyCreationError wrapping the original factory error.
825
1086
  *
826
- * Child containers inherit access to parent dependencies but maintain
827
- * their own scope for new registrations and instance caching.
1087
+ * @param tag - The tag of the dependency that failed to be created
1088
+ * @param error - The original error thrown by the factory function
828
1089
  */
829
- child<TChildScope extends Scope>(scope: TChildScope): ScopedContainer<TReg, TScope | TChildScope>;
1090
+ constructor(tag: AnyTag, error: unknown);
830
1091
  }
831
1092
  /**
832
- * Creates a new empty dependency injection container.
1093
+ * Error thrown when one or more finalizers fail during container destruction.
833
1094
  *
834
- * This is a convenience factory function that creates a new DependencyContainer instance.
835
- * The returned container starts with no registered dependencies and the type parameter
836
- * defaults to `never`, indicating no dependencies are available for retrieval yet.
1095
+ * This error aggregates multiple finalizer failures that occurred during
1096
+ * `container.destroy()`. Even if some finalizers fail, the container cleanup
1097
+ * process continues and this error contains details of all failures.
837
1098
  *
838
- * @returns A new empty DependencyContainer instance
839
- *
840
- * @example
1099
+ * @example Handling finalization errors
841
1100
  * ```typescript
842
- * import { container, Tag } from 'sandl';
843
- *
844
- * class DatabaseService extends Tag.Class('DatabaseService') {}
845
- * class UserService extends Tag.Class('UserService') {}
846
- *
847
- * const c = container()
848
- * .register(DatabaseService, () => new DatabaseService())
849
- * .register(UserService, async (container) =>
850
- * new UserService(await container.get(DatabaseService))
851
- * );
852
- *
853
- * const userService = await c.get(UserService);
1101
+ * try {
1102
+ * await container.destroy();
1103
+ * } catch (error) {
1104
+ * if (error instanceof DependencyFinalizationError) {
1105
+ * console.error('Some finalizers failed');
1106
+ * console.error('Error details:', error.detail.errors);
1107
+ * }
1108
+ * }
854
1109
  * ```
855
1110
  */
856
- declare function container(): Container<never>;
857
- declare function scopedContainer<TScope extends Scope>(scope: TScope): ScopedContainer<never, TScope>;
1111
+ declare class DependencyFinalizationError extends ContainerError {
1112
+ /**
1113
+ * @internal
1114
+ * Creates a DependencyFinalizationError aggregating multiple finalizer failures.
1115
+ *
1116
+ * @param errors - Array of errors thrown by individual finalizers
1117
+ */
1118
+ constructor(errors: unknown[]);
1119
+ }
858
1120
  //#endregion
859
1121
  //#region src/layer.d.ts
1122
+ /**
1123
+ * The most generic layer type that accepts any concrete layer.
1124
+ */
1125
+ type AnyLayer = Layer<any, any>;
1126
+ /**
1127
+ * The type ID for the Layer interface.
1128
+ */
1129
+ declare const LayerTypeId: unique symbol;
860
1130
  /**
861
1131
  * A dependency layer represents a reusable, composable unit of dependency registrations.
862
1132
  * Layers allow you to organize your dependency injection setup into logical groups
863
1133
  * that can be combined and reused across different contexts.
864
1134
  *
1135
+ * ## Type Variance
1136
+ *
1137
+ * The Layer interface uses TypeScript's variance annotations to enable safe substitutability:
1138
+ *
1139
+ * ### TRequires (covariant)
1140
+ * A layer requiring fewer dependencies can substitute one requiring more:
1141
+ * - `Layer<never, X>` can be used where `Layer<A | B, X>` is expected
1142
+ * - Intuition: A service that needs nothing is more flexible than one that needs specific deps
1143
+ *
1144
+ * ### TProvides (contravariant)
1145
+ * A layer providing more services can substitute one providing fewer:
1146
+ * - `Layer<X, A | B>` can be used where `Layer<X, A>` is expected
1147
+ * - Intuition: A service that gives you extra things is compatible with expecting fewer things
1148
+ *
865
1149
  * @template TRequires - The union of tags this layer requires to be satisfied by other layers
866
1150
  * @template TProvides - The union of tags this layer provides/registers
867
1151
  *
868
1152
  * @example Basic layer usage
869
1153
  * ```typescript
870
- * import { layer, Tag, container } from 'sandl';
1154
+ * import { layer, Tag, container } from 'sandly';
871
1155
  *
872
- * class DatabaseService extends Tag.Class('DatabaseService') {
1156
+ * class DatabaseService extends Tag.Service('DatabaseService') {
873
1157
  * query() { return 'data'; }
874
1158
  * }
875
1159
  *
@@ -879,70 +1163,126 @@ declare function scopedContainer<TScope extends Scope>(scope: TScope): ScopedCon
879
1163
  * );
880
1164
  *
881
1165
  * // Apply the layer to a container
882
- * const c = container();
883
- * const finalContainer = databaseLayer().register(c);
1166
+ * const c = Container.empty();
1167
+ * const finalContainer = databaseLayer.register(c);
884
1168
  *
885
- * const db = await finalContainer.get(DatabaseService);
1169
+ * const db = await finalContainer.resolve(DatabaseService);
886
1170
  * ```
887
1171
  *
888
- * @example Layer composition
1172
+ * @example Layer composition with variance
889
1173
  * ```typescript
890
1174
  * // Layer that requires DatabaseService and provides UserService
891
1175
  * const userLayer = layer<typeof DatabaseService, typeof UserService>((container) =>
892
- * container.register(UserService, async (c) =>
893
- * new UserService(await c.get(DatabaseService))
1176
+ * container.register(UserService, async (ctx) =>
1177
+ * new UserService(await ctx.resolve(DatabaseService))
894
1178
  * )
895
1179
  * );
896
1180
  *
897
- * // Compose layers: database layer provides what user layer needs
898
- * const appLayer = databaseLayer().to(userLayer());
1181
+ * // Compose layers: provide database layer to user layer
1182
+ * const appLayer = userLayer.provide(databaseLayer);
899
1183
  * ```
900
1184
  */
901
- interface Layer<TRequires extends AnyTag = never, 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
+ };
902
1190
  /**
903
1191
  * Applies this layer's registrations to the given container.
904
1192
  *
905
- * @param container - The container to register dependencies into
906
- * @returns A new container with this layer's dependencies registered
1193
+ * ## Generic Container Support
907
1194
  *
908
- * @example
1195
+ * The signature uses `TContainer extends AnyTag` to accept containers with any existing
1196
+ * services while preserving type information. The container must provide at least this
1197
+ * layer's requirements (`TRequires`) but can have additional services (`TContainer`).
1198
+ *
1199
+ * Result container has: `TRequires | TContainer | TProvides` - everything that was
1200
+ * already there plus this layer's new provisions.
1201
+ *
1202
+ * @param container - The container to register dependencies into (must satisfy TRequires)
1203
+ * @returns A new container with this layer's dependencies registered and all existing services preserved
1204
+ *
1205
+ * @example Basic usage
909
1206
  * ```typescript
910
- * const container = container();
911
- * const updatedContainer = myLayer.register(container);
1207
+ * const c = Container.empty();
1208
+ * const updatedContainer = myLayer.register(c);
1209
+ * ```
1210
+ *
1211
+ * @example With existing services preserved
1212
+ * ```typescript
1213
+ * const baseContainer = Container.empty()
1214
+ * .register(ExistingService, () => new ExistingService());
1215
+ *
1216
+ * const enhanced = myLayer.register(baseContainer);
1217
+ * // Enhanced container has both ExistingService and myLayer's provisions
912
1218
  * ```
913
1219
  */
914
- register: <TScope extends Scope>(container: IContainer<TRequires, TScope>) => IContainer<TRequires | TProvides, TScope>;
1220
+ register: <TContainer extends AnyTag>(container: IContainer<TRequires | TContainer>) => IContainer<TRequires | TContainer | TProvides>;
915
1221
  /**
916
- * Composes this layer with a target layer, creating a pipeline where this layer's
917
- * provisions satisfy the target layer's requirements. This creates a dependency
918
- * flow from source → target.
1222
+ * Provides a dependency layer to this layer, creating a pipeline where the dependency layer's
1223
+ * provisions satisfy this layer's requirements. This creates a dependency flow from dependency → this.
919
1224
  *
920
- * Type-safe: The target layer's requirements must be satisfiable by this layer's
1225
+ * Type-safe: This layer's requirements must be satisfiable by the dependency layer's
921
1226
  * provisions and any remaining external requirements.
922
1227
  *
923
- * @template TTargetRequires - What the target layer requires
924
- * @template TTargetProvides - What the target layer provides
925
- * @param target - The layer to compose with
926
- * @returns A new composed layer
1228
+ * @template TDepRequires - What the dependency layer requires
1229
+ * @template TDepProvides - What the dependency layer provides
1230
+ * @param dependency - The layer to provide as a dependency
1231
+ * @returns A new composed layer that only exposes this layer's provisions
927
1232
  *
928
1233
  * @example Simple composition
929
1234
  * ```typescript
930
1235
  * const configLayer = layer<never, typeof ConfigTag>(...);
931
1236
  * const dbLayer = layer<typeof ConfigTag, typeof DatabaseService>(...);
932
1237
  *
933
- * // Config provides what database needs
934
- * const infraLayer = configLayer().to(dbLayer());
1238
+ * // Provide config to database layer
1239
+ * const infraLayer = dbLayer.provide(configLayer);
1240
+ * ```
1241
+ *
1242
+ * @example Multi-level composition (reads naturally left-to-right)
1243
+ * ```typescript
1244
+ * const appLayer = apiLayer
1245
+ * .provide(serviceLayer)
1246
+ * .provide(databaseLayer)
1247
+ * .provide(configLayer);
1248
+ * ```
1249
+ */
1250
+ provide: <TDepRequires extends AnyTag, TDepProvides extends AnyTag>(dependency: Layer<TDepRequires, TDepProvides>) => Layer<TDepRequires | Exclude<TRequires, TDepProvides>, TProvides>;
1251
+ /**
1252
+ * Provides a dependency layer to this layer and merges the provisions.
1253
+ * Unlike `.provide()`, this method includes both this layer's provisions and the dependency layer's
1254
+ * provisions in the result type. This is useful when you want to expose services from both layers.
1255
+ *
1256
+ * Type-safe: This layer's requirements must be satisfiable by the dependency layer's
1257
+ * provisions and any remaining external requirements.
1258
+ *
1259
+ * @template TDepRequires - What the dependency layer requires
1260
+ * @template TDepProvides - What the dependency layer provides
1261
+ * @param dependency - The layer to provide as a dependency
1262
+ * @returns A new composed layer that provides services from both layers
1263
+ *
1264
+ * @example Providing with merged provisions
1265
+ * ```typescript
1266
+ * const configLayer = layer<never, typeof ConfigTag>(...);
1267
+ * const dbLayer = layer<typeof ConfigTag, typeof DatabaseService>(...);
1268
+ *
1269
+ * // Provide config to database layer, and both services are available
1270
+ * const infraLayer = dbLayer.provideMerge(configLayer);
1271
+ * // Type: Layer<never, typeof ConfigTag | typeof DatabaseService>
935
1272
  * ```
936
1273
  *
937
- * @example Multi-level composition
1274
+ * @example Difference from .provide()
938
1275
  * ```typescript
939
- * const appLayer = configLayer()
940
- * .to(databaseLayer())
941
- * .to(serviceLayer())
942
- * .to(apiLayer());
1276
+ * // .provide() only exposes this layer's provisions:
1277
+ * const withProvide = dbLayer.provide(configLayer);
1278
+ * // Type: Layer<never, typeof DatabaseService>
1279
+ *
1280
+ * // .provideMerge() exposes both layers' provisions:
1281
+ * const withProvideMerge = dbLayer.provideMerge(configLayer);
1282
+ * // Type: Layer<never, typeof ConfigTag | typeof DatabaseService>
943
1283
  * ```
944
1284
  */
945
- to: <TTargetRequires extends AnyTag, TTargetProvides extends AnyTag>(target: Layer<TTargetRequires, TTargetProvides>) => Layer<TRequires | Exclude<TTargetRequires, TProvides>, TProvides | TTargetProvides>;
1285
+ provideMerge: <TDepRequires extends AnyTag, TDepProvides extends AnyTag>(dependency: Layer<TDepRequires, TDepProvides>) => Layer<TDepRequires | Exclude<TRequires, TDepProvides>, TProvides | TDepProvides>;
946
1286
  /**
947
1287
  * Merges this layer with another layer, combining their requirements and provisions.
948
1288
  * This is useful for combining independent layers that don't have a dependency
@@ -959,42 +1299,33 @@ interface Layer<TRequires extends AnyTag = never, TProvides extends AnyTag = nev
959
1299
  * const loggingLayer = layer<never, typeof LoggerService>(...);
960
1300
  *
961
1301
  * // Combine infrastructure layers
962
- * const infraLayer = persistenceLayer().and(loggingLayer());
1302
+ * const infraLayer = persistenceLayer.merge(loggingLayer);
963
1303
  * ```
964
1304
  *
965
1305
  * @example Building complex layer combinations
966
1306
  * ```typescript
967
- * const appInfraLayer = persistenceLayer()
968
- * .and(messagingLayer())
969
- * .and(observabilityLayer());
1307
+ * const appInfraLayer = persistenceLayer
1308
+ * .merge(messagingLayer)
1309
+ * .merge(observabilityLayer);
970
1310
  * ```
971
1311
  */
972
- and: <TOtherRequires extends AnyTag, TOtherProvides extends AnyTag>(other: Layer<TOtherRequires, TOtherProvides>) => Layer<TRequires | TOtherRequires, TProvides | TOtherProvides>;
1312
+ merge: <TOtherRequires extends AnyTag, TOtherProvides extends AnyTag>(other: Layer<TOtherRequires, TOtherProvides>) => Layer<TRequires | TOtherRequires, TProvides | TOtherProvides>;
973
1313
  }
974
- /**
975
- * A factory function for creating layers.
976
- *
977
- * @template TRequires - The union of tags this layer requires
978
- * @template TProvides - The union of tags this layer provides
979
- * @template TParams - Optional parameters that can be passed to configure the layer
980
- */
981
- type LayerFactory<TRequires extends AnyTag, TProvides extends AnyTag, TParams = undefined> = TParams extends undefined ? () => Layer<TRequires, TProvides> : (params: TParams) => Layer<TRequires, TProvides>;
982
1314
  /**
983
1315
  * Creates a new dependency layer that encapsulates a set of dependency registrations.
984
1316
  * Layers are the primary building blocks for organizing and composing dependency injection setups.
985
1317
  *
986
1318
  * @template TRequires - The union of dependency tags this layer requires from other layers or external setup
987
1319
  * @template TProvides - The union of dependency tags this layer registers/provides
988
- * @template TParams - Optional parameters that can be passed to configure the layer
989
1320
  *
990
- * @param register - Function that performs the dependency registrations. Receives a container and optional params.
991
- * @returns A layer factory function. If TParams is undefined, returns a parameterless function. Otherwise returns a function that takes TParams.
1321
+ * @param register - Function that performs the dependency registrations. Receives a container.
1322
+ * @returns The layer instance.
992
1323
  *
993
- * @example Simple layer without parameters
1324
+ * @example Simple layer
994
1325
  * ```typescript
995
- * import { layer, Tag } from '@/di/layer.js';
1326
+ * import { layer, Tag } from 'sandly';
996
1327
  *
997
- * class DatabaseService extends Tag.Class('DatabaseService') {
1328
+ * class DatabaseService extends Tag.Service('DatabaseService') {
998
1329
  * constructor(private url: string = 'sqlite://memory') {}
999
1330
  * query() { return 'data'; }
1000
1331
  * }
@@ -1005,37 +1336,7 @@ type LayerFactory<TRequires extends AnyTag, TProvides extends AnyTag, TParams =
1005
1336
  * );
1006
1337
  *
1007
1338
  * // Usage
1008
- * const dbLayerInstance = databaseLayer(); // No parameters needed
1009
- * ```
1010
- *
1011
- * @example Layer with dependencies
1012
- * ```typescript
1013
- * const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
1014
- *
1015
- * // Layer that requires ConfigTag and provides DatabaseService
1016
- * const databaseLayer = layer<typeof ConfigTag, typeof DatabaseService>((container) =>
1017
- * container.register(DatabaseService, async (c) => {
1018
- * const config = await c.get(ConfigTag);
1019
- * return new DatabaseService(config.dbUrl);
1020
- * })
1021
- * );
1022
- * ```
1023
- *
1024
- * @example Parameterized layer
1025
- * ```typescript
1026
- * interface DatabaseConfig {
1027
- * host: string;
1028
- * port: number;
1029
- * }
1030
- *
1031
- * // Layer that takes configuration parameters
1032
- * const databaseLayer = layer<never, typeof DatabaseService, DatabaseConfig>(
1033
- * (container, config) =>
1034
- * container.register(DatabaseService, () => new DatabaseService(config))
1035
- * );
1036
- *
1037
- * // Usage with parameters
1038
- * const dbLayerInstance = databaseLayer({ host: 'localhost', port: 5432 });
1339
+ * const dbLayerInstance = databaseLayer;
1039
1340
  * ```
1040
1341
  *
1041
1342
  * @example Complex application layer structure
@@ -1049,37 +1350,45 @@ type LayerFactory<TRequires extends AnyTag, TProvides extends AnyTag, TParams =
1049
1350
  * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
1050
1351
  * (container) =>
1051
1352
  * container
1052
- * .register(DatabaseService, async (c) => new DatabaseService(await c.get(ConfigTag)))
1053
- * .register(CacheService, async (c) => new CacheService(await c.get(ConfigTag)))
1353
+ * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
1354
+ * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
1054
1355
  * );
1055
1356
  *
1056
1357
  * // Service layer (requires infrastructure)
1057
1358
  * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
1058
1359
  * (container) =>
1059
- * container.register(UserService, async (c) =>
1060
- * new UserService(await c.get(DatabaseService), await c.get(CacheService))
1360
+ * container.register(UserService, async (ctx) =>
1361
+ * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
1061
1362
  * )
1062
1363
  * );
1063
1364
  *
1064
1365
  * // Compose the complete application
1065
- * const appLayer = configLayer().to(infraLayer()).to(serviceLayer());
1366
+ * const appLayer = serviceLayer.provide(infraLayer).provide(configLayer);
1066
1367
  * ```
1067
1368
  */
1068
- declare function layer<TRequires extends AnyTag = never, TProvides extends AnyTag = never, TParams = undefined>(register: <TScope extends Scope>(container: IContainer<TRequires, TScope>, params: TParams) => IContainer<TRequires | TProvides, TScope>): LayerFactory<TRequires, TProvides, TParams>;
1369
+ declare function layer<TRequires extends AnyTag = never, TProvides extends AnyTag = never>(register: <TContainer extends AnyTag>(container: IContainer<TRequires | TContainer>) => IContainer<TRequires | TContainer | TProvides>): Layer<TRequires, TProvides>;
1069
1370
  /**
1070
1371
  * Helper type that extracts the union of all requirements from an array of layers.
1071
- * Used by Layer.merge() to compute the correct requirement type for the merged layer.
1372
+ * Used by Layer.mergeAll() to compute the correct requirement type for the merged layer.
1373
+ *
1374
+ * Works with AnyLayer[] constraint which accepts any concrete layer through variance:
1375
+ * - Layer<never, X> → extracts `never` (no requirements)
1376
+ * - Layer<A | B, Y> → extracts `A | B` (specific requirements)
1072
1377
  *
1073
1378
  * @internal
1074
1379
  */
1075
- type UnionOfRequires<T extends readonly Layer<AnyTag, AnyTag>[]> = { [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];
1076
1381
  /**
1077
1382
  * Helper type that extracts the union of all provisions from an array of layers.
1078
- * Used by Layer.merge() to compute the correct provision type for the merged layer.
1383
+ * Used by Layer.mergeAll() to compute the correct provision type for the merged layer.
1384
+ *
1385
+ * Works with AnyLayer[] constraint which accepts any concrete layer through variance:
1386
+ * - Layer<X, never> → extracts `never` (no provisions)
1387
+ * - Layer<Y, A | B> → extracts `A | B` (specific provisions)
1079
1388
  *
1080
1389
  * @internal
1081
1390
  */
1082
- type UnionOfProvides<T extends readonly Layer<AnyTag, AnyTag>[]> = { [K in keyof T]: T[K] extends Layer<AnyTag, 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];
1083
1392
  /**
1084
1393
  * Utility object containing helper functions for working with layers.
1085
1394
  */
@@ -1092,43 +1401,58 @@ declare const Layer: {
1092
1401
  *
1093
1402
  * @example
1094
1403
  * ```typescript
1095
- * import { Layer } from 'sandl';
1404
+ * import { Layer } from 'sandly';
1096
1405
  *
1097
1406
  * const baseLayer = Layer.empty();
1098
1407
  * const appLayer = baseLayer
1099
- * .and(configLayer())
1100
- * .and(serviceLayer());
1408
+ * .merge(configLayer)
1409
+ * .merge(serviceLayer);
1101
1410
  * ```
1102
1411
  */
1103
- empty(): Layer;
1412
+ empty(): Layer<never, never>;
1104
1413
  /**
1105
1414
  * Merges multiple layers at once in a type-safe way.
1106
- * This is equivalent to chaining `.and()` calls but more convenient for multiple layers.
1415
+ * This is equivalent to chaining `.merge()` calls but more convenient for multiple layers.
1416
+ *
1417
+ * ## Type Safety with Variance
1418
+ *
1419
+ * Uses the AnyLayer constraint (Layer<never, AnyTag>) which accepts any concrete layer
1420
+ * through the Layer interface's variance annotations:
1421
+ *
1422
+ * - **Contravariant TRequires**: Layer<typeof ServiceA, X> can be passed because requiring
1423
+ * ServiceA is more restrictive than requiring `never` (nothing)
1424
+ * - **Covariant TProvides**: Layer<Y, typeof ServiceB> can be passed because providing
1425
+ * ServiceB is compatible with the general `AnyTag` type
1426
+ *
1427
+ * The return type correctly extracts and unions the actual requirement/provision types
1428
+ * from all input layers, preserving full type safety.
1107
1429
  *
1108
1430
  * All layers are merged in order, combining their requirements and provisions.
1109
1431
  * The resulting layer requires the union of all input layer requirements and
1110
1432
  * provides the union of all input layer provisions.
1111
1433
  *
1112
- * @template T - The tuple type of layers to merge
1434
+ * @template T - The tuple type of layers to merge (constrained to AnyLayer for variance)
1113
1435
  * @param layers - At least 2 layers to merge together
1114
- * @returns A new layer that combines all input layers
1436
+ * @returns A new layer that combines all input layers with correct union types
1115
1437
  *
1116
- * @example Basic usage
1438
+ * @example Basic usage with different layer types
1117
1439
  * ```typescript
1118
- * import { Layer } from 'sandl';
1440
+ * import { Layer } from 'sandly';
1119
1441
  *
1120
- * const infraLayer = Layer.merge(
1121
- * databaseLayer(),
1122
- * cacheLayer(),
1123
- * loggingLayer()
1124
- * );
1442
+ * // These all have different types but work thanks to variance:
1443
+ * const dbLayer = layer<never, typeof DatabaseService>(...); // no requirements
1444
+ * const userLayer = layer<typeof DatabaseService, typeof UserService>(...); // requires DB
1445
+ * const configLayer = layer<never, typeof ConfigService>(...); // no requirements
1446
+ *
1447
+ * const infraLayer = Layer.mergeAll(dbLayer, userLayer, configLayer);
1448
+ * // Type: Layer<typeof DatabaseService, typeof DatabaseService | typeof UserService | typeof ConfigService>
1125
1449
  * ```
1126
1450
  *
1127
- * @example Equivalent to chaining .and()
1451
+ * @example Equivalent to chaining .merge()
1128
1452
  * ```typescript
1129
1453
  * // These are equivalent:
1130
- * const layer1 = Layer.merge(layerA(), layerB(), layerC());
1131
- * const layer2 = layerA().and(layerB()).and(layerC());
1454
+ * const layer1 = Layer.mergeAll(layerA, layerB, layerC);
1455
+ * const layer2 = layerA.merge(layerB).merge(layerC);
1132
1456
  * ```
1133
1457
  *
1134
1458
  * @example Building infrastructure layers
@@ -1138,79 +1462,211 @@ declare const Layer: {
1138
1462
  * const observabilityLayer = layer<never, typeof Logger | typeof Metrics>(...);
1139
1463
  *
1140
1464
  * // Merge all infrastructure concerns into one layer
1141
- * const infraLayer = Layer.merge(
1142
- * persistenceLayer(),
1143
- * messagingLayer(),
1144
- * observabilityLayer()
1465
+ * const infraLayer = Layer.mergeAll(
1466
+ * persistenceLayer,
1467
+ * messagingLayer,
1468
+ * observabilityLayer
1145
1469
  * );
1146
1470
  *
1147
- * // Now infraLayer provides: DatabaseService | CacheService | MessageQueue | Logger | Metrics
1471
+ * // Result type: Layer<never, DatabaseService | CacheService | MessageQueue | Logger | Metrics>
1148
1472
  * ```
1149
1473
  */
1150
- merge<T extends readonly [Layer<AnyTag, AnyTag>, Layer<AnyTag, AnyTag>, ...Layer<AnyTag, AnyTag>[]]>(...layers: T): Layer<UnionOfRequires<T>, UnionOfProvides<T>>;
1474
+ mergeAll<T extends readonly [AnyLayer, AnyLayer, ...AnyLayer[]]>(...layers: T): Layer<UnionOfRequires<T>, UnionOfProvides<T>>;
1475
+ /**
1476
+ * Merges exactly two layers, combining their requirements and provisions.
1477
+ * This is similar to the `.merge()` method but available as a static function.
1478
+ *
1479
+ * @template TRequires1 - What the first layer requires
1480
+ * @template TProvides1 - What the first layer provides
1481
+ * @template TRequires2 - What the second layer requires
1482
+ * @template TProvides2 - What the second layer provides
1483
+ * @param layer1 - The first layer to merge
1484
+ * @param layer2 - The second layer to merge
1485
+ * @returns A new merged layer requiring both layers' requirements and providing both layers' provisions
1486
+ *
1487
+ * @example Merging two layers
1488
+ * ```typescript
1489
+ * import { Layer } from 'sandly';
1490
+ *
1491
+ * const dbLayer = layer<never, typeof DatabaseService>(...);
1492
+ * const cacheLayer = layer<never, typeof CacheService>(...);
1493
+ *
1494
+ * const persistenceLayer = Layer.merge(dbLayer, cacheLayer);
1495
+ * // Type: Layer<never, typeof DatabaseService | typeof CacheService>
1496
+ * ```
1497
+ */
1498
+ merge<TRequires1 extends AnyTag, TProvides1 extends AnyTag, TRequires2 extends AnyTag, TProvides2 extends AnyTag>(layer1: Layer<TRequires1, TProvides1>, layer2: Layer<TRequires2, TProvides2>): Layer<TRequires1 | TRequires2, TProvides1 | TProvides2>;
1151
1499
  };
1152
1500
  //#endregion
1153
- //#region src/service.d.ts
1501
+ //#region src/scoped-container.d.ts
1502
+ type Scope = string | symbol;
1503
+ declare class ScopedContainer<TReg extends AnyTag> extends Container<TReg> {
1504
+ readonly scope: Scope;
1505
+ private parent;
1506
+ private readonly children;
1507
+ protected constructor(parent: IContainer<TReg> | null, scope: Scope);
1508
+ static empty(scope: Scope): ScopedContainer<never>;
1509
+ /**
1510
+ * Registers a dependency in the scoped container.
1511
+ *
1512
+ * Overrides the base implementation to return ScopedContainer type
1513
+ * for proper method chaining support.
1514
+ */
1515
+ register<T extends AnyTag>(tag: T, spec: DependencySpec<T, TReg>): ScopedContainer<TReg | T>;
1516
+ /**
1517
+ * Checks if a dependency has been registered in this scope or any parent scope.
1518
+ *
1519
+ * This method checks the current scope first, then walks up the parent chain.
1520
+ * Returns true if the dependency has been registered somewhere in the scope hierarchy.
1521
+ */
1522
+ has(tag: AnyTag): boolean;
1523
+ /**
1524
+ * Checks if a dependency has been instantiated in this scope or any parent scope.
1525
+ *
1526
+ * This method checks the current scope first, then walks up the parent chain.
1527
+ * Returns true if the dependency has been instantiated somewhere in the scope hierarchy.
1528
+ */
1529
+ exists(tag: AnyTag): boolean;
1530
+ /**
1531
+ * Retrieves a dependency instance, resolving from the current scope or parent scopes.
1532
+ *
1533
+ * Resolution strategy:
1534
+ * 1. Check cache in current scope
1535
+ * 2. Check if factory exists in current scope - if so, create instance here
1536
+ * 3. Otherwise, delegate to parent scope
1537
+ * 4. If no parent or parent doesn't have it, throw UnknownDependencyError
1538
+ */
1539
+ resolve<T extends TReg>(tag: T): Promise<TagType<T>>;
1540
+ /**
1541
+ * Destroys this scoped container and its children, preserving the container structure for reuse.
1542
+ *
1543
+ * This method ensures proper cleanup order while maintaining reusability:
1544
+ * 1. Destroys all child scopes first (they may depend on parent scope dependencies)
1545
+ * 2. Then calls finalizers for dependencies created in this scope
1546
+ * 3. Clears only instance caches - preserves factories, finalizers, and child structure
1547
+ *
1548
+ * Child destruction happens first to ensure dependencies don't get cleaned up
1549
+ * before their dependents.
1550
+ */
1551
+ destroy(): Promise<void>;
1552
+ /**
1553
+ * Creates a new scoped container by merging this container's registrations with another container.
1554
+ *
1555
+ * This method overrides the base Container.merge to return a ScopedContainer instead of a regular Container.
1556
+ * The resulting scoped container contains all registrations from both containers and becomes a root scope
1557
+ * (no parent) with the scope name from this container.
1558
+ *
1559
+ * @param other - The container to merge with
1560
+ * @returns A new ScopedContainer with combined registrations
1561
+ * @throws {ContainerDestroyedError} If this container has been destroyed
1562
+ */
1563
+ merge<TTarget extends AnyTag>(other: Container<TTarget>): ScopedContainer<TReg | TTarget>;
1564
+ /**
1565
+ * Creates a child scoped container.
1566
+ *
1567
+ * Child containers inherit access to parent dependencies but maintain
1568
+ * their own scope for new registrations and instance caching.
1569
+ */
1570
+ child(scope: Scope): ScopedContainer<TReg>;
1571
+ }
1154
1572
  /**
1155
- * Extracts constructor parameter types from a TaggedClass.
1156
- * Only parameters that extend AnyTag are considered as dependencies.
1573
+ * Converts a regular container into a scoped container, copying all registrations.
1574
+ *
1575
+ * This function creates a new ScopedContainer instance and copies all factory functions
1576
+ * and finalizers from the source container. The resulting scoped container becomes a root
1577
+ * scope (no parent) with all the same dependency registrations.
1578
+ *
1579
+ * **Important**: Only the registrations are copied, not any cached instances.
1580
+ * The new scoped container starts with an empty instance cache.
1581
+ *
1582
+ * @param container - The container to convert to a scoped container
1583
+ * @param scope - A string or symbol identifier for this scope (used for debugging)
1584
+ * @returns A new ScopedContainer instance with all registrations copied from the source container
1585
+ * @throws {ContainerDestroyedError} If the source container has been destroyed
1586
+ *
1587
+ * @example Converting a regular container to scoped
1588
+ * ```typescript
1589
+ * import { container, scoped } from 'sandly';
1590
+ *
1591
+ * const appContainer = Container.empty()
1592
+ * .register(DatabaseService, () => new DatabaseService())
1593
+ * .register(ConfigService, () => new ConfigService());
1594
+ *
1595
+ * const scopedAppContainer = scoped(appContainer, 'app');
1596
+ *
1597
+ * // Create child scopes
1598
+ * const requestContainer = scopedAppContainer.child('request');
1599
+ * ```
1600
+ *
1601
+ * @example Copying complex registrations
1602
+ * ```typescript
1603
+ * const baseContainer = Container.empty()
1604
+ * .register(DatabaseService, () => new DatabaseService())
1605
+ * .register(UserService, {
1606
+ * factory: async (ctx) => new UserService(await ctx.resolve(DatabaseService)),
1607
+ * finalizer: (service) => service.cleanup()
1608
+ * });
1609
+ *
1610
+ * const scopedContainer = scoped(baseContainer, 'app');
1611
+ * // scopedContainer now has all the same registrations with finalizers preserved
1612
+ * ```
1157
1613
  */
1158
- type ConstructorParams<T extends ClassTag<unknown>> = T extends (new (...args: infer A) => unknown) ? A : never;
1614
+ declare function scoped<TReg extends AnyTag>(container: Container<TReg>, scope: Scope): ScopedContainer<TReg>;
1615
+ //#endregion
1616
+ //#region src/service.d.ts
1159
1617
  /**
1160
- * Helper to convert a tagged instance type back to its constructor type.
1161
- * This uses the fact that tagged classes have a specific structure with TagId property.
1618
+ * Extracts constructor parameter types from a ServiceTag.
1619
+ * Only parameters that extend AnyTag are considered as dependencies.
1620
+ * @internal
1162
1621
  */
1163
- type InstanceToConstructorType<T> = T extends {
1164
- readonly [TagId]: infer Id;
1165
- } ? Id extends string | symbol ? TaggedClass<T, Id> : never : never;
1622
+ type ConstructorParams<T extends ServiceTag<TagId, unknown>> = T extends (new (...args: infer A) => unknown) ? A : never;
1166
1623
  /**
1167
- * Extracts constructor-typed dependencies from constructor parameters.
1168
- * Converts instance types to their corresponding constructor types.
1169
- * Handles both ClassTag dependencies (automatic) and ValueTag dependencies (via Inject helper).
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
1170
1631
  */
1171
- type FilterTags<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
1172
- readonly [TagId]: string | symbol;
1173
- } ? InstanceToConstructorType<T[K]> : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
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];
1174
1635
  /**
1175
- * Extracts the instance type that a TaggedClass constructor creates.
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
1176
1640
  */
1177
1641
 
1178
1642
  /**
1179
- * Extracts only the dependency tags from a constructor's parameters for ClassTag services,
1180
- * or returns never for ValueTag services (which have no constructor dependencies).
1181
- * This is used to determine what dependencies a service requires.
1643
+ * Union of all dependency tags a ServiceTag constructor requires.
1644
+ * Filters out non‑DI parameters.
1182
1645
  */
1183
- type ServiceDependencies<T extends AnyTag> = T extends ClassTag<unknown> ? FilterTags<ConstructorParams<T>> extends AnyTag ? FilterTags<ConstructorParams<T>> : never : never;
1646
+ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstructorDeps<ConstructorParams<T>> extends AnyTag ? ExtractConstructorDeps<ConstructorParams<T>> : never;
1184
1647
  /**
1185
- * Represents a service layer that can be created from any tag type.
1186
- * For ClassTag services, dependencies are automatically inferred from constructor parameters.
1187
- * For ValueTag services, there are no dependencies since they don't have constructors.
1648
+ * Ordered tuple of dependency tags (and other constructor params)
1649
+ * inferred from a ServiceTag’s constructor.
1188
1650
  */
1189
- interface Service<T extends AnyTag> extends Layer<ServiceDependencies<T>, T> {
1190
- /**
1191
- * The tag that this service represents (ClassTag or ValueTag)
1192
- */
1193
- readonly serviceClass: T;
1194
- }
1651
+
1195
1652
  /**
1196
- * 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.
1197
1654
  *
1198
- * For ClassTag services:
1655
+ * For ServiceTag services:
1199
1656
  * - Dependencies are automatically inferred from constructor parameters
1200
1657
  * - The factory function must handle dependency injection by resolving dependencies from the container
1201
1658
  *
1202
1659
  * For ValueTag services:
1203
1660
  * - No constructor dependencies are needed since they don't have constructors
1204
1661
  *
1205
- * @template T - The tag representing the service (ClassTag or ValueTag)
1206
- * @template TParams - Optional parameters for service configuration
1207
- * @param serviceClass - The tag (ClassTag or ValueTag)
1208
- * @param factory - Factory function for service instantiation with container and optional params
1209
- * @returns A factory function that creates a service layer
1662
+ * @template T - The tag representing the service (ServiceTag or ValueTag)
1663
+ * @param tag - The tag (ServiceTag or ValueTag)
1664
+ * @param factory - Factory function for service instantiation with container
1665
+ * @returns The service layer
1210
1666
  *
1211
1667
  * @example Simple service without dependencies
1212
1668
  * ```typescript
1213
- * class LoggerService extends Tag.Class('LoggerService') {
1669
+ * class LoggerService extends Tag.Service('LoggerService') {
1214
1670
  * log(message: string) { console.log(message); }
1215
1671
  * }
1216
1672
  *
@@ -1219,11 +1675,11 @@ interface Service<T extends AnyTag> extends Layer<ServiceDependencies<T>, T> {
1219
1675
  *
1220
1676
  * @example Service with dependencies
1221
1677
  * ```typescript
1222
- * class DatabaseService extends Tag.Class('DatabaseService') {
1678
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1223
1679
  * query() { return []; }
1224
1680
  * }
1225
1681
  *
1226
- * class UserService extends Tag.Class('UserService') {
1682
+ * class UserService extends Tag.Service('UserService') {
1227
1683
  * constructor(private db: DatabaseService) {
1228
1684
  * super();
1229
1685
  * }
@@ -1231,25 +1687,128 @@ interface Service<T extends AnyTag> extends Layer<ServiceDependencies<T>, T> {
1231
1687
  * getUsers() { return this.db.query(); }
1232
1688
  * }
1233
1689
  *
1234
- * const userService = service(UserService, async (container) =>
1235
- * new UserService(await container.get(DatabaseService))
1690
+ * const userService = service(UserService, async (ctx) =>
1691
+ * new UserService(await ctx.resolve(DatabaseService))
1236
1692
  * );
1237
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
+ * ```
1238
1762
  *
1239
- * @example Service with configuration parameters
1763
+ * @example With finalizer for cleanup
1240
1764
  * ```typescript
1241
- * class DatabaseService extends Tag.Class('DatabaseService') {
1242
- * constructor(private config: { dbUrl: string }) {
1765
+ * class DatabaseService extends Tag.Service('DatabaseService') {
1766
+ * constructor(private connectionString: string) {
1243
1767
  * super();
1244
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
+ * }
1245
1782
  * }
1246
1783
  *
1247
- * const dbService = service(
1784
+ * // Service with automatic cleanup
1785
+ * const dbService = autoService(
1248
1786
  * DatabaseService,
1249
- * (container, params: { dbUrl: string }) => new DatabaseService(params)
1787
+ * ['postgresql://localhost:5432/mydb'],
1788
+ * (service) => service.disconnect() // Finalizer for cleanup
1250
1789
  * );
1251
1790
  * ```
1252
1791
  */
1253
- declare function service<T extends AnyTag, TParams = undefined>(serviceClass: T, factory: (container: IContainer<ServiceDependencies<T>>, params: TParams) => PromiseOrValue<ServiceOf<T>>): TParams extends undefined ? () => Service<T> : (params: TParams) => Service<T>;
1254
1792
  //#endregion
1255
- export { type Inject, Layer, type Scope, type Service, type ServiceOf, Tag, type TaggedClass, type ValueTag, container, layer, scopedContainer, service };
1793
+ //#region src/value.d.ts
1794
+ /**
1795
+ * Creates a layer that provides a constant value for a given tag.
1796
+ *
1797
+ * @param tag - The value tag to provide
1798
+ * @param constantValue - The constant value to provide
1799
+ * @returns A layer with no dependencies that provides the constant value
1800
+ *
1801
+ * @example
1802
+ * ```typescript
1803
+ * const ApiKey = Tag.of('ApiKey')<string>();
1804
+ * const DatabaseUrl = Tag.of('DatabaseUrl')<string>();
1805
+ *
1806
+ * const apiKey = value(ApiKey, 'my-secret-key');
1807
+ * const dbUrl = value(DatabaseUrl, 'postgresql://localhost:5432/myapp');
1808
+ *
1809
+ * const config = Layer.merge(apiKey, dbUrl);
1810
+ * ```
1811
+ */
1812
+ declare function value<Id extends TagId, T>(tag: ValueTag<Id, T>, constantValue: T): Layer<never, ValueTag<Id, T>>;
1813
+ //#endregion
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 };