@zudojs/container 0.0.1 → 1.0.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 (83) hide show
  1. package/README.md +205 -8
  2. package/dist/containerCore/containerCore.core.d.ts +105 -14
  3. package/dist/containerCore/containerCore.core.js +211 -44
  4. package/dist/containerCore/containerCore.scope.d.ts +63 -18
  5. package/dist/containerCore/containerCore.scope.js +108 -29
  6. package/dist/containerCore/containerCore.type.d.ts +22 -16
  7. package/dist/containerLifecycle/containerLifecycle.core.d.ts +59 -9
  8. package/dist/containerLifecycle/containerLifecycle.core.js +58 -15
  9. package/dist/containerOptions/containerOptions.type.d.ts +7 -17
  10. package/dist/containerOptions/containerOptions.type.js +0 -19
  11. package/dist/containerProvider/containerProvider.core.d.ts +11 -3
  12. package/dist/containerProvider/containerProvider.core.js +11 -5
  13. package/dist/containerRegistration/containerRegistration.core.d.ts +9 -3
  14. package/dist/containerRegistration/containerRegistration.core.js +0 -6
  15. package/dist/containerRegistry/containerRegistry.core.d.ts +23 -9
  16. package/dist/containerRegistry/containerRegistry.core.js +50 -47
  17. package/dist/containerRegistry/containerRegistry.type.d.ts +3 -1
  18. package/dist/containerRegistry/containerRegistry.type.js +2 -0
  19. package/dist/containerRegistry/index.d.ts +0 -1
  20. package/dist/containerRegistry/index.js +0 -1
  21. package/dist/containerResolution/containerResolution.core.d.ts +42 -10
  22. package/dist/containerResolution/containerResolution.core.js +207 -64
  23. package/dist/containerResolution/containerResolution.error.d.ts +68 -3
  24. package/dist/containerResolution/containerResolution.error.js +107 -3
  25. package/dist/containerResolution/containerResolution.type.d.ts +35 -3
  26. package/dist/containerResolution/index.d.ts +1 -0
  27. package/dist/containerResolution/index.js +1 -0
  28. package/dist/containerToken/containerToken.type.d.ts +26 -1
  29. package/dist/containerToken/containerToken.type.js +18 -0
  30. package/package.json +17 -8
  31. package/dist/.tsbuildinfo +0 -1
  32. package/dist/containerCore/containerCore.core.d.ts.map +0 -1
  33. package/dist/containerCore/containerCore.core.js.map +0 -1
  34. package/dist/containerCore/containerCore.scope.d.ts.map +0 -1
  35. package/dist/containerCore/containerCore.scope.js.map +0 -1
  36. package/dist/containerCore/containerCore.type.d.ts.map +0 -1
  37. package/dist/containerCore/containerCore.type.js.map +0 -1
  38. package/dist/containerCore/index.d.ts.map +0 -1
  39. package/dist/containerCore/index.js.map +0 -1
  40. package/dist/containerLifecycle/containerLifecycle.core.d.ts.map +0 -1
  41. package/dist/containerLifecycle/containerLifecycle.core.js.map +0 -1
  42. package/dist/containerLifecycle/index.d.ts.map +0 -1
  43. package/dist/containerLifecycle/index.js.map +0 -1
  44. package/dist/containerOptions/containerOptions.type.d.ts.map +0 -1
  45. package/dist/containerOptions/containerOptions.type.js.map +0 -1
  46. package/dist/containerOptions/index.d.ts.map +0 -1
  47. package/dist/containerOptions/index.js.map +0 -1
  48. package/dist/containerProvider/containerProvider.core.d.ts.map +0 -1
  49. package/dist/containerProvider/containerProvider.core.js.map +0 -1
  50. package/dist/containerProvider/index.d.ts.map +0 -1
  51. package/dist/containerProvider/index.js.map +0 -1
  52. package/dist/containerRegistration/containerRegistration.core.d.ts.map +0 -1
  53. package/dist/containerRegistration/containerRegistration.core.js.map +0 -1
  54. package/dist/containerRegistration/index.d.ts.map +0 -1
  55. package/dist/containerRegistration/index.js.map +0 -1
  56. package/dist/containerRegistry/containerRegistry.core.d.ts.map +0 -1
  57. package/dist/containerRegistry/containerRegistry.core.js.map +0 -1
  58. package/dist/containerRegistry/containerRegistry.error.d.ts +0 -12
  59. package/dist/containerRegistry/containerRegistry.error.d.ts.map +0 -1
  60. package/dist/containerRegistry/containerRegistry.error.js +0 -22
  61. package/dist/containerRegistry/containerRegistry.error.js.map +0 -1
  62. package/dist/containerRegistry/containerRegistry.type.d.ts.map +0 -1
  63. package/dist/containerRegistry/containerRegistry.type.js.map +0 -1
  64. package/dist/containerRegistry/index.d.ts.map +0 -1
  65. package/dist/containerRegistry/index.js.map +0 -1
  66. package/dist/containerResolution/containerResolution.core.d.ts.map +0 -1
  67. package/dist/containerResolution/containerResolution.core.js.map +0 -1
  68. package/dist/containerResolution/containerResolution.error.d.ts.map +0 -1
  69. package/dist/containerResolution/containerResolution.error.js.map +0 -1
  70. package/dist/containerResolution/containerResolution.type.d.ts.map +0 -1
  71. package/dist/containerResolution/containerResolution.type.js.map +0 -1
  72. package/dist/containerResolution/index.d.ts.map +0 -1
  73. package/dist/containerResolution/index.js.map +0 -1
  74. package/dist/containerScope/containerScope.type.d.ts.map +0 -1
  75. package/dist/containerScope/containerScope.type.js.map +0 -1
  76. package/dist/containerScope/index.d.ts.map +0 -1
  77. package/dist/containerScope/index.js.map +0 -1
  78. package/dist/containerToken/containerToken.type.d.ts.map +0 -1
  79. package/dist/containerToken/containerToken.type.js.map +0 -1
  80. package/dist/containerToken/index.d.ts.map +0 -1
  81. package/dist/containerToken/index.js.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
