sandly 0.5.3 → 1.0.1
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 +65 -14
- package/dist/index.d.ts +477 -400
- package/dist/index.js +245 -146
- package/package.json +68 -74
package/dist/index.d.ts
CHANGED
|
@@ -197,40 +197,6 @@ declare const Tag: {
|
|
|
197
197
|
* ```
|
|
198
198
|
*/
|
|
199
199
|
of: <Id extends TagId>(id: Id) => <T>() => ValueTag<Id, T>;
|
|
200
|
-
/**
|
|
201
|
-
* Creates an anonymous value tag with a unique symbol identifier.
|
|
202
|
-
*
|
|
203
|
-
* This is useful when you want a tag that's guaranteed to be unique but don't
|
|
204
|
-
* need a human-readable identifier. Each call creates a new unique symbol,
|
|
205
|
-
* making it impossible to accidentally create duplicate tags.
|
|
206
|
-
*
|
|
207
|
-
* @template T - The type that this tag represents
|
|
208
|
-
* @returns A value tag with a unique symbol identifier
|
|
209
|
-
*
|
|
210
|
-
* @example
|
|
211
|
-
* ```typescript
|
|
212
|
-
* interface InternalConfig {
|
|
213
|
-
* secretKey: string;
|
|
214
|
-
* }
|
|
215
|
-
*
|
|
216
|
-
* const InternalConfigTag = Tag.for<InternalConfig>();
|
|
217
|
-
*
|
|
218
|
-
* // This tag is guaranteed to be unique - no chance of conflicts
|
|
219
|
-
* container.register(InternalConfigTag, () => ({
|
|
220
|
-
* secretKey: generateSecret()
|
|
221
|
-
* }));
|
|
222
|
-
* ```
|
|
223
|
-
*
|
|
224
|
-
* @example Multiple anonymous tags
|
|
225
|
-
* ```typescript
|
|
226
|
-
* const ConfigA = Tag.for<string>();
|
|
227
|
-
* const ConfigB = Tag.for<string>();
|
|
228
|
-
*
|
|
229
|
-
* // These are different tags even though they have the same type
|
|
230
|
-
* console.log(ConfigA === ConfigB); // false
|
|
231
|
-
* ```
|
|
232
|
-
*/
|
|
233
|
-
for: <T>() => ValueTag<symbol, T>;
|
|
234
200
|
/**
|
|
235
201
|
* Creates a base class that can be extended to create service classes with dependency tags.
|
|
236
202
|
*
|
|
@@ -301,11 +267,9 @@ declare const Tag: {
|
|
|
301
267
|
* @example
|
|
302
268
|
* ```typescript
|
|
303
269
|
* const StringTag = Tag.of('myString')<string>();
|
|
304
|
-
* const SymbolTag = Tag.for<number>();
|
|
305
270
|
* class ServiceClass extends Tag.Service('MyService') {}
|
|
306
271
|
*
|
|
307
272
|
* console.log(Tag.id(StringTag)); // "myString"
|
|
308
|
-
* console.log(Tag.id(SymbolTag)); // "Symbol()"
|
|
309
273
|
* console.log(Tag.id(ServiceClass)); // "MyService"
|
|
310
274
|
* ```
|
|
311
275
|
*
|
|
@@ -353,12 +317,12 @@ type Inject<T extends ValueTag<TagId, unknown>> = T extends ValueTag<any, infer
|
|
|
353
317
|
*/
|
|
354
318
|
type ExtractInjectTag<T> = T extends {
|
|
355
319
|
readonly [InjectSource]?: infer U;
|
|
356
|
-
} ? U : never;
|
|
357
|
-
//#endregion
|
|
320
|
+
} ? U : never; //#endregion
|
|
358
321
|
//#region src/types.d.ts
|
|
359
322
|
type PromiseOrValue<T> = T | Promise<T>;
|
|
360
323
|
type Contravariant<A> = (_: A) => void;
|
|
361
324
|
type Covariant<A> = (_: never) => A;
|
|
325
|
+
|
|
362
326
|
//#endregion
|
|
363
327
|
//#region src/container.d.ts
|
|
364
328
|
/**
|
|
@@ -372,7 +336,7 @@ type Covariant<A> = (_: never) => A;
|
|
|
372
336
|
* (returning Promise<T>). The container handles both cases transparently.
|
|
373
337
|
*
|
|
374
338
|
* @template T - The type of the service instance being created
|
|
375
|
-
* @template
|
|
339
|
+
* @template TRequires - Union type of all required dependencies
|
|
376
340
|
*
|
|
377
341
|
* @example Synchronous factory
|
|
378
342
|
* ```typescript
|
|
@@ -392,7 +356,7 @@ type Covariant<A> = (_: never) => A;
|
|
|
392
356
|
* };
|
|
393
357
|
* ```
|
|
394
358
|
*/
|
|
395
|
-
type Factory<T,
|
|
359
|
+
type Factory<T, TRequires extends AnyTag> = (ctx: ResolutionContext<TRequires>) => PromiseOrValue<T>;
|
|
396
360
|
/**
|
|
397
361
|
* Type representing a finalizer function used to clean up dependency instances.
|
|
398
362
|
*
|
|
@@ -446,7 +410,7 @@ type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
|
|
|
446
410
|
* lifecycle logic or want to share lifecycle definitions across multiple services.
|
|
447
411
|
*
|
|
448
412
|
* @template T - The instance type
|
|
449
|
-
* @template
|
|
413
|
+
* @template TRequires - Union type of all required dependencies
|
|
450
414
|
*
|
|
451
415
|
* @example Using DependencyLifecycle as an object
|
|
452
416
|
* ```typescript
|
|
@@ -524,8 +488,8 @@ type Finalizer<T> = (instance: T) => PromiseOrValue<void>;
|
|
|
524
488
|
* );
|
|
525
489
|
* ```
|
|
526
490
|
*/
|
|
527
|
-
interface DependencyLifecycle<T,
|
|
528
|
-
create: Factory<T,
|
|
491
|
+
interface DependencyLifecycle<T, TRequires extends AnyTag> {
|
|
492
|
+
create: Factory<T, TRequires>;
|
|
529
493
|
cleanup?: Finalizer<T>;
|
|
530
494
|
}
|
|
531
495
|
/**
|
|
@@ -536,7 +500,7 @@ interface DependencyLifecycle<T, TReg extends AnyTag> {
|
|
|
536
500
|
* - A complete lifecycle object with both factory and finalizer
|
|
537
501
|
*
|
|
538
502
|
* @template T - The dependency tag type
|
|
539
|
-
* @template
|
|
503
|
+
* @template TRequires - Union type of all required dependencies
|
|
540
504
|
*
|
|
541
505
|
* @example Simple factory registration
|
|
542
506
|
* ```typescript
|
|
@@ -556,33 +520,37 @@ interface DependencyLifecycle<T, TReg extends AnyTag> {
|
|
|
556
520
|
* Container.empty().register(DatabaseConnection, spec);
|
|
557
521
|
* ```
|
|
558
522
|
*/
|
|
559
|
-
type DependencySpec<T extends AnyTag,
|
|
523
|
+
type DependencySpec<T extends AnyTag, TRequires extends AnyTag> = Factory<TagType<T>, TRequires> | DependencyLifecycle<TagType<T>, TRequires>;
|
|
560
524
|
/**
|
|
561
525
|
* Type representing the context available to factory functions during dependency resolution.
|
|
562
526
|
*
|
|
563
527
|
* This type contains only the `resolve` and `resolveAll` methods from the container, which are used to retrieve
|
|
564
528
|
* other dependencies during the creation of a service.
|
|
565
529
|
*
|
|
566
|
-
* @template
|
|
530
|
+
* @template TTags - Union type of all dependencies available in the container
|
|
567
531
|
*/
|
|
568
|
-
type ResolutionContext<
|
|
532
|
+
type ResolutionContext<TTags extends AnyTag> = Pick<IContainer<TTags>, 'resolve' | 'resolveAll'>;
|
|
569
533
|
declare const ContainerTypeId: unique symbol;
|
|
570
534
|
/**
|
|
571
535
|
* Interface representing a container that can register and retrieve dependencies.
|
|
572
536
|
*
|
|
573
|
-
* @template
|
|
537
|
+
* @template TTags - Union type of all dependencies available in the container
|
|
574
538
|
*/
|
|
575
|
-
interface IContainer<
|
|
539
|
+
interface IContainer<TTags extends AnyTag = never> {
|
|
576
540
|
readonly [ContainerTypeId]: {
|
|
577
|
-
readonly
|
|
541
|
+
readonly _TTags: Contravariant<TTags>;
|
|
578
542
|
};
|
|
579
|
-
register: <T extends AnyTag>(tag: T, spec: DependencySpec<T,
|
|
543
|
+
register: <T extends AnyTag>(tag: T, spec: DependencySpec<T, TTags>) => IContainer<TTags | T>;
|
|
580
544
|
has(tag: AnyTag): boolean;
|
|
581
545
|
exists(tag: AnyTag): boolean;
|
|
582
|
-
resolve: <T extends
|
|
583
|
-
resolveAll: <const T extends readonly
|
|
546
|
+
resolve: <T extends TTags>(tag: T) => Promise<TagType<T>>;
|
|
547
|
+
resolveAll: <const T extends readonly TTags[]>(...tags: T) => Promise<{ [K in keyof T]: TagType<T[K]> }>;
|
|
584
548
|
destroy(): Promise<void>;
|
|
585
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Extracts the registered tags (TTags) from a container type.
|
|
552
|
+
*/
|
|
553
|
+
type ContainerTags<C> = C extends IContainer<infer TTags> ? TTags : never;
|
|
586
554
|
/**
|
|
587
555
|
* A type-safe dependency injection container that manages service instantiation,
|
|
588
556
|
* caching, and lifecycle management with support for async dependencies and
|
|
@@ -592,7 +560,7 @@ interface IContainer<TReg extends AnyTag = never> {
|
|
|
592
560
|
* at the type level, ensuring that only registered dependencies can be retrieved
|
|
593
561
|
* and preventing runtime errors.
|
|
594
562
|
*
|
|
595
|
-
* @template
|
|
563
|
+
* @template TTags - Union type of all registered dependency tags in this container
|
|
596
564
|
*
|
|
597
565
|
* @example Basic usage with service tags
|
|
598
566
|
* ```typescript
|
|
@@ -650,9 +618,9 @@ interface IContainer<TReg extends AnyTag = never> {
|
|
|
650
618
|
* await c.destroy(); // Calls all finalizers
|
|
651
619
|
* ```
|
|
652
620
|
*/
|
|
653
|
-
declare class Container<
|
|
621
|
+
declare class Container<TTags extends AnyTag> implements IContainer<TTags> {
|
|
654
622
|
readonly [ContainerTypeId]: {
|
|
655
|
-
readonly
|
|
623
|
+
readonly _TTags: Contravariant<TTags>;
|
|
656
624
|
};
|
|
657
625
|
protected constructor();
|
|
658
626
|
/**
|
|
@@ -665,7 +633,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
665
633
|
* Factory functions for creating dependency instances.
|
|
666
634
|
* @internal
|
|
667
635
|
*/
|
|
668
|
-
protected readonly factories: Map<AnyTag, Factory<unknown,
|
|
636
|
+
protected readonly factories: Map<AnyTag, Factory<unknown, TTags>>;
|
|
669
637
|
/**
|
|
670
638
|
* Finalizer functions for cleaning up dependencies when the container is destroyed.
|
|
671
639
|
* @internal
|
|
@@ -763,7 +731,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
763
731
|
* );
|
|
764
732
|
* ```
|
|
765
733
|
*/
|
|
766
|
-
register<T extends AnyTag>(tag: T, spec: DependencySpec<T,
|
|
734
|
+
register<T extends AnyTag>(tag: T, spec: DependencySpec<T, TTags>): Container<TTags | T>;
|
|
767
735
|
/**
|
|
768
736
|
* Checks if a dependency has been registered in the container.
|
|
769
737
|
*
|
|
@@ -837,13 +805,13 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
837
805
|
* const userService = await c.resolve(UserService);
|
|
838
806
|
* ```
|
|
839
807
|
*/
|
|
840
|
-
resolve<T extends
|
|
808
|
+
resolve<T extends TTags>(tag: T): Promise<TagType<T>>;
|
|
841
809
|
/**
|
|
842
810
|
* Internal resolution method that tracks the dependency chain for circular dependency detection.
|
|
843
811
|
* Can be overridden by subclasses (e.g., ScopedContainer) to implement custom resolution logic.
|
|
844
812
|
* @internal
|
|
845
813
|
*/
|
|
846
|
-
protected resolveInternal<T extends
|
|
814
|
+
protected resolveInternal<T extends TTags>(tag: T, chain: AnyTag[]): Promise<TagType<T>>;
|
|
847
815
|
/**
|
|
848
816
|
* Resolves multiple dependencies concurrently using Promise.all.
|
|
849
817
|
*
|
|
@@ -884,7 +852,7 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
884
852
|
* const results = await c.resolveAll(); // Returns empty array
|
|
885
853
|
* ```
|
|
886
854
|
*/
|
|
887
|
-
resolveAll<const T extends readonly
|
|
855
|
+
resolveAll<const T extends readonly TTags[]>(...tags: T): Promise<{ [K in keyof T]: TagType<T[K]> }>;
|
|
888
856
|
/**
|
|
889
857
|
* Destroys all instantiated dependencies by calling their finalizers and makes the container unusable.
|
|
890
858
|
*
|
|
@@ -958,297 +926,142 @@ declare class Container<TReg extends AnyTag> implements IContainer<TReg> {
|
|
|
958
926
|
*/
|
|
959
927
|
destroy(): Promise<void>;
|
|
960
928
|
}
|
|
929
|
+
|
|
961
930
|
//#endregion
|
|
962
|
-
//#region src/
|
|
963
|
-
type
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
931
|
+
//#region src/scoped-container.d.ts
|
|
932
|
+
type Scope = string | symbol;
|
|
933
|
+
declare class ScopedContainer<TTags extends AnyTag> extends Container<TTags> {
|
|
934
|
+
readonly scope: Scope;
|
|
935
|
+
private parent;
|
|
936
|
+
private readonly children;
|
|
937
|
+
protected constructor(parent: IContainer<TTags> | null, scope: Scope);
|
|
938
|
+
/**
|
|
939
|
+
* Creates a new empty scoped container instance.
|
|
940
|
+
* @param scope - The scope identifier for this container
|
|
941
|
+
* @returns A new empty ScopedContainer instance with no registered dependencies
|
|
942
|
+
*/
|
|
943
|
+
static empty(scope: Scope): ScopedContainer<never>;
|
|
944
|
+
/**
|
|
945
|
+
* Registers a dependency in the scoped container.
|
|
946
|
+
*
|
|
947
|
+
* Overrides the base implementation to return ScopedContainer type
|
|
948
|
+
* for proper method chaining support.
|
|
949
|
+
*/
|
|
950
|
+
register<T extends AnyTag>(tag: T, spec: DependencySpec<T, TTags>): ScopedContainer<TTags | T>;
|
|
951
|
+
/**
|
|
952
|
+
* Checks if a dependency has been registered in this scope or any parent scope.
|
|
953
|
+
*
|
|
954
|
+
* This method checks the current scope first, then walks up the parent chain.
|
|
955
|
+
* Returns true if the dependency has been registered somewhere in the scope hierarchy.
|
|
956
|
+
*/
|
|
957
|
+
has(tag: AnyTag): boolean;
|
|
958
|
+
/**
|
|
959
|
+
* Checks if a dependency has been instantiated in this scope or any parent scope.
|
|
960
|
+
*
|
|
961
|
+
* This method checks the current scope first, then walks up the parent chain.
|
|
962
|
+
* Returns true if the dependency has been instantiated somewhere in the scope hierarchy.
|
|
963
|
+
*/
|
|
964
|
+
exists(tag: AnyTag): boolean;
|
|
965
|
+
/**
|
|
966
|
+
* Retrieves a dependency instance, resolving from the current scope or parent scopes.
|
|
967
|
+
*
|
|
968
|
+
* Resolution strategy:
|
|
969
|
+
* 1. Check cache in current scope
|
|
970
|
+
* 2. Check if factory exists in current scope - if so, create instance here
|
|
971
|
+
* 3. Otherwise, delegate to parent scope
|
|
972
|
+
* 4. If no parent or parent doesn't have it, throw UnknownDependencyError
|
|
973
|
+
*/
|
|
974
|
+
resolve<T extends TTags>(tag: T): Promise<TagType<T>>;
|
|
975
|
+
/**
|
|
976
|
+
* Internal resolution with delegation logic for scoped containers.
|
|
977
|
+
* @internal
|
|
978
|
+
*/
|
|
979
|
+
protected resolveInternal<T extends TTags>(tag: T, chain: AnyTag[]): Promise<TagType<T>>;
|
|
980
|
+
/**
|
|
981
|
+
* Destroys this scoped container and its children, preserving the container structure for reuse.
|
|
982
|
+
*
|
|
983
|
+
* This method ensures proper cleanup order while maintaining reusability:
|
|
984
|
+
* 1. Destroys all child scopes first (they may depend on parent scope dependencies)
|
|
985
|
+
* 2. Then calls finalizers for dependencies created in this scope
|
|
986
|
+
* 3. Clears only instance caches - preserves factories, finalizers, and child structure
|
|
987
|
+
*
|
|
988
|
+
* Child destruction happens first to ensure dependencies don't get cleaned up
|
|
989
|
+
* before their dependents.
|
|
990
|
+
*/
|
|
991
|
+
destroy(): Promise<void>;
|
|
992
|
+
/**
|
|
993
|
+
* Creates a child scoped container.
|
|
994
|
+
*
|
|
995
|
+
* Child containers inherit access to parent dependencies but maintain
|
|
996
|
+
* their own scope for new registrations and instance caching.
|
|
997
|
+
*/
|
|
998
|
+
child(scope: Scope): ScopedContainer<TTags>;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/layer.d.ts
|
|
978
1003
|
/**
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
* This extends the native Error class to provide consistent error handling
|
|
982
|
-
* and structured error information across the library.
|
|
1004
|
+
* Replaces the TTags type parameter in a container type with a new type.
|
|
1005
|
+
* Preserves the concrete container type (Container, ScopedContainer, or IContainer).
|
|
983
1006
|
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* if (error instanceof SandlyError) {
|
|
990
|
-
* console.error('DI Error:', error.message);
|
|
991
|
-
* console.error('Details:', error.detail);
|
|
992
|
-
* }
|
|
993
|
-
* }
|
|
994
|
-
* ```
|
|
1007
|
+
* Uses contravariance to detect container types:
|
|
1008
|
+
* - Any ScopedContainer<X> extends ScopedContainer<never>
|
|
1009
|
+
* - Any Container<X> extends Container<never> (but not ScopedContainer<never>)
|
|
1010
|
+
* - Falls back to IContainer for anything else
|
|
1011
|
+
* @internal
|
|
995
1012
|
*/
|
|
996
|
-
|
|
997
|
-
detail: Record<string, unknown> | undefined;
|
|
998
|
-
constructor(message: string, {
|
|
999
|
-
cause,
|
|
1000
|
-
detail
|
|
1001
|
-
}?: ErrorProps);
|
|
1002
|
-
static ensure(error: unknown): SandlyError;
|
|
1003
|
-
dump(): ErrorDump;
|
|
1004
|
-
dumps(): string;
|
|
1005
|
-
}
|
|
1013
|
+
type WithContainerTags<TContainer, TNewTags extends AnyTag> = TContainer extends ScopedContainer<never> ? ScopedContainer<TNewTags> : TContainer extends Container<never> ? Container<TNewTags> : IContainer<TNewTags>;
|
|
1006
1014
|
/**
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* This error occurs when calling `container.register()` for a tag that has already been instantiated.
|
|
1010
|
-
* Registration must happen before any instantiation occurs, as cached instances would still be used
|
|
1011
|
-
* by existing dependencies.
|
|
1015
|
+
* The most generic layer type that accepts any concrete layer.
|
|
1012
1016
|
*/
|
|
1013
|
-
|
|
1017
|
+
type AnyLayer = Layer<any, any>;
|
|
1014
1018
|
/**
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
* This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
|
|
1018
|
-
* on a container that has already been destroyed. It indicates a programming error where the container
|
|
1019
|
-
* is being used after it has been destroyed.
|
|
1019
|
+
* The type ID for the Layer interface.
|
|
1020
1020
|
*/
|
|
1021
|
-
declare
|
|
1021
|
+
declare const LayerTypeId: unique symbol;
|
|
1022
1022
|
/**
|
|
1023
|
-
*
|
|
1023
|
+
* A dependency layer represents a reusable, composable unit of dependency registrations.
|
|
1024
|
+
* Layers allow you to organize your dependency injection setup into logical groups
|
|
1025
|
+
* that can be combined and reused across different contexts.
|
|
1024
1026
|
*
|
|
1025
|
-
*
|
|
1026
|
-
* registered via `container.register()`. It indicates a programming error where
|
|
1027
|
-
* the dependency setup is incomplete.
|
|
1027
|
+
* ## Type Variance
|
|
1028
1028
|
*
|
|
1029
|
-
*
|
|
1030
|
-
* ```typescript
|
|
1031
|
-
* const container = Container.empty(); // Empty container
|
|
1029
|
+
* The Layer interface uses TypeScript's variance annotations to enable safe substitutability:
|
|
1032
1030
|
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1037
|
-
* console.error('Missing dependency:', error.message);
|
|
1038
|
-
* }
|
|
1039
|
-
* }
|
|
1040
|
-
* ```
|
|
1041
|
-
*/
|
|
1042
|
-
declare class UnknownDependencyError extends SandlyError {
|
|
1043
|
-
/**
|
|
1044
|
-
* @internal
|
|
1045
|
-
* Creates an UnknownDependencyError for the given tag.
|
|
1046
|
-
*
|
|
1047
|
-
* @param tag - The dependency tag that wasn't found
|
|
1048
|
-
*/
|
|
1049
|
-
constructor(tag: AnyTag);
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Error thrown when a circular dependency is detected during dependency resolution.
|
|
1031
|
+
* ### TRequires (covariant)
|
|
1032
|
+
* A layer requiring fewer dependencies can substitute one requiring more:
|
|
1033
|
+
* - `Layer<never, X>` can be used where `Layer<A | B, X>` is expected
|
|
1034
|
+
* - Intuition: A service that needs nothing is more flexible than one that needs specific deps
|
|
1053
1035
|
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1036
|
+
* ### TProvides (contravariant)
|
|
1037
|
+
* A layer providing more services can substitute one providing fewer:
|
|
1038
|
+
* - `Layer<X, A | B>` can be used where `Layer<X, A>` is expected
|
|
1039
|
+
* - Intuition: A service that gives you extra things is compatible with expecting fewer things
|
|
1057
1040
|
*
|
|
1058
|
-
* @
|
|
1059
|
-
*
|
|
1060
|
-
* class ServiceA extends Tag.Service('ServiceA') {}
|
|
1061
|
-
* class ServiceB extends Tag.Service('ServiceB') {}
|
|
1041
|
+
* @template TRequires - The union of tags this layer requires to be satisfied by other layers
|
|
1042
|
+
* @template TProvides - The union of tags this layer provides/registers
|
|
1062
1043
|
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* )
|
|
1067
|
-
* .register(ServiceB, async (ctx) =>
|
|
1068
|
-
* new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
|
|
1069
|
-
* );
|
|
1044
|
+
* @example Basic layer usage
|
|
1045
|
+
* ```typescript
|
|
1046
|
+
* import { layer, Tag, container } from 'sandly';
|
|
1070
1047
|
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
* } catch (error) {
|
|
1074
|
-
* if (error instanceof CircularDependencyError) {
|
|
1075
|
-
* console.error('Circular dependency:', error.message);
|
|
1076
|
-
* // Output: "Circular dependency detected for ServiceA: ServiceA -> ServiceB -> ServiceA"
|
|
1077
|
-
* }
|
|
1048
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1049
|
+
* query() { return 'data'; }
|
|
1078
1050
|
* }
|
|
1079
|
-
* ```
|
|
1080
|
-
*/
|
|
1081
|
-
declare class CircularDependencyError extends SandlyError {
|
|
1082
|
-
/**
|
|
1083
|
-
* @internal
|
|
1084
|
-
* Creates a CircularDependencyError with the dependency chain information.
|
|
1085
|
-
*
|
|
1086
|
-
* @param tag - The tag where the circular dependency was detected
|
|
1087
|
-
* @param dependencyChain - The chain of dependencies that led to the circular reference
|
|
1088
|
-
*/
|
|
1089
|
-
constructor(tag: AnyTag, dependencyChain: AnyTag[]);
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Error thrown when a dependency factory function throws an error during instantiation.
|
|
1093
|
-
*
|
|
1094
|
-
* This wraps the original error with additional context about which dependency
|
|
1095
|
-
* failed to be created. The original error is preserved as the `cause` property.
|
|
1096
1051
|
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
* ```typescript
|
|
1102
|
-
* class DatabaseService extends Tag.Service('DatabaseService') {}
|
|
1052
|
+
* // Create a layer that provides DatabaseService
|
|
1053
|
+
* const databaseLayer = layer<never, typeof DatabaseService>((container) =>
|
|
1054
|
+
* container.register(DatabaseService, () => new DatabaseService())
|
|
1055
|
+
* );
|
|
1103
1056
|
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1057
|
+
* // Apply the layer to a container
|
|
1058
|
+
* const container = Container.empty();
|
|
1059
|
+
* const finalContainer = databaseLayer.register(c);
|
|
1107
1060
|
*
|
|
1108
|
-
*
|
|
1109
|
-
* await c.resolve(DatabaseService);
|
|
1110
|
-
* } catch (error) {
|
|
1111
|
-
* if (error instanceof DependencyCreationError) {
|
|
1112
|
-
* console.error('Failed to create:', error.message);
|
|
1113
|
-
* console.error('Original error:', error.cause);
|
|
1114
|
-
* }
|
|
1115
|
-
* }
|
|
1061
|
+
* const db = await finalContainer.resolve(DatabaseService);
|
|
1116
1062
|
* ```
|
|
1117
1063
|
*
|
|
1118
|
-
* @example
|
|
1119
|
-
* ```typescript
|
|
1120
|
-
* // ServiceA -> ServiceB -> ServiceC (ServiceC throws)
|
|
1121
|
-
* try {
|
|
1122
|
-
* await container.resolve(ServiceA);
|
|
1123
|
-
* } catch (error) {
|
|
1124
|
-
* if (error instanceof DependencyCreationError) {
|
|
1125
|
-
* console.error('Top-level error:', error.message); // "Error creating instance of ServiceA"
|
|
1126
|
-
* const rootCause = error.getRootCause();
|
|
1127
|
-
* console.error('Root cause:', rootCause); // Original error from ServiceC
|
|
1128
|
-
* }
|
|
1129
|
-
* }
|
|
1130
|
-
* ```
|
|
1131
|
-
*/
|
|
1132
|
-
declare class DependencyCreationError extends SandlyError {
|
|
1133
|
-
/**
|
|
1134
|
-
* @internal
|
|
1135
|
-
* Creates a DependencyCreationError wrapping the original factory error.
|
|
1136
|
-
*
|
|
1137
|
-
* @param tag - The tag of the dependency that failed to be created
|
|
1138
|
-
* @param error - The original error thrown by the factory function
|
|
1139
|
-
*/
|
|
1140
|
-
constructor(tag: AnyTag, error: unknown);
|
|
1141
|
-
/**
|
|
1142
|
-
* Traverses the error chain to find the root cause error.
|
|
1143
|
-
*
|
|
1144
|
-
* When dependencies are nested, each level wraps the error in a DependencyCreationError.
|
|
1145
|
-
* This method unwraps all the layers to get to the original error that started the failure.
|
|
1146
|
-
*
|
|
1147
|
-
* @returns The root cause error (not a DependencyCreationError unless that's the only error)
|
|
1148
|
-
*
|
|
1149
|
-
* @example
|
|
1150
|
-
* ```typescript
|
|
1151
|
-
* try {
|
|
1152
|
-
* await container.resolve(UserService);
|
|
1153
|
-
* } catch (error) {
|
|
1154
|
-
* if (error instanceof DependencyCreationError) {
|
|
1155
|
-
* const rootCause = error.getRootCause();
|
|
1156
|
-
* console.error('Root cause:', rootCause);
|
|
1157
|
-
* }
|
|
1158
|
-
* }
|
|
1159
|
-
* ```
|
|
1160
|
-
*/
|
|
1161
|
-
getRootCause(): unknown;
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Error thrown when one or more finalizers fail during container destruction.
|
|
1165
|
-
*
|
|
1166
|
-
* This error aggregates multiple finalizer failures that occurred during
|
|
1167
|
-
* `container.destroy()`. Even if some finalizers fail, the container cleanup
|
|
1168
|
-
* process continues and this error contains details of all failures.
|
|
1169
|
-
*
|
|
1170
|
-
* @example Handling finalization errors
|
|
1171
|
-
* ```typescript
|
|
1172
|
-
* try {
|
|
1173
|
-
* await container.destroy();
|
|
1174
|
-
* } catch (error) {
|
|
1175
|
-
* if (error instanceof DependencyFinalizationError) {
|
|
1176
|
-
* console.error('Some finalizers failed');
|
|
1177
|
-
* console.error('Error details:', error.detail.errors);
|
|
1178
|
-
* }
|
|
1179
|
-
* }
|
|
1180
|
-
* ```
|
|
1181
|
-
*/
|
|
1182
|
-
declare class DependencyFinalizationError extends SandlyError {
|
|
1183
|
-
private readonly errors;
|
|
1184
|
-
/**
|
|
1185
|
-
* @internal
|
|
1186
|
-
* Creates a DependencyFinalizationError aggregating multiple finalizer failures.
|
|
1187
|
-
*
|
|
1188
|
-
* @param errors - Array of errors thrown by individual finalizers
|
|
1189
|
-
*/
|
|
1190
|
-
constructor(errors: unknown[]);
|
|
1191
|
-
/**
|
|
1192
|
-
* Returns the root causes of the errors that occurred during finalization.
|
|
1193
|
-
*
|
|
1194
|
-
* @returns An array of the errors that occurred during finalization.
|
|
1195
|
-
* You can expect at least one error in the array.
|
|
1196
|
-
*/
|
|
1197
|
-
getRootCauses(): unknown[];
|
|
1198
|
-
}
|
|
1199
|
-
//#endregion
|
|
1200
|
-
//#region src/layer.d.ts
|
|
1201
|
-
/**
|
|
1202
|
-
* The most generic layer type that accepts any concrete layer.
|
|
1203
|
-
*/
|
|
1204
|
-
type AnyLayer = Layer<any, any>;
|
|
1205
|
-
/**
|
|
1206
|
-
* The type ID for the Layer interface.
|
|
1207
|
-
*/
|
|
1208
|
-
declare const LayerTypeId: unique symbol;
|
|
1209
|
-
/**
|
|
1210
|
-
* A dependency layer represents a reusable, composable unit of dependency registrations.
|
|
1211
|
-
* Layers allow you to organize your dependency injection setup into logical groups
|
|
1212
|
-
* that can be combined and reused across different contexts.
|
|
1213
|
-
*
|
|
1214
|
-
* ## Type Variance
|
|
1215
|
-
*
|
|
1216
|
-
* The Layer interface uses TypeScript's variance annotations to enable safe substitutability:
|
|
1217
|
-
*
|
|
1218
|
-
* ### TRequires (covariant)
|
|
1219
|
-
* A layer requiring fewer dependencies can substitute one requiring more:
|
|
1220
|
-
* - `Layer<never, X>` can be used where `Layer<A | B, X>` is expected
|
|
1221
|
-
* - Intuition: A service that needs nothing is more flexible than one that needs specific deps
|
|
1222
|
-
*
|
|
1223
|
-
* ### TProvides (contravariant)
|
|
1224
|
-
* A layer providing more services can substitute one providing fewer:
|
|
1225
|
-
* - `Layer<X, A | B>` can be used where `Layer<X, A>` is expected
|
|
1226
|
-
* - Intuition: A service that gives you extra things is compatible with expecting fewer things
|
|
1227
|
-
*
|
|
1228
|
-
* @template TRequires - The union of tags this layer requires to be satisfied by other layers
|
|
1229
|
-
* @template TProvides - The union of tags this layer provides/registers
|
|
1230
|
-
*
|
|
1231
|
-
* @example Basic layer usage
|
|
1232
|
-
* ```typescript
|
|
1233
|
-
* import { layer, Tag, container } from 'sandly';
|
|
1234
|
-
*
|
|
1235
|
-
* class DatabaseService extends Tag.Service('DatabaseService') {
|
|
1236
|
-
* query() { return 'data'; }
|
|
1237
|
-
* }
|
|
1238
|
-
*
|
|
1239
|
-
* // Create a layer that provides DatabaseService
|
|
1240
|
-
* const databaseLayer = layer<never, typeof DatabaseService>((container) =>
|
|
1241
|
-
* container.register(DatabaseService, () => new DatabaseService())
|
|
1242
|
-
* );
|
|
1243
|
-
*
|
|
1244
|
-
* // Apply the layer to a container
|
|
1245
|
-
* const container = Container.empty();
|
|
1246
|
-
* const finalContainer = databaseLayer.register(c);
|
|
1247
|
-
*
|
|
1248
|
-
* const db = await finalContainer.resolve(DatabaseService);
|
|
1249
|
-
* ```
|
|
1250
|
-
*
|
|
1251
|
-
* @example Layer composition with variance
|
|
1064
|
+
* @example Layer composition with variance
|
|
1252
1065
|
* ```typescript
|
|
1253
1066
|
* // Layer that requires DatabaseService and provides UserService
|
|
1254
1067
|
* const userLayer = layer<typeof DatabaseService, typeof UserService>((container) =>
|
|
@@ -1296,7 +1109,7 @@ interface Layer<TRequires extends AnyTag, TProvides extends AnyTag> {
|
|
|
1296
1109
|
* // Enhanced container has both ExistingService and myLayer's provisions
|
|
1297
1110
|
* ```
|
|
1298
1111
|
*/
|
|
1299
|
-
register: <TContainer extends
|
|
1112
|
+
register: <TContainer extends IContainer<TRequires>>(container: TContainer) => WithContainerTags<TContainer, ContainerTags<TContainer> | TProvides>;
|
|
1300
1113
|
/**
|
|
1301
1114
|
* Provides a dependency layer to this layer, creating a pipeline where the dependency layer's
|
|
1302
1115
|
* provisions satisfy this layer's requirements. This creates a dependency flow from dependency → this.
|
|
@@ -1576,76 +1389,360 @@ declare const Layer: {
|
|
|
1576
1389
|
*/
|
|
1577
1390
|
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>;
|
|
1578
1391
|
};
|
|
1392
|
+
|
|
1579
1393
|
//#endregion
|
|
1580
|
-
//#region src/
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1394
|
+
//#region src/constant.d.ts
|
|
1395
|
+
/**
|
|
1396
|
+
* Creates a layer that provides a constant value for a given tag.
|
|
1397
|
+
*
|
|
1398
|
+
* @param tag - The value tag to provide
|
|
1399
|
+
* @param constantValue - The constant value to provide
|
|
1400
|
+
* @returns A layer with no dependencies that provides the constant value
|
|
1401
|
+
*
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```typescript
|
|
1404
|
+
* const ApiKey = Tag.of('ApiKey')<string>();
|
|
1405
|
+
* const DatabaseUrl = Tag.of('DatabaseUrl')<string>();
|
|
1406
|
+
*
|
|
1407
|
+
* const apiKey = constant(ApiKey, 'my-secret-key');
|
|
1408
|
+
* const dbUrl = constant(DatabaseUrl, 'postgresql://localhost:5432/myapp');
|
|
1409
|
+
*
|
|
1410
|
+
* const config = Layer.merge(apiKey, dbUrl);
|
|
1411
|
+
* ```
|
|
1412
|
+
*/
|
|
1413
|
+
declare function constant<T extends ValueTag<TagId, unknown>>(tag: T, constantValue: TagType<T>): Layer<never, T>;
|
|
1414
|
+
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/dependency.d.ts
|
|
1417
|
+
/**
|
|
1418
|
+
* Extracts a union type from a tuple of tags.
|
|
1419
|
+
* Returns `never` for empty arrays.
|
|
1420
|
+
* @internal
|
|
1421
|
+
*/
|
|
1422
|
+
type TagsToUnion<T extends readonly AnyTag[]> = T[number];
|
|
1423
|
+
/**
|
|
1424
|
+
* Creates a layer that provides a single dependency with inferred requirements.
|
|
1425
|
+
*
|
|
1426
|
+
* This is a simplified alternative to `layer()` for the common case of defining
|
|
1427
|
+
* a single dependency. Unlike `service()` and `autoService()`, this works with
|
|
1428
|
+
* any tag type (ServiceTag or ValueTag) and doesn't require extending `Tag.Service()`.
|
|
1429
|
+
*
|
|
1430
|
+
* Requirements are passed as an optional array of tags, allowing TypeScript to infer
|
|
1431
|
+
* both the tag type and the requirements automatically - no explicit type
|
|
1432
|
+
* parameters needed.
|
|
1433
|
+
*
|
|
1434
|
+
* @param tag - The tag (ServiceTag or ValueTag) that identifies this dependency
|
|
1435
|
+
* @param spec - Factory function or lifecycle object for creating the dependency
|
|
1436
|
+
* @param requirements - Optional array of dependency tags this dependency requires (defaults to [])
|
|
1437
|
+
* @returns A layer that requires the specified dependencies and provides the tag
|
|
1438
|
+
*
|
|
1439
|
+
* @example Simple dependency without requirements
|
|
1440
|
+
* ```typescript
|
|
1441
|
+
* const Config = Tag.of('Config')<{ apiUrl: string }>();
|
|
1442
|
+
*
|
|
1443
|
+
* // No requirements - can omit the array
|
|
1444
|
+
* const configDep = dependency(Config, () => ({
|
|
1445
|
+
* apiUrl: process.env.API_URL!
|
|
1446
|
+
* }));
|
|
1447
|
+
* ```
|
|
1448
|
+
*
|
|
1449
|
+
* @example Dependency with requirements
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* const database = dependency(
|
|
1452
|
+
* Database,
|
|
1453
|
+
* async (ctx) => {
|
|
1454
|
+
* const config = await ctx.resolve(Config);
|
|
1455
|
+
* const logger = await ctx.resolve(Logger);
|
|
1456
|
+
* logger.info('Creating database connection');
|
|
1457
|
+
* return createDb(config.DATABASE);
|
|
1458
|
+
* },
|
|
1459
|
+
* [Config, Logger]
|
|
1460
|
+
* );
|
|
1461
|
+
* ```
|
|
1462
|
+
*
|
|
1463
|
+
* @example Dependency with lifecycle (create + cleanup)
|
|
1464
|
+
* ```typescript
|
|
1465
|
+
* const database = dependency(
|
|
1466
|
+
* Database,
|
|
1467
|
+
* {
|
|
1468
|
+
* create: async (ctx) => {
|
|
1469
|
+
* const config = await ctx.resolve(Config);
|
|
1470
|
+
* const logger = await ctx.resolve(Logger);
|
|
1471
|
+
* logger.info('Creating database connection');
|
|
1472
|
+
* return await createDb(config.DATABASE);
|
|
1473
|
+
* },
|
|
1474
|
+
* cleanup: async (db) => {
|
|
1475
|
+
* await disconnectDb(db);
|
|
1476
|
+
* },
|
|
1477
|
+
* },
|
|
1478
|
+
* [Config, Logger]
|
|
1479
|
+
* );
|
|
1480
|
+
* ```
|
|
1481
|
+
*
|
|
1482
|
+
* @example Comparison with layer()
|
|
1483
|
+
* ```typescript
|
|
1484
|
+
* // Using layer() - verbose, requires explicit type parameters
|
|
1485
|
+
* const database = layer<typeof Config | typeof Logger, typeof Database>(
|
|
1486
|
+
* (container) =>
|
|
1487
|
+
* container.register(Database, async (ctx) => {
|
|
1488
|
+
* const config = await ctx.resolve(Config);
|
|
1489
|
+
* return createDb(config.DATABASE);
|
|
1490
|
+
* })
|
|
1491
|
+
* );
|
|
1492
|
+
*
|
|
1493
|
+
* // Using dependency() - cleaner, fully inferred types
|
|
1494
|
+
* const database = dependency(
|
|
1495
|
+
* Database,
|
|
1496
|
+
* async (ctx) => {
|
|
1497
|
+
* const config = await ctx.resolve(Config);
|
|
1498
|
+
* return createDb(config.DATABASE);
|
|
1499
|
+
* },
|
|
1500
|
+
* [Config, Logger]
|
|
1501
|
+
* );
|
|
1502
|
+
* ```
|
|
1503
|
+
*/
|
|
1504
|
+
declare function dependency<TTag extends AnyTag, TRequirements extends readonly AnyTag[] = []>(tag: TTag, spec: DependencySpec<TTag, TagsToUnion<TRequirements>>, requirements?: TRequirements): Layer<TagsToUnion<TRequirements>, TTag>;
|
|
1505
|
+
|
|
1506
|
+
//#endregion
|
|
1507
|
+
//#region src/errors.d.ts
|
|
1508
|
+
type ErrorDump = {
|
|
1509
|
+
name: string;
|
|
1510
|
+
message: string;
|
|
1511
|
+
stack?: string;
|
|
1512
|
+
detail: Record<string, unknown>;
|
|
1513
|
+
cause?: unknown;
|
|
1514
|
+
};
|
|
1515
|
+
type SandlyErrorOptions = {
|
|
1516
|
+
cause?: unknown;
|
|
1517
|
+
detail?: Record<string, unknown>;
|
|
1518
|
+
};
|
|
1519
|
+
/**
|
|
1520
|
+
* Base error class for all library errors.
|
|
1521
|
+
*
|
|
1522
|
+
* This extends the native Error class to provide consistent error handling
|
|
1523
|
+
* and structured error information across the library.
|
|
1524
|
+
*
|
|
1525
|
+
* @example Catching library errors
|
|
1526
|
+
* ```typescript
|
|
1527
|
+
* try {
|
|
1528
|
+
* await container.resolve(SomeService);
|
|
1529
|
+
* } catch (error) {
|
|
1530
|
+
* if (error instanceof SandlyError) {
|
|
1531
|
+
* console.error('DI Error:', error.message);
|
|
1532
|
+
* console.error('Details:', error.detail);
|
|
1533
|
+
* }
|
|
1534
|
+
* }
|
|
1535
|
+
* ```
|
|
1536
|
+
*/
|
|
1537
|
+
declare class SandlyError extends Error {
|
|
1538
|
+
detail: Record<string, unknown> | undefined;
|
|
1539
|
+
constructor(message: string, {
|
|
1540
|
+
cause,
|
|
1541
|
+
detail
|
|
1542
|
+
}?: SandlyErrorOptions);
|
|
1543
|
+
static ensure(error: unknown): SandlyError;
|
|
1544
|
+
dump(): ErrorDump;
|
|
1545
|
+
dumps(): string;
|
|
1587
1546
|
/**
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
* @returns A new empty ScopedContainer instance with no registered dependencies
|
|
1547
|
+
* Recursively extract cause chain from any Error.
|
|
1548
|
+
* Handles both AppError (with dump()) and plain Errors (with cause property).
|
|
1591
1549
|
*/
|
|
1592
|
-
|
|
1550
|
+
private dumpCause;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Error thrown when attempting to register a dependency that has already been instantiated.
|
|
1554
|
+
*
|
|
1555
|
+
* This error occurs when calling `container.register()` for a tag that has already been instantiated.
|
|
1556
|
+
* Registration must happen before any instantiation occurs, as cached instances would still be used
|
|
1557
|
+
* by existing dependencies.
|
|
1558
|
+
*/
|
|
1559
|
+
declare class DependencyAlreadyInstantiatedError extends SandlyError {}
|
|
1560
|
+
/**
|
|
1561
|
+
* Error thrown when attempting to use a container that has been destroyed.
|
|
1562
|
+
*
|
|
1563
|
+
* This error occurs when calling `container.resolve()`, `container.register()`, or `container.destroy()`
|
|
1564
|
+
* on a container that has already been destroyed. It indicates a programming error where the container
|
|
1565
|
+
* is being used after it has been destroyed.
|
|
1566
|
+
*/
|
|
1567
|
+
declare class ContainerDestroyedError extends SandlyError {}
|
|
1568
|
+
/**
|
|
1569
|
+
* Error thrown when attempting to retrieve a dependency that hasn't been registered.
|
|
1570
|
+
*
|
|
1571
|
+
* This error occurs when calling `container.resolve(Tag)` for a tag that was never
|
|
1572
|
+
* registered via `container.register()`. It indicates a programming error where
|
|
1573
|
+
* the dependency setup is incomplete.
|
|
1574
|
+
*
|
|
1575
|
+
* @example
|
|
1576
|
+
* ```typescript
|
|
1577
|
+
* const container = Container.empty(); // Empty container
|
|
1578
|
+
*
|
|
1579
|
+
* try {
|
|
1580
|
+
* await c.resolve(UnregisteredService); // This will throw
|
|
1581
|
+
* } catch (error) {
|
|
1582
|
+
* if (error instanceof UnknownDependencyError) {
|
|
1583
|
+
* console.error('Missing dependency:', error.message);
|
|
1584
|
+
* }
|
|
1585
|
+
* }
|
|
1586
|
+
* ```
|
|
1587
|
+
*/
|
|
1588
|
+
declare class UnknownDependencyError extends SandlyError {
|
|
1593
1589
|
/**
|
|
1594
|
-
*
|
|
1590
|
+
* @internal
|
|
1591
|
+
* Creates an UnknownDependencyError for the given tag.
|
|
1595
1592
|
*
|
|
1596
|
-
*
|
|
1597
|
-
* for proper method chaining support.
|
|
1593
|
+
* @param tag - The dependency tag that wasn't found
|
|
1598
1594
|
*/
|
|
1599
|
-
|
|
1595
|
+
constructor(tag: AnyTag);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Error thrown when a circular dependency is detected during dependency resolution.
|
|
1599
|
+
*
|
|
1600
|
+
* This occurs when service A depends on service B, which depends on service A (directly
|
|
1601
|
+
* or through a chain of dependencies). The error includes the full dependency chain
|
|
1602
|
+
* to help identify the circular reference.
|
|
1603
|
+
*
|
|
1604
|
+
* @example Circular dependency scenario
|
|
1605
|
+
* ```typescript
|
|
1606
|
+
* class ServiceA extends Tag.Service('ServiceA') {}
|
|
1607
|
+
* class ServiceB extends Tag.Service('ServiceB') {}
|
|
1608
|
+
*
|
|
1609
|
+
* const container = Container.empty()
|
|
1610
|
+
* .register(ServiceA, async (ctx) =>
|
|
1611
|
+
* new ServiceA(await ctx.resolve(ServiceB)) // Depends on B
|
|
1612
|
+
* )
|
|
1613
|
+
* .register(ServiceB, async (ctx) =>
|
|
1614
|
+
* new ServiceB(await ctx.resolve(ServiceA)) // Depends on A - CIRCULAR!
|
|
1615
|
+
* );
|
|
1616
|
+
*
|
|
1617
|
+
* try {
|
|
1618
|
+
* await c.resolve(ServiceA);
|
|
1619
|
+
* } catch (error) {
|
|
1620
|
+
* if (error instanceof CircularDependencyError) {
|
|
1621
|
+
* console.error('Circular dependency:', error.message);
|
|
1622
|
+
* // Output: "Circular dependency detected for ServiceA: ServiceA -> ServiceB -> ServiceA"
|
|
1623
|
+
* }
|
|
1624
|
+
* }
|
|
1625
|
+
* ```
|
|
1626
|
+
*/
|
|
1627
|
+
declare class CircularDependencyError extends SandlyError {
|
|
1600
1628
|
/**
|
|
1601
|
-
*
|
|
1629
|
+
* @internal
|
|
1630
|
+
* Creates a CircularDependencyError with the dependency chain information.
|
|
1602
1631
|
*
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1632
|
+
* @param tag - The tag where the circular dependency was detected
|
|
1633
|
+
* @param dependencyChain - The chain of dependencies that led to the circular reference
|
|
1605
1634
|
*/
|
|
1606
|
-
|
|
1635
|
+
constructor(tag: AnyTag, dependencyChain: AnyTag[]);
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Error thrown when a dependency factory function throws an error during instantiation.
|
|
1639
|
+
*
|
|
1640
|
+
* This wraps the original error with additional context about which dependency
|
|
1641
|
+
* failed to be created. The original error is preserved as the `cause` property.
|
|
1642
|
+
*
|
|
1643
|
+
* When dependencies are nested (A depends on B depends on C), and C's factory throws,
|
|
1644
|
+
* you get nested DependencyCreationErrors. Use `getRootCause()` to get the original error.
|
|
1645
|
+
*
|
|
1646
|
+
* @example Factory throwing error
|
|
1647
|
+
* ```typescript
|
|
1648
|
+
* class DatabaseService extends Tag.Service('DatabaseService') {}
|
|
1649
|
+
*
|
|
1650
|
+
* const container = Container.empty().register(DatabaseService, () => {
|
|
1651
|
+
* throw new Error('Database connection failed');
|
|
1652
|
+
* });
|
|
1653
|
+
*
|
|
1654
|
+
* try {
|
|
1655
|
+
* await c.resolve(DatabaseService);
|
|
1656
|
+
* } catch (error) {
|
|
1657
|
+
* if (error instanceof DependencyCreationError) {
|
|
1658
|
+
* console.error('Failed to create:', error.message);
|
|
1659
|
+
* console.error('Original error:', error.cause);
|
|
1660
|
+
* }
|
|
1661
|
+
* }
|
|
1662
|
+
* ```
|
|
1663
|
+
*
|
|
1664
|
+
* @example Getting root cause from nested errors
|
|
1665
|
+
* ```typescript
|
|
1666
|
+
* // ServiceA -> ServiceB -> ServiceC (ServiceC throws)
|
|
1667
|
+
* try {
|
|
1668
|
+
* await container.resolve(ServiceA);
|
|
1669
|
+
* } catch (error) {
|
|
1670
|
+
* if (error instanceof DependencyCreationError) {
|
|
1671
|
+
* console.error('Top-level error:', error.message); // "Error creating instance of ServiceA"
|
|
1672
|
+
* const rootCause = error.getRootCause();
|
|
1673
|
+
* console.error('Root cause:', rootCause); // Original error from ServiceC
|
|
1674
|
+
* }
|
|
1675
|
+
* }
|
|
1676
|
+
* ```
|
|
1677
|
+
*/
|
|
1678
|
+
declare class DependencyCreationError extends SandlyError {
|
|
1607
1679
|
/**
|
|
1608
|
-
*
|
|
1680
|
+
* @internal
|
|
1681
|
+
* Creates a DependencyCreationError wrapping the original factory error.
|
|
1609
1682
|
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1683
|
+
* @param tag - The tag of the dependency that failed to be created
|
|
1684
|
+
* @param error - The original error thrown by the factory function
|
|
1612
1685
|
*/
|
|
1613
|
-
|
|
1686
|
+
constructor(tag: AnyTag, error: unknown);
|
|
1614
1687
|
/**
|
|
1615
|
-
*
|
|
1688
|
+
* Traverses the error chain to find the root cause error.
|
|
1616
1689
|
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1690
|
+
* When dependencies are nested, each level wraps the error in a DependencyCreationError.
|
|
1691
|
+
* This method unwraps all the layers to get to the original error that started the failure.
|
|
1692
|
+
*
|
|
1693
|
+
* @returns The root cause error (not a DependencyCreationError unless that's the only error)
|
|
1694
|
+
*
|
|
1695
|
+
* @example
|
|
1696
|
+
* ```typescript
|
|
1697
|
+
* try {
|
|
1698
|
+
* await container.resolve(UserService);
|
|
1699
|
+
* } catch (error) {
|
|
1700
|
+
* if (error instanceof DependencyCreationError) {
|
|
1701
|
+
* const rootCause = error.getRootCause();
|
|
1702
|
+
* console.error('Root cause:', rootCause);
|
|
1703
|
+
* }
|
|
1704
|
+
* }
|
|
1705
|
+
* ```
|
|
1622
1706
|
*/
|
|
1623
|
-
|
|
1707
|
+
getRootCause(): unknown;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Error thrown when one or more finalizers fail during container destruction.
|
|
1711
|
+
*
|
|
1712
|
+
* This error aggregates multiple finalizer failures that occurred during
|
|
1713
|
+
* `container.destroy()`. Even if some finalizers fail, the container cleanup
|
|
1714
|
+
* process continues and this error contains details of all failures.
|
|
1715
|
+
*
|
|
1716
|
+
* @example Handling finalization errors
|
|
1717
|
+
* ```typescript
|
|
1718
|
+
* try {
|
|
1719
|
+
* await container.destroy();
|
|
1720
|
+
* } catch (error) {
|
|
1721
|
+
* if (error instanceof DependencyFinalizationError) {
|
|
1722
|
+
* console.error('Some finalizers failed');
|
|
1723
|
+
* console.error('Error details:', error.detail.errors);
|
|
1724
|
+
* }
|
|
1725
|
+
* }
|
|
1726
|
+
* ```
|
|
1727
|
+
*/
|
|
1728
|
+
declare class DependencyFinalizationError extends SandlyError {
|
|
1729
|
+
private readonly errors;
|
|
1624
1730
|
/**
|
|
1625
|
-
* Internal resolution with delegation logic for scoped containers.
|
|
1626
1731
|
* @internal
|
|
1627
|
-
|
|
1628
|
-
protected resolveInternal<T extends TReg>(tag: T, chain: AnyTag[]): Promise<TagType<T>>;
|
|
1629
|
-
/**
|
|
1630
|
-
* Destroys this scoped container and its children, preserving the container structure for reuse.
|
|
1631
|
-
*
|
|
1632
|
-
* This method ensures proper cleanup order while maintaining reusability:
|
|
1633
|
-
* 1. Destroys all child scopes first (they may depend on parent scope dependencies)
|
|
1634
|
-
* 2. Then calls finalizers for dependencies created in this scope
|
|
1635
|
-
* 3. Clears only instance caches - preserves factories, finalizers, and child structure
|
|
1732
|
+
* Creates a DependencyFinalizationError aggregating multiple finalizer failures.
|
|
1636
1733
|
*
|
|
1637
|
-
*
|
|
1638
|
-
* before their dependents.
|
|
1734
|
+
* @param errors - Array of errors thrown by individual finalizers
|
|
1639
1735
|
*/
|
|
1640
|
-
|
|
1736
|
+
constructor(errors: unknown[]);
|
|
1641
1737
|
/**
|
|
1642
|
-
*
|
|
1738
|
+
* Returns the root causes of the errors that occurred during finalization.
|
|
1643
1739
|
*
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1740
|
+
* @returns An array of the errors that occurred during finalization.
|
|
1741
|
+
* You can expect at least one error in the array.
|
|
1646
1742
|
*/
|
|
1647
|
-
|
|
1743
|
+
getRootCauses(): unknown[];
|
|
1648
1744
|
}
|
|
1745
|
+
|
|
1649
1746
|
//#endregion
|
|
1650
1747
|
//#region src/service.d.ts
|
|
1651
1748
|
/**
|
|
@@ -1843,26 +1940,6 @@ type AutoServiceSpec<T extends ServiceTag<TagId, unknown>> = ServiceDepsTuple<T>
|
|
|
1843
1940
|
* ```
|
|
1844
1941
|
*/
|
|
1845
1942
|
declare function autoService<T extends ServiceTag<TagId, unknown>>(tag: T, spec: AutoServiceSpec<T>): Layer<ServiceDependencies<T>, CanonicalTag<T>>;
|
|
1943
|
+
|
|
1846
1944
|
//#endregion
|
|
1847
|
-
|
|
1848
|
-
/**
|
|
1849
|
-
* Creates a layer that provides a constant value for a given tag.
|
|
1850
|
-
*
|
|
1851
|
-
* @param tag - The value tag to provide
|
|
1852
|
-
* @param constantValue - The constant value to provide
|
|
1853
|
-
* @returns A layer with no dependencies that provides the constant value
|
|
1854
|
-
*
|
|
1855
|
-
* @example
|
|
1856
|
-
* ```typescript
|
|
1857
|
-
* const ApiKey = Tag.of('ApiKey')<string>();
|
|
1858
|
-
* const DatabaseUrl = Tag.of('DatabaseUrl')<string>();
|
|
1859
|
-
*
|
|
1860
|
-
* const apiKey = value(ApiKey, 'my-secret-key');
|
|
1861
|
-
* const dbUrl = value(DatabaseUrl, 'postgresql://localhost:5432/myapp');
|
|
1862
|
-
*
|
|
1863
|
-
* const config = Layer.merge(apiKey, dbUrl);
|
|
1864
|
-
* ```
|
|
1865
|
-
*/
|
|
1866
|
-
declare function value<T extends ValueTag<TagId, unknown>>(tag: T, constantValue: TagType<T>): Layer<never, T>;
|
|
1867
|
-
//#endregion
|
|
1868
|
-
export { type AnyLayer, type AnyTag, CircularDependencyError, Container, ContainerDestroyedError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, type DependencyLifecycle, type DependencySpec, type Factory, type Finalizer, type IContainer, type Inject, InjectSource, Layer, type PromiseOrValue, type ResolutionContext, SandlyError, type Scope, ScopedContainer, type ServiceDependencies, type ServiceDepsTuple, type ServiceTag, Tag, type TagType, UnknownDependencyError, type ValueTag, autoService, layer, service, value };
|
|
1945
|
+
export { AnyLayer, AnyTag, CircularDependencyError, Container, ContainerDestroyedError, ContainerTags, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, DependencyLifecycle, DependencySpec, Factory, Finalizer, IContainer, Inject, InjectSource, Layer, PromiseOrValue, ResolutionContext, SandlyError, Scope, ScopedContainer, ServiceDependencies, ServiceDepsTuple, ServiceTag, Tag, TagType, UnknownDependencyError, ValueTag, autoService, constant, dependency, layer, service };
|