sandly 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts +59 -36
- package/dist/index.js +151 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,13 +23,13 @@ class DatabaseService extends Tag.Service('DatabaseService') {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const container = Container.empty().register(DatabaseService, () => new DatabaseService());
|
|
27
27
|
|
|
28
28
|
// ✅ TypeScript knows DatabaseService is available
|
|
29
|
-
const db = await
|
|
29
|
+
const db = await container.resolve(DatabaseService);
|
|
30
30
|
|
|
31
31
|
// ❌ Compile error - UserService not registered
|
|
32
|
-
const user = await
|
|
32
|
+
const user = await container.resolve(UserService); // Type error
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Modular Architecture with Layers
|
package/dist/index.d.ts
CHANGED
|
@@ -13,15 +13,15 @@ type TagId = string | symbol;
|
|
|
13
13
|
*
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
|
-
declare const ValueTagIdKey = "
|
|
17
|
-
declare const ServiceTagIdKey = "
|
|
16
|
+
declare const ValueTagIdKey = "sandly/ValueTagIdKey";
|
|
17
|
+
declare const ServiceTagIdKey = "sandly/ServiceTagIdKey";
|
|
18
18
|
/**
|
|
19
|
-
* Internal
|
|
20
|
-
* This
|
|
19
|
+
* Internal string used to identify the type of a tagged type within the dependency injection system.
|
|
20
|
+
* This string is used as a property key to attach metadata to both value tags and service tags.
|
|
21
21
|
* It is used to carry the type of the tagged type and should not be used directly.
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
|
24
|
-
declare const TagTypeKey
|
|
24
|
+
declare const TagTypeKey = "sandly/TagTypeKey";
|
|
25
25
|
/**
|
|
26
26
|
* Type representing a value-based dependency tag.
|
|
27
27
|
*
|
|
@@ -70,9 +70,9 @@ interface ValueTag<Id extends TagId, T> {
|
|
|
70
70
|
*/
|
|
71
71
|
interface ServiceTag<Id extends TagId, T> {
|
|
72
72
|
new (...args: any[]): T & {
|
|
73
|
-
readonly [ServiceTagIdKey]
|
|
73
|
+
readonly [ServiceTagIdKey]?: Id;
|
|
74
74
|
};
|
|
75
|
-
readonly [ServiceTagIdKey]
|
|
75
|
+
readonly [ServiceTagIdKey]?: Id;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
78
|
* Utility type that extracts the service type from any dependency tag.
|
|
@@ -286,7 +286,7 @@ declare const Tag: {
|
|
|
286
286
|
* ```
|
|
287
287
|
*/
|
|
288
288
|
Service: <Id extends TagId>(id: Id) => ServiceTag<Id, {
|
|
289
|
-
readonly "
|
|
289
|
+
readonly "sandly/ServiceTagIdKey"?: Id;
|
|
290
290
|
}>;
|
|
291
291
|
/**
|
|
292
292
|
* Extracts the string representation of a tag's identifier.
|
|
@@ -311,14 +311,14 @@ declare const Tag: {
|
|
|
311
311
|
*
|
|
312
312
|
* @internal - Primarily for internal use in error messages and debugging
|
|
313
313
|
*/
|
|
314
|
-
id: (tag: AnyTag) => TagId;
|
|
314
|
+
id: (tag: AnyTag) => TagId | undefined;
|
|
315
315
|
isTag: (tag: unknown) => tag is AnyTag;
|
|
316
316
|
};
|
|
317
317
|
/**
|
|
318
|
-
*
|
|
318
|
+
* String used to store the original ValueTag in Inject<T> types.
|
|
319
319
|
* This prevents property name collisions while allowing type-level extraction.
|
|
320
320
|
*/
|
|
321
|
-
declare const InjectSource
|
|
321
|
+
declare const InjectSource = "sandly/InjectSource";
|
|
322
322
|
/**
|
|
323
323
|
* Helper type for injecting ValueTag dependencies in constructor parameters.
|
|
324
324
|
* This allows clean specification of ValueTag dependencies while preserving
|
|
@@ -549,7 +549,7 @@ interface IContainer<TReg extends AnyTag = never> {
|
|
|
549
549
|
* getUser() { return this.db.query(); }
|
|
550
550
|
* }
|
|
551
551
|
*
|
|
552
|
-
* const
|
|
552
|
+
* const container = Container.empty()
|
|
553
553
|
* .register(DatabaseService, () => new DatabaseService())
|
|
554
554
|
* .register(UserService, async (ctx) =>
|
|
555
555
|
* new UserService(await ctx.resolve(DatabaseService))
|
|
@@ -563,7 +563,7 @@ interface IContainer<TReg extends AnyTag = never> {
|
|
|
563
563
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
564
564
|
* const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
|
|
565
565
|
*
|
|
566
|
-
* const
|
|
566
|
+
* const container = Container.empty()
|
|
567
567
|
* .register(ApiKeyTag, () => process.env.API_KEY!)
|
|
568
568
|
* .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
|
|
569
569
|
*
|
|
@@ -578,7 +578,7 @@ interface IContainer<TReg extends AnyTag = never> {
|
|
|
578
578
|
* async disconnect() { return; }
|
|
579
579
|
* }
|
|
580
580
|
*
|
|
581
|
-
* const
|
|
581
|
+
* const container = Container.empty().register(
|
|
582
582
|
* DatabaseConnection,
|
|
583
583
|
* async () => {
|
|
584
584
|
* const conn = new DatabaseConnection();
|
|
@@ -596,6 +596,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
596
596
|
readonly [ContainerTypeId]: {
|
|
597
597
|
readonly _TReg: Contravariant<TReg>;
|
|
598
598
|
};
|
|
599
|
+
protected constructor();
|
|
599
600
|
/**
|
|
600
601
|
* Cache of instantiated dependencies as promises.
|
|
601
602
|
* Ensures singleton behavior and supports concurrent access.
|
|
@@ -617,6 +618,10 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
617
618
|
* @internal
|
|
618
619
|
*/
|
|
619
620
|
protected isDestroyed: boolean;
|
|
621
|
+
/**
|
|
622
|
+
* Creates a new empty container instance.
|
|
623
|
+
* @returns A new empty Container instance with no registered dependencies.
|
|
624
|
+
*/
|
|
620
625
|
static empty(): Container<never>;
|
|
621
626
|
/**
|
|
622
627
|
* Registers a dependency in the container with a factory function and optional finalizer.
|
|
@@ -642,7 +647,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
642
647
|
* log(message: string) { console.log(message); }
|
|
643
648
|
* }
|
|
644
649
|
*
|
|
645
|
-
* const
|
|
650
|
+
* const container = Container.empty().register(
|
|
646
651
|
* LoggerService,
|
|
647
652
|
* () => new LoggerService()
|
|
648
653
|
* );
|
|
@@ -654,7 +659,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
654
659
|
* constructor(private db: DatabaseService, private logger: LoggerService) {}
|
|
655
660
|
* }
|
|
656
661
|
*
|
|
657
|
-
* const
|
|
662
|
+
* const container = Container.empty()
|
|
658
663
|
* .register(DatabaseService, () => new DatabaseService())
|
|
659
664
|
* .register(LoggerService, () => new LoggerService())
|
|
660
665
|
* .register(UserService, async (ctx) =>
|
|
@@ -667,7 +672,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
667
672
|
*
|
|
668
673
|
* @example Overriding a dependency
|
|
669
674
|
* ```typescript
|
|
670
|
-
* const
|
|
675
|
+
* const container = Container.empty()
|
|
671
676
|
* .register(DatabaseService, () => new DatabaseService())
|
|
672
677
|
* .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
|
|
673
678
|
* ```
|
|
@@ -676,7 +681,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
676
681
|
* ```typescript
|
|
677
682
|
* const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
|
|
678
683
|
*
|
|
679
|
-
* const
|
|
684
|
+
* const container = Container.empty().register(
|
|
680
685
|
* ConfigTag,
|
|
681
686
|
* () => ({ apiUrl: 'https://api.example.com' })
|
|
682
687
|
* );
|
|
@@ -689,7 +694,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
689
694
|
* async close() { return; }
|
|
690
695
|
* }
|
|
691
696
|
*
|
|
692
|
-
* const
|
|
697
|
+
* const container = Container.empty().register(
|
|
693
698
|
* DatabaseConnection,
|
|
694
699
|
* async () => {
|
|
695
700
|
* const conn = new DatabaseConnection();
|
|
@@ -712,7 +717,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
712
717
|
*
|
|
713
718
|
* @example
|
|
714
719
|
* ```typescript
|
|
715
|
-
* const
|
|
720
|
+
* const container = Container.empty().register(DatabaseService, () => new DatabaseService());
|
|
716
721
|
* console.log(c.has(DatabaseService)); // true
|
|
717
722
|
* ```
|
|
718
723
|
*/
|
|
@@ -743,7 +748,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
743
748
|
*
|
|
744
749
|
* @example Basic usage
|
|
745
750
|
* ```typescript
|
|
746
|
-
* const
|
|
751
|
+
* const container = Container.empty()
|
|
747
752
|
* .register(DatabaseService, () => new DatabaseService());
|
|
748
753
|
*
|
|
749
754
|
* const db = await c.resolve(DatabaseService);
|
|
@@ -764,7 +769,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
764
769
|
*
|
|
765
770
|
* @example Dependency injection in factories
|
|
766
771
|
* ```typescript
|
|
767
|
-
* const
|
|
772
|
+
* const container = Container.empty()
|
|
768
773
|
* .register(DatabaseService, () => new DatabaseService())
|
|
769
774
|
* .register(UserService, async (ctx) => {
|
|
770
775
|
* const db = await ctx.resolve(DatabaseService);
|
|
@@ -793,7 +798,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
793
798
|
*
|
|
794
799
|
* @example Basic usage
|
|
795
800
|
* ```typescript
|
|
796
|
-
* const
|
|
801
|
+
* const container = Container.empty()
|
|
797
802
|
* .register(DatabaseService, () => new DatabaseService())
|
|
798
803
|
* .register(LoggerService, () => new LoggerService());
|
|
799
804
|
*
|
|
@@ -803,7 +808,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
803
808
|
* @example Mixed tag types
|
|
804
809
|
* ```typescript
|
|
805
810
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
806
|
-
* const
|
|
811
|
+
* const container = Container.empty()
|
|
807
812
|
* .register(ApiKeyTag, () => 'secret-key')
|
|
808
813
|
* .register(UserService, () => new UserService());
|
|
809
814
|
*
|
|
@@ -872,7 +877,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
872
877
|
*
|
|
873
878
|
* @example Basic cleanup
|
|
874
879
|
* ```typescript
|
|
875
|
-
* const
|
|
880
|
+
* const container = Container.empty()
|
|
876
881
|
* .register(DatabaseConnection,
|
|
877
882
|
* async () => {
|
|
878
883
|
* const conn = new DatabaseConnection();
|
|
@@ -995,7 +1000,7 @@ declare class ContainerDestroyedError extends ContainerError {}
|
|
|
995
1000
|
*
|
|
996
1001
|
* @example
|
|
997
1002
|
* ```typescript
|
|
998
|
-
* const
|
|
1003
|
+
* const container = Container.empty(); // Empty container
|
|
999
1004
|
*
|
|
1000
1005
|
* try {
|
|
1001
1006
|
* await c.resolve(UnregisteredService); // This will throw
|
|
@@ -1027,7 +1032,7 @@ declare class UnknownDependencyError extends ContainerError {
|
|
|
1027
1032
|
* class ServiceA extends Tag.Service('ServiceA') {}
|
|
1028
1033
|
* class ServiceB extends Tag.Service('ServiceB') {}
|
|
1029
1034
|
*
|
|
1030
|
-
* const
|
|
1035
|
+
* const container = Container.empty()
|
|
1031
1036
|
* .register(ServiceA, async (ctx) =>
|
|
1032
1037
|
* new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
|
|
1033
1038
|
* )
|
|
@@ -1065,7 +1070,7 @@ declare class CircularDependencyError extends ContainerError {
|
|
|
1065
1070
|
* ```typescript
|
|
1066
1071
|
* class DatabaseService extends Tag.Service('DatabaseService') {}
|
|
1067
1072
|
*
|
|
1068
|
-
* const
|
|
1073
|
+
* const container = Container.empty().register(DatabaseService, () => {
|
|
1069
1074
|
* throw new Error('Database connection failed');
|
|
1070
1075
|
* });
|
|
1071
1076
|
*
|
|
@@ -1163,7 +1168,7 @@ declare const LayerTypeId: unique symbol;
|
|
|
1163
1168
|
* );
|
|
1164
1169
|
*
|
|
1165
1170
|
* // Apply the layer to a container
|
|
1166
|
-
* const
|
|
1171
|
+
* const container = Container.empty();
|
|
1167
1172
|
* const finalContainer = databaseLayer.register(c);
|
|
1168
1173
|
*
|
|
1169
1174
|
* const db = await finalContainer.resolve(DatabaseService);
|
|
@@ -1204,7 +1209,7 @@ interface Layer<TRequires extends AnyTag, TProvides extends AnyTag> {
|
|
|
1204
1209
|
*
|
|
1205
1210
|
* @example Basic usage
|
|
1206
1211
|
* ```typescript
|
|
1207
|
-
* const
|
|
1212
|
+
* const container = Container.empty();
|
|
1208
1213
|
* const updatedContainer = myLayer.register(c);
|
|
1209
1214
|
* ```
|
|
1210
1215
|
*
|
|
@@ -1505,6 +1510,11 @@ declare class ScopedContainer<TReg extends AnyTag> extends Container<TReg> {
|
|
|
1505
1510
|
private parent;
|
|
1506
1511
|
private readonly children;
|
|
1507
1512
|
protected constructor(parent: IContainer<TReg> | null, scope: Scope);
|
|
1513
|
+
/**
|
|
1514
|
+
* Creates a new empty scoped container instance.
|
|
1515
|
+
* @param scope - The scope identifier for this container
|
|
1516
|
+
* @returns A new empty ScopedContainer instance with no registered dependencies
|
|
1517
|
+
*/
|
|
1508
1518
|
static empty(scope: Scope): ScopedContainer<never>;
|
|
1509
1519
|
/**
|
|
1510
1520
|
* Registers a dependency in the scoped container.
|
|
@@ -1630,7 +1640,7 @@ type ConstructorParams<T extends ServiceTag<TagId, unknown>> = T extends (new (.
|
|
|
1630
1640
|
* @internal
|
|
1631
1641
|
*/
|
|
1632
1642
|
type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
|
|
1633
|
-
readonly [ServiceTagIdKey]
|
|
1643
|
+
readonly [ServiceTagIdKey]?: infer Id;
|
|
1634
1644
|
} ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? never : ExtractInjectTag<T[K]> }[number];
|
|
1635
1645
|
/**
|
|
1636
1646
|
* Produces an ordered tuple of constructor parameters
|
|
@@ -1638,7 +1648,9 @@ type ExtractConstructorDeps<T extends readonly unknown[]> = T extends readonly [
|
|
|
1638
1648
|
* while non‑DI parameters are preserved as‑is.
|
|
1639
1649
|
* @internal
|
|
1640
1650
|
*/
|
|
1641
|
-
|
|
1651
|
+
type InferConstructorDepsTuple<T extends readonly unknown[]> = T extends readonly [] ? never : { [K in keyof T]: T[K] extends {
|
|
1652
|
+
readonly [ServiceTagIdKey]?: infer Id;
|
|
1653
|
+
} ? Id extends TagId ? ServiceTag<Id, T[K]> : never : ExtractInjectTag<T[K]> extends never ? T[K] : ExtractInjectTag<T[K]> };
|
|
1642
1654
|
/**
|
|
1643
1655
|
* Union of all dependency tags a ServiceTag constructor requires.
|
|
1644
1656
|
* Filters out non‑DI parameters.
|
|
@@ -1648,7 +1660,7 @@ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstruc
|
|
|
1648
1660
|
* Ordered tuple of dependency tags (and other constructor params)
|
|
1649
1661
|
* inferred from a ServiceTag’s constructor.
|
|
1650
1662
|
*/
|
|
1651
|
-
|
|
1663
|
+
type ServiceDepsTuple<T extends ServiceTag<TagId, unknown>> = InferConstructorDepsTuple<ConstructorParams<T>>;
|
|
1652
1664
|
/**
|
|
1653
1665
|
* Creates a service layer from any tag type (ServiceTag or ValueTag) with optional parameters.
|
|
1654
1666
|
*
|
|
@@ -1693,6 +1705,14 @@ type ServiceDependencies<T extends ServiceTag<TagId, unknown>> = ExtractConstruc
|
|
|
1693
1705
|
* ```
|
|
1694
1706
|
*/
|
|
1695
1707
|
declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: DependencySpec<T, ServiceDependencies<T>>): Layer<ServiceDependencies<T>, T>;
|
|
1708
|
+
/**
|
|
1709
|
+
* Specification for autoService.
|
|
1710
|
+
* Can be either a tuple of constructor parameters or an object with dependencies and finalizer.
|
|
1711
|
+
*/
|
|
1712
|
+
type AutoServiceSpec<T extends ServiceTag<TagId, unknown>> = ServiceDepsTuple<T> | {
|
|
1713
|
+
dependencies: ServiceDepsTuple<T>;
|
|
1714
|
+
finalizer?: Finalizer<TagType<T>>;
|
|
1715
|
+
};
|
|
1696
1716
|
/**
|
|
1697
1717
|
* Creates a service layer with automatic dependency injection by inferring constructor parameters.
|
|
1698
1718
|
*
|
|
@@ -1784,11 +1804,14 @@ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: Dep
|
|
|
1784
1804
|
* // Service with automatic cleanup
|
|
1785
1805
|
* const dbService = autoService(
|
|
1786
1806
|
* DatabaseService,
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1807
|
+
* {
|
|
1808
|
+
* dependencies: ['postgresql://localhost:5432/mydb'],
|
|
1809
|
+
* finalizer: (service) => service.disconnect() // Finalizer for cleanup
|
|
1810
|
+
* }
|
|
1789
1811
|
* );
|
|
1790
1812
|
* ```
|
|
1791
1813
|
*/
|
|
1814
|
+
declare function autoService<T extends ServiceTag<TagId, unknown>>(tag: T, spec: AutoServiceSpec<T>): Layer<ServiceDependencies<T>, T>;
|
|
1792
1815
|
//#endregion
|
|
1793
1816
|
//#region src/value.d.ts
|
|
1794
1817
|
/**
|
|
@@ -1811,4 +1834,4 @@ declare function service<T extends ServiceTag<TagId, unknown>>(tag: T, spec: Dep
|
|
|
1811
1834
|
*/
|
|
1812
1835
|
declare function value<Id extends TagId, T>(tag: ValueTag<Id, T>, constantValue: T): Layer<never, ValueTag<Id, T>>;
|
|
1813
1836
|
//#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 };
|
|
1837
|
+
export { type AnyLayer, type AnyTag, CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type DependencySpec, type Factory, type Finalizer, type IContainer, type Inject, InjectSource, Layer, type PromiseOrValue, type ResolutionContext, type Scope, ScopedContainer, type ServiceDependencies, type ServiceDepsTuple, type ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, autoService, layer, scoped, service, value };
|
package/dist/index.js
CHANGED
|
@@ -24,15 +24,15 @@ function getKey(obj, ...keys) {
|
|
|
24
24
|
*
|
|
25
25
|
* @internal
|
|
26
26
|
*/
|
|
27
|
-
const ValueTagIdKey = "
|
|
28
|
-
const ServiceTagIdKey = "
|
|
27
|
+
const ValueTagIdKey = "sandly/ValueTagIdKey";
|
|
28
|
+
const ServiceTagIdKey = "sandly/ServiceTagIdKey";
|
|
29
29
|
/**
|
|
30
|
-
* Internal
|
|
31
|
-
* This
|
|
30
|
+
* Internal string used to identify the type of a tagged type within the dependency injection system.
|
|
31
|
+
* This string is used as a property key to attach metadata to both value tags and service tags.
|
|
32
32
|
* It is used to carry the type of the tagged type and should not be used directly.
|
|
33
33
|
* @internal
|
|
34
34
|
*/
|
|
35
|
-
const TagTypeKey =
|
|
35
|
+
const TagTypeKey = "sandly/TagTypeKey";
|
|
36
36
|
/**
|
|
37
37
|
* Utility object containing factory functions for creating dependency tags.
|
|
38
38
|
*
|
|
@@ -68,10 +68,10 @@ const Tag = {
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* String used to store the original ValueTag in Inject<T> types.
|
|
72
72
|
* This prevents property name collisions while allowing type-level extraction.
|
|
73
73
|
*/
|
|
74
|
-
const InjectSource =
|
|
74
|
+
const InjectSource = "sandly/InjectSource";
|
|
75
75
|
|
|
76
76
|
//#endregion
|
|
77
77
|
//#region src/errors.ts
|
|
@@ -149,7 +149,7 @@ var ContainerDestroyedError = class extends ContainerError {};
|
|
|
149
149
|
*
|
|
150
150
|
* @example
|
|
151
151
|
* ```typescript
|
|
152
|
-
* const
|
|
152
|
+
* const container = Container.empty(); // Empty container
|
|
153
153
|
*
|
|
154
154
|
* try {
|
|
155
155
|
* await c.resolve(UnregisteredService); // This will throw
|
|
@@ -183,7 +183,7 @@ var UnknownDependencyError = class extends ContainerError {
|
|
|
183
183
|
* class ServiceA extends Tag.Service('ServiceA') {}
|
|
184
184
|
* class ServiceB extends Tag.Service('ServiceB') {}
|
|
185
185
|
*
|
|
186
|
-
* const
|
|
186
|
+
* const container = Container.empty()
|
|
187
187
|
* .register(ServiceA, async (ctx) =>
|
|
188
188
|
* new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
|
|
189
189
|
* )
|
|
@@ -227,7 +227,7 @@ var CircularDependencyError = class extends ContainerError {
|
|
|
227
227
|
* ```typescript
|
|
228
228
|
* class DatabaseService extends Tag.Service('DatabaseService') {}
|
|
229
229
|
*
|
|
230
|
-
* const
|
|
230
|
+
* const container = Container.empty().register(DatabaseService, () => {
|
|
231
231
|
* throw new Error('Database connection failed');
|
|
232
232
|
* });
|
|
233
233
|
*
|
|
@@ -324,7 +324,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
|
|
|
324
324
|
* getUser() { return this.db.query(); }
|
|
325
325
|
* }
|
|
326
326
|
*
|
|
327
|
-
* const
|
|
327
|
+
* const container = Container.empty()
|
|
328
328
|
* .register(DatabaseService, () => new DatabaseService())
|
|
329
329
|
* .register(UserService, async (ctx) =>
|
|
330
330
|
* new UserService(await ctx.resolve(DatabaseService))
|
|
@@ -338,7 +338,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
|
|
|
338
338
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
339
339
|
* const ConfigTag = Tag.of('config')<{ dbUrl: string }>();
|
|
340
340
|
*
|
|
341
|
-
* const
|
|
341
|
+
* const container = Container.empty()
|
|
342
342
|
* .register(ApiKeyTag, () => process.env.API_KEY!)
|
|
343
343
|
* .register(ConfigTag, () => ({ dbUrl: 'postgresql://localhost:5432' }));
|
|
344
344
|
*
|
|
@@ -353,7 +353,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
|
|
|
353
353
|
* async disconnect() { return; }
|
|
354
354
|
* }
|
|
355
355
|
*
|
|
356
|
-
* const
|
|
356
|
+
* const container = Container.empty().register(
|
|
357
357
|
* DatabaseConnection,
|
|
358
358
|
* async () => {
|
|
359
359
|
* const conn = new DatabaseConnection();
|
|
@@ -369,6 +369,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
|
|
|
369
369
|
*/
|
|
370
370
|
var Container = class Container {
|
|
371
371
|
[ContainerTypeId];
|
|
372
|
+
constructor() {}
|
|
372
373
|
/**
|
|
373
374
|
* Cache of instantiated dependencies as promises.
|
|
374
375
|
* Ensures singleton behavior and supports concurrent access.
|
|
@@ -390,6 +391,10 @@ var Container = class Container {
|
|
|
390
391
|
* @internal
|
|
391
392
|
*/
|
|
392
393
|
isDestroyed = false;
|
|
394
|
+
/**
|
|
395
|
+
* Creates a new empty container instance.
|
|
396
|
+
* @returns A new empty Container instance with no registered dependencies.
|
|
397
|
+
*/
|
|
393
398
|
static empty() {
|
|
394
399
|
return new Container();
|
|
395
400
|
}
|
|
@@ -417,7 +422,7 @@ var Container = class Container {
|
|
|
417
422
|
* log(message: string) { console.log(message); }
|
|
418
423
|
* }
|
|
419
424
|
*
|
|
420
|
-
* const
|
|
425
|
+
* const container = Container.empty().register(
|
|
421
426
|
* LoggerService,
|
|
422
427
|
* () => new LoggerService()
|
|
423
428
|
* );
|
|
@@ -429,7 +434,7 @@ var Container = class Container {
|
|
|
429
434
|
* constructor(private db: DatabaseService, private logger: LoggerService) {}
|
|
430
435
|
* }
|
|
431
436
|
*
|
|
432
|
-
* const
|
|
437
|
+
* const container = Container.empty()
|
|
433
438
|
* .register(DatabaseService, () => new DatabaseService())
|
|
434
439
|
* .register(LoggerService, () => new LoggerService())
|
|
435
440
|
* .register(UserService, async (ctx) =>
|
|
@@ -442,7 +447,7 @@ var Container = class Container {
|
|
|
442
447
|
*
|
|
443
448
|
* @example Overriding a dependency
|
|
444
449
|
* ```typescript
|
|
445
|
-
* const
|
|
450
|
+
* const container = Container.empty()
|
|
446
451
|
* .register(DatabaseService, () => new DatabaseService())
|
|
447
452
|
* .register(DatabaseService, () => new MockDatabaseService()); // Overrides the previous registration
|
|
448
453
|
* ```
|
|
@@ -451,7 +456,7 @@ var Container = class Container {
|
|
|
451
456
|
* ```typescript
|
|
452
457
|
* const ConfigTag = Tag.of('config')<{ apiUrl: string }>();
|
|
453
458
|
*
|
|
454
|
-
* const
|
|
459
|
+
* const container = Container.empty().register(
|
|
455
460
|
* ConfigTag,
|
|
456
461
|
* () => ({ apiUrl: 'https://api.example.com' })
|
|
457
462
|
* );
|
|
@@ -464,7 +469,7 @@ var Container = class Container {
|
|
|
464
469
|
* async close() { return; }
|
|
465
470
|
* }
|
|
466
471
|
*
|
|
467
|
-
* const
|
|
472
|
+
* const container = Container.empty().register(
|
|
468
473
|
* DatabaseConnection,
|
|
469
474
|
* async () => {
|
|
470
475
|
* const conn = new DatabaseConnection();
|
|
@@ -498,7 +503,7 @@ var Container = class Container {
|
|
|
498
503
|
*
|
|
499
504
|
* @example
|
|
500
505
|
* ```typescript
|
|
501
|
-
* const
|
|
506
|
+
* const container = Container.empty().register(DatabaseService, () => new DatabaseService());
|
|
502
507
|
* console.log(c.has(DatabaseService)); // true
|
|
503
508
|
* ```
|
|
504
509
|
*/
|
|
@@ -533,7 +538,7 @@ var Container = class Container {
|
|
|
533
538
|
*
|
|
534
539
|
* @example Basic usage
|
|
535
540
|
* ```typescript
|
|
536
|
-
* const
|
|
541
|
+
* const container = Container.empty()
|
|
537
542
|
* .register(DatabaseService, () => new DatabaseService());
|
|
538
543
|
*
|
|
539
544
|
* const db = await c.resolve(DatabaseService);
|
|
@@ -554,7 +559,7 @@ var Container = class Container {
|
|
|
554
559
|
*
|
|
555
560
|
* @example Dependency injection in factories
|
|
556
561
|
* ```typescript
|
|
557
|
-
* const
|
|
562
|
+
* const container = Container.empty()
|
|
558
563
|
* .register(DatabaseService, () => new DatabaseService())
|
|
559
564
|
* .register(UserService, async (ctx) => {
|
|
560
565
|
* const db = await ctx.resolve(DatabaseService);
|
|
@@ -604,7 +609,7 @@ var Container = class Container {
|
|
|
604
609
|
*
|
|
605
610
|
* @example Basic usage
|
|
606
611
|
* ```typescript
|
|
607
|
-
* const
|
|
612
|
+
* const container = Container.empty()
|
|
608
613
|
* .register(DatabaseService, () => new DatabaseService())
|
|
609
614
|
* .register(LoggerService, () => new LoggerService());
|
|
610
615
|
*
|
|
@@ -614,7 +619,7 @@ var Container = class Container {
|
|
|
614
619
|
* @example Mixed tag types
|
|
615
620
|
* ```typescript
|
|
616
621
|
* const ApiKeyTag = Tag.of('apiKey')<string>();
|
|
617
|
-
* const
|
|
622
|
+
* const container = Container.empty()
|
|
618
623
|
* .register(ApiKeyTag, () => 'secret-key')
|
|
619
624
|
* .register(UserService, () => new UserService());
|
|
620
625
|
*
|
|
@@ -704,7 +709,7 @@ var Container = class Container {
|
|
|
704
709
|
*
|
|
705
710
|
* @example Basic cleanup
|
|
706
711
|
* ```typescript
|
|
707
|
-
* const
|
|
712
|
+
* const container = Container.empty()
|
|
708
713
|
* .register(DatabaseConnection,
|
|
709
714
|
* async () => {
|
|
710
715
|
* const conn = new DatabaseConnection();
|
|
@@ -907,6 +912,11 @@ var ScopedContainer = class ScopedContainer extends Container {
|
|
|
907
912
|
this.parent = parent;
|
|
908
913
|
this.scope = scope;
|
|
909
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Creates a new empty scoped container instance.
|
|
917
|
+
* @param scope - The scope identifier for this container
|
|
918
|
+
* @returns A new empty ScopedContainer instance with no registered dependencies
|
|
919
|
+
*/
|
|
910
920
|
static empty(scope) {
|
|
911
921
|
return new ScopedContainer(null, scope);
|
|
912
922
|
}
|
|
@@ -1109,6 +1119,122 @@ function service(tag, spec) {
|
|
|
1109
1119
|
return container.register(tag, spec);
|
|
1110
1120
|
});
|
|
1111
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a service layer with automatic dependency injection by inferring constructor parameters.
|
|
1124
|
+
*
|
|
1125
|
+
* This is a convenience function that automatically resolves constructor dependencies and passes
|
|
1126
|
+
* both DI-managed dependencies and static values to the service constructor in the correct order.
|
|
1127
|
+
* It eliminates the need to manually write factory functions for services with constructor dependencies.
|
|
1128
|
+
*
|
|
1129
|
+
* @template T - The ServiceTag representing the service class
|
|
1130
|
+
* @param tag - The service tag (must be a ServiceTag, not a ValueTag)
|
|
1131
|
+
* @param deps - Tuple of constructor parameters in order - mix of dependency tags and static values
|
|
1132
|
+
* @param finalizer - Optional cleanup function called when the container is destroyed
|
|
1133
|
+
* @returns A service layer that automatically handles dependency injection
|
|
1134
|
+
*
|
|
1135
|
+
* @example Simple service with dependencies
|
|
1136
|
+
* ```typescript
|
|
1137
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1138
|
+
* constructor(private url: string) {
|
|
1139
|
+
* super();
|
|
1140
|
+
* }
|
|
1141
|
+
* connect() { return `Connected to ${this.url}`; }
|
|
1142
|
+
* }
|
|
1143
|
+
*
|
|
1144
|
+
* class UserService extends Tag.Service('UserService') {
|
|
1145
|
+
* constructor(private db: DatabaseService, private timeout: number) {
|
|
1146
|
+
* super();
|
|
1147
|
+
* }
|
|
1148
|
+
* getUsers() { return this.db.query('SELECT * FROM users'); }
|
|
1149
|
+
* }
|
|
1150
|
+
*
|
|
1151
|
+
* // Automatically inject DatabaseService and pass static timeout value
|
|
1152
|
+
* const userService = autoService(UserService, [DatabaseService, 5000]);
|
|
1153
|
+
* ```
|
|
1154
|
+
*
|
|
1155
|
+
* @example Mixed dependencies and static values
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* class NotificationService extends Tag.Service('NotificationService') {
|
|
1158
|
+
* constructor(
|
|
1159
|
+
* private logger: LoggerService,
|
|
1160
|
+
* private apiKey: string,
|
|
1161
|
+
* private retries: number,
|
|
1162
|
+
* private cache: CacheService
|
|
1163
|
+
* ) {
|
|
1164
|
+
* super();
|
|
1165
|
+
* }
|
|
1166
|
+
* }
|
|
1167
|
+
*
|
|
1168
|
+
* // Mix of DI tags and static values in constructor order
|
|
1169
|
+
* const notificationService = autoService(NotificationService, [
|
|
1170
|
+
* LoggerService, // Will be resolved from container
|
|
1171
|
+
* 'secret-api-key', // Static string value
|
|
1172
|
+
* 3, // Static number value
|
|
1173
|
+
* CacheService // Will be resolved from container
|
|
1174
|
+
* ]);
|
|
1175
|
+
* ```
|
|
1176
|
+
*
|
|
1177
|
+
* @example Compared to manual service creation
|
|
1178
|
+
* ```typescript
|
|
1179
|
+
* // Manual approach (more verbose)
|
|
1180
|
+
* const userServiceManual = service(UserService, async (ctx) => {
|
|
1181
|
+
* const db = await ctx.resolve(DatabaseService);
|
|
1182
|
+
* return new UserService(db, 5000);
|
|
1183
|
+
* });
|
|
1184
|
+
*
|
|
1185
|
+
* // Auto approach (concise)
|
|
1186
|
+
* const userServiceAuto = autoService(UserService, [DatabaseService, 5000]);
|
|
1187
|
+
* ```
|
|
1188
|
+
*
|
|
1189
|
+
* @example With finalizer for cleanup
|
|
1190
|
+
* ```typescript
|
|
1191
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1192
|
+
* constructor(private connectionString: string) {
|
|
1193
|
+
* super();
|
|
1194
|
+
* }
|
|
1195
|
+
*
|
|
1196
|
+
* private connection: Connection | null = null;
|
|
1197
|
+
*
|
|
1198
|
+
* async connect() {
|
|
1199
|
+
* this.connection = await createConnection(this.connectionString);
|
|
1200
|
+
* }
|
|
1201
|
+
*
|
|
1202
|
+
* async disconnect() {
|
|
1203
|
+
* if (this.connection) {
|
|
1204
|
+
* await this.connection.close();
|
|
1205
|
+
* this.connection = null;
|
|
1206
|
+
* }
|
|
1207
|
+
* }
|
|
1208
|
+
* }
|
|
1209
|
+
*
|
|
1210
|
+
* // Service with automatic cleanup
|
|
1211
|
+
* const dbService = autoService(
|
|
1212
|
+
* DatabaseService,
|
|
1213
|
+
* {
|
|
1214
|
+
* dependencies: ['postgresql://localhost:5432/mydb'],
|
|
1215
|
+
* finalizer: (service) => service.disconnect() // Finalizer for cleanup
|
|
1216
|
+
* }
|
|
1217
|
+
* );
|
|
1218
|
+
* ```
|
|
1219
|
+
*/
|
|
1220
|
+
function autoService(tag, spec) {
|
|
1221
|
+
if (Array.isArray(spec)) spec = { dependencies: spec };
|
|
1222
|
+
const factory = async (ctx) => {
|
|
1223
|
+
const diDeps = [];
|
|
1224
|
+
for (const dep of spec.dependencies) if (Tag.isTag(dep)) diDeps.push(dep);
|
|
1225
|
+
const resolved = await ctx.resolveAll(...diDeps);
|
|
1226
|
+
const args = [];
|
|
1227
|
+
let resolvedIndex = 0;
|
|
1228
|
+
for (const dep of spec.dependencies) if (Tag.isTag(dep)) args.push(resolved[resolvedIndex++]);
|
|
1229
|
+
else args.push(dep);
|
|
1230
|
+
return new tag(...args);
|
|
1231
|
+
};
|
|
1232
|
+
const finalSpec = spec.finalizer ? {
|
|
1233
|
+
factory,
|
|
1234
|
+
finalizer: spec.finalizer
|
|
1235
|
+
} : factory;
|
|
1236
|
+
return service(tag, finalSpec);
|
|
1237
|
+
}
|
|
1112
1238
|
|
|
1113
1239
|
//#endregion
|
|
1114
1240
|
//#region src/value.ts
|
|
@@ -1135,4 +1261,4 @@ function value(tag, constantValue) {
|
|
|
1135
1261
|
}
|
|
1136
1262
|
|
|
1137
1263
|
//#endregion
|
|
1138
|
-
export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, layer, scoped, service, value };
|
|
1264
|
+
export { CircularDependencyError, Container, ContainerDestroyedError, ContainerError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, ScopedContainer, Tag, UnknownDependencyError, autoService, layer, scoped, service, value };
|