@@ -1,8 +1,73 @@
1
1
  /**
2
2
  * @zudojs/container/containerResolution/containerResolution.error
3
3
  *
4
- * Error classes re-exported from @zudojs/errors.
5
- * Token description helpers live in containerToken.type.ts.
4
+ * Resolution error classes local to this package. They extend the published
5
+ * `ContainerError` base from @zudojs/errors; the published package does not
6
+ * yet ship dedicated classes for these failure modes.
6
7
  */
7
- export {};
8
+ import { ContainerError, ProviderResolutionError } from "@zudojs/errors";
9
+ /**
10
+ * Thrown when a SCOPED registration is resolved without a scope
11
+ * (i.e. directly from the root container).
12
+ */
13
+ export declare class ScopedResolutionError extends ContainerError {
14
+ readonly chain: readonly string[];
15
+ constructor(token: string, chain?: readonly string[]);
16
+ }
17
+ /**
18
+ * Thrown when a longer-lived consumer (a SINGLETON) would capture a
19
+ * shorter-lived SCOPED dependency, freezing it beyond its scope's lifetime.
20
+ */
21
+ export declare class CaptiveDependencyError extends ContainerError {
22
+ readonly consumer: string;
23
+ readonly dependency: string;
24
+ readonly chain: readonly string[];
25
+ constructor(consumer: string, dependency: string, chain?: readonly string[]);
26
+ }
27
+ /**
28
+ * Thrown when a resolution chain exceeds `maxResolutionDepth`.
29
+ */
30
+ export declare class MaxResolutionDepthError extends ContainerError {
31
+ readonly depth: number;
32
+ readonly maxDepth: number;
33
+ readonly chain: readonly string[];
34
+ constructor(token: string, depth: number, maxDepth: number, chain?: readonly string[]);
35
+ }
36
+ /**
37
+ * Thrown when a provider fails while resolving a token. Extends the published
38
+ * `ProviderResolutionError` with the resolution chain that led to the failing
39
+ * token (outermost request first) so callers can inspect it programmatically
40
+ * instead of parsing the message.
41
+ */
42
+ export declare class DependencyResolutionError extends ProviderResolutionError {
43
+ readonly chain: readonly string[];
44
+ constructor(token: string, cause: unknown, chain?: readonly string[]);
45
+ toJSON(): {
46
+ name: string;
47
+ message: string;
48
+ code: string;
49
+ category: import("@zudojs/errors").ErrorCategory;
50
+ severity: import("@zudojs/errors").ErrorSeverity;
51
+ statusCode: number;
52
+ expose: boolean;
53
+ isOperational: boolean;
54
+ metadata: Readonly<import("@zudojs/errors").ErrorMetadata>;
55
+ stack?: string;
56
+ cause?: import("@zudojs/errors").SerializedBaseError | unknown;
57
+ token?: string | undefined;
58
+ chain: readonly string[];
59
+ };
60
+ }
61
+ /**
62
+ * Thrown when a factory registered as SINGLETON or SCOPED returns a Promise.
63
+ *
64
+ * The container is synchronous: it would otherwise cache the Promise itself
65
+ * as the instance and the eventual value would never be disposal-tracked.
66
+ * Await the resource before registering it (e.g. `registerValue`), or use a
67
+ * TRANSIENT registration whose callers await the result themselves.
68
+ */
69
+ export declare class AsyncProviderError extends ContainerError {
70
+ readonly scope: string;
71
+ constructor(token: string, scope: string);
72
+ }
8
73
  //# sourceMappingURL=containerResolution.error.d.ts.map
