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