@@ -1,8 +1,112 @@
1
1
  /**
2
2
  * @zudojs/container/containerResolution/containerResolution.error
3
3
  *
4
- * Error classes re-exported from @zudojs/errors.
5
- * Token description helpers live in containerToken.type.ts.
4
+ * Resolution error classes local to this package. They extend the published
5
+ * `ContainerError` base from @zudojs/errors; the published package does not
6
+ * yet ship dedicated classes for these failure modes.
6
7
  */
7
- export {};
8
+ import { ContainerError, ProviderResolutionError } from "@zudojs/errors";
9
+ function formatChain(chain) {
10
+ return chain.join(" -> ");
11
+ }
12
+ /**
13
+ * Thrown when a SCOPED registration is resolved without a scope
14
+ * (i.e. directly from the root container).
15
+ */
16
+ export class ScopedResolutionError extends ContainerError {
17
+ chain;
18
+ constructor(token, chain = []) {
19
+ const suffix = chain.length > 1 ? ` (chain: ${formatChain(chain)})` : "";
20
+ super(`Scoped token ${token} cannot be resolved outside a scope. ` +
21
+ `Create one with container.createScope() and resolve through it.${suffix}`, {
22
+ code: "CONTAINER_SCOPED_OUTSIDE_SCOPE",
23
+ token,
24
+ statusCode: 500,
25
+ expose: false,
26
+ });
27
+ this.chain = chain;
28
+ }
29
+ }
30
+ /**
31
+ * Thrown when a longer-lived consumer (a SINGLETON) would capture a
32
+ * shorter-lived SCOPED dependency, freezing it beyond its scope's lifetime.
33
+ */
34
+ export class CaptiveDependencyError extends ContainerError {
35
+ consumer;
36
+ dependency;
37
+ chain;
38
+ constructor(consumer, dependency, chain = []) {
39
+ const suffix = chain.length > 1 ? ` (chain: ${formatChain(chain)})` : "";
40
+ super(`Captive dependency: singleton "${consumer}" depends on scoped "${dependency}". ` +
41
+ `A longer-lived consumer cannot capture a shorter-lived dependency.${suffix}`, {
42
+ code: "CONTAINER_CAPTIVE_DEPENDENCY",
43
+ token: dependency,
44
+ statusCode: 500,
45
+ expose: false,
46
+ });
47
+ this.consumer = consumer;
48
+ this.dependency = dependency;
49
+ this.chain = chain;
50
+ }
51
+ }
52
+ /**
53
+ * Thrown when a resolution chain exceeds `maxResolutionDepth`.
54
+ */
55
+ export class MaxResolutionDepthError extends ContainerError {
56
+ depth;
57
+ maxDepth;
58
+ chain;
59
+ constructor(token, depth, maxDepth, chain = []) {
60
+ super(`Maximum resolution depth of ${maxDepth} exceeded while resolving ` +
61
+ `token ${token} at depth ${depth}.`, {
62
+ code: "CONTAINER_MAX_RESOLUTION_DEPTH",
63
+ token,
64
+ statusCode: 500,
65
+ expose: false,
66
+ });
67
+ this.depth = depth;
68
+ this.maxDepth = maxDepth;
69
+ this.chain = chain;
70
+ }
71
+ }
72
+ /**
73
+ * Thrown when a provider fails while resolving a token. Extends the published
74
+ * `ProviderResolutionError` with the resolution chain that led to the failing
75
+ * token (outermost request first) so callers can inspect it programmatically
76
+ * instead of parsing the message.
77
+ */
78
+ export class DependencyResolutionError extends ProviderResolutionError {
79
+ chain;
80
+ constructor(token, cause, chain = []) {
81
+ const detail = cause instanceof Error ? cause.message : String(cause);
82
+ const suffix = chain.length > 0 ? ` (chain: ${formatChain(chain)})` : "";
83
+ super(token, `Failed to resolve ${token}: ${detail}${suffix}`, cause);
84
+ this.chain = Object.freeze([...chain]);
85
+ }
86
+ toJSON() {
87
+ return { ...super.toJSON(), chain: this.chain };
88
+ }
89
+ }
90
+ /**
91
+ * Thrown when a factory registered as SINGLETON or SCOPED returns a Promise.
92
+ *
93
+ * The container is synchronous: it would otherwise cache the Promise itself
94
+ * as the instance and the eventual value would never be disposal-tracked.
95
+ * Await the resource before registering it (e.g. `registerValue`), or use a
96
+ * TRANSIENT registration whose callers await the result themselves.
97
+ */
98
+ export class AsyncProviderError extends ContainerError {
99
+ scope;
100
+ constructor(token, scope) {
101
+ super(`Factory for ${scope} token ${token} returned a Promise. Async factories ` +
102
+ `are not supported for cached (singleton/scoped) registrations: ` +
103
+ `await the value before registering it, or register it as TRANSIENT.`, {
104
+ code: "CONTAINER_ASYNC_PROVIDER_UNSUPPORTED",
105
+ token,
106
+ statusCode: 500,
107
+ expose: false,
108
+ });
109
+ this.scope = scope;
110
+ }
111
+ }
8
112
  //# sourceMappingURL=containerResolution.error.js.map
@@ -6,8 +6,17 @@ import type { ContainerScope } from "../containerScope/containerScope.type.js";
6
6
  import type { Token } from "../containerToken/containerToken.type.js";
7
7
  /**
8
8
  * A cache containing resolved dependency instances.
9
+ *
10
+ * A plain `Map` satisfies this interface; scope caches created by the
11
+ * resolver additionally fall back to their parent scope's cache for lookups
12
+ * (see {@link ContainerResolver.createScope}).
9
13
  */
10
- export type ResolutionCache = Map<Token<unknown>, unknown>;
14
+ export interface ResolutionCache {
15
+ has(token: Token<unknown>): boolean;
16
+ get(token: Token<unknown>): unknown;
17
+ set(token: Token<unknown>, value: unknown): void;
18
+ clear(): void;
19
+ }
11
20
  /**
12
21
  * Dependency resolution path.
13
22
  * Used for diagnostics and circular dependency detection.
@@ -17,12 +26,35 @@ export type ResolutionPath = readonly Token<unknown>[];
17
26
  * Options controlling dependency resolution.
18
27
  */
19
28
  export interface ResolutionOptions {
20
- /** Existing cache for the current resolution scope. */
29
+ /**
30
+ * Scope cache for SCOPED instances. When absent the resolution is a root
31
+ * resolution and SCOPED registrations throw. SINGLETON instances always
32
+ * live in the resolver's own singleton cache, never in this cache.
33
+ */
21
34
  readonly cache?: ResolutionCache;
22
35
  /** Current dependency resolution path. Normally managed internally. */
23
36
  readonly path?: ResolutionPath;
24
- /** Whether to allow resolving unregistered classes directly from their constructors. Defaults to true. */
37
+ /** Whether unregistered class tokens may be resolved. Defaults to true. */
25
38
  readonly autoRegisterClasses?: boolean;
39
+ /**
40
+ * Whether auto-resolved class tokens may be added to the registry.
41
+ * When false (e.g. registrations are frozen) unregistered classes are
42
+ * instantiated ephemerally without being registered. Defaults to true.
43
+ */
44
+ readonly allowRegistration?: boolean;
45
+ /** Whether circular dependencies are detected. Defaults to true. */
46
+ readonly detectCircularDependencies?: boolean;
47
+ /**
48
+ * Maximum resolution chain depth before resolution aborts with a
49
+ * MaxResolutionDepthError. Defaults to 100.
50
+ */
51
+ readonly maxResolutionDepth?: number;
52
+ /**
53
+ * Invoked for every instance the resolver creates during this resolution
54
+ * (never for cache hits), in creation order — dependencies are reported
55
+ * before their dependents. Owners use it to track instances for disposal.
56
+ */
57
+ readonly onInstanceCreated?: (result: ResolutionResult<unknown>) => void;
26
58
  }
27
59
  /**
28
60
  * Resolution result containing the resolved value and diagnostic information.
@@ -4,5 +4,6 @@
4
4
  * Dependency resolution and injection.
5
5
  */
6
6
  export * from "./containerResolution.type.js";
7
+ export * from "./containerResolution.error.js";
7
8
  export * from "./containerResolution.core.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -4,5 +4,6 @@
4
4
  * Dependency resolution and injection.
5
5
  */
6
6
  export * from "./containerResolution.type.js";
7
+ export * from "./containerResolution.error.js";
7
8
  export * from "./containerResolution.core.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -2,8 +2,15 @@
2
2
  * Dependency injection token definitions for Zudojs.
3
3
  * Tokens identify dependencies within the container.
4
4
  */
5
+ /**
6
+ * A class constructor usable as a token or provider implementation.
7
+ *
8
+ * The parameter list is intentionally loose (`any[]`) so classes with typed
9
+ * constructor parameters are accepted; the container supplies arguments via
10
+ * the provider's `inject` list (or none for zero-arg constructors).
11
+ */
5
12
  export interface Constructor<T = unknown> {
6
- new (...args: unknown[]): T;
13
+ new (...args: any[]): T;
7
14
  }
8
15
  export type TokenSymbol = symbol;
9
16
  export type TokenString = string;
@@ -12,7 +19,25 @@ export interface InjectionToken<T = unknown> {
12
19
  readonly token: Token<T>;
13
20
  readonly description?: string;
14
21
  }
22
+ /**
23
+ * Creates a unique, typed injection token backed by a fresh `Symbol`.
24
+ *
25
+ * Note on typing: plain string and symbol tokens are *untyped casts* — the
26
+ * container cannot verify at compile time that the value registered under a
27
+ * string/symbol actually has type `T`. Prefer `InjectionToken`s created here
28
+ * (or constructor tokens) so the association between token and type lives in
29
+ * one place.
30
+ */
15
31
  export declare function createToken<T>(description: string): InjectionToken<T>;
32
+ /**
33
+ * Creates a typed injection token backed by `Symbol.for(key)`.
34
+ *
35
+ * Because `Symbol.for` uses the process-wide global symbol registry, two
36
+ * independent calls (even from different packages) with the same `key`
37
+ * produce the SAME token and will collide in the container — this is by
38
+ * design for cross-package sharing, but means keys must be namespaced
39
+ * (e.g. `"myapp:db"`). Use {@link createToken} for collision-free tokens.
40
+ */
16
41
  export declare function createGlobalToken<T>(key: string): InjectionToken<T>;
17
42
  export declare function unwrapToken<T>(token: Token<T> | InjectionToken<T>): Token<T>;
18
43
  export declare function isInjectionToken<T = unknown>(value: unknown): value is InjectionToken<T>;
@@ -2,9 +2,27 @@
2
2
  * Dependency injection token definitions for Zudojs.
3
3
  * Tokens identify dependencies within the container.
4
4
  */
5
+ /**
6
+ * Creates a unique, typed injection token backed by a fresh `Symbol`.
7
+ *
8
+ * Note on typing: plain string and symbol tokens are *untyped casts* — the
9
+ * container cannot verify at compile time that the value registered under a
10
+ * string/symbol actually has type `T`. Prefer `InjectionToken`s created here
11
+ * (or constructor tokens) so the association between token and type lives in
12
+ * one place.
13
+ */
5
14
  export function createToken(description) {
6
15
  return Object.freeze({ token: Symbol(description), description });
7
16
  }
17
+ /**
18
+ * Creates a typed injection token backed by `Symbol.for(key)`.
19
+ *
20
+ * Because `Symbol.for` uses the process-wide global symbol registry, two
21
+ * independent calls (even from different packages) with the same `key`
22
+ * produce the SAME token and will collide in the container — this is by
23
+ * design for cross-package sharing, but means keys must be namespaced
24
+ * (e.g. `"myapp:db"`). Use {@link createToken} for collision-free tokens.
25
+ */
8
26
  export function createGlobalToken(key) {
9
27
  return Object.freeze({ token: Symbol.for(key), description: key });
10
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/container",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "description": "Token-based dependency injection container for managing application dependencies and service lifetimes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -12,13 +12,17 @@
12
12
  }
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "!dist/**/*.map",
17
+ "!dist/**/*.tsbuildinfo",
18
+ "!dist/.tsbuildinfo"
16
19
  ],
17
20
  "dependencies": {
18
- "@zudojs/errors": "0.0.1"
21
+ "@zudojs/errors": "1.0.0"
19
22
  },
20
23
  "devDependencies": {
21
- "typescript": "^7.0.2"
24
+ "typescript": "7.0.2",
25
+ "vitest": "^4.1.11"
22
26
  },
23
27
  "license": "MIT",
24
28
  "publishConfig": {
@@ -27,7 +31,6 @@
27
31
  "engines": {
28
32
  "node": ">=24.0.0"
29
33
  },
30
- "module": "./dist/index.js",
31
34
  "keywords": [
32
35
  "zudojs",
33
36
  "di",
@@ -35,13 +38,19 @@
35
38
  "container"
36
39
  ],
37
40
  "homepage": "https://github.com/oyinlola-tech/zudo#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/oyinlola-tech/zudo/issues"
43
+ },
38
44
  "repository": {
39
45
  "type": "git",
40
- "url": "https://github.com/oyinlola-tech/zudo"
46
+ "url": "https://github.com/oyinlola-tech/zudo",
47
+ "directory": "packages/container"
41
48
  },
42
49
  "scripts": {
43
50
  "build": "tsc -p tsconfig.json",
44
- "typecheck": "tsc -p tsconfig.json --noEmit",
45
- "clean": "tsc -p tsconfig.json --clean"
51
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
52
+ "clean": "rm -rf dist",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest"
46
55
  }
47
56
  }