@kurdel/ioc 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +39 -0
- package/lib/api/container.d.ts +135 -0
- package/lib/api/container.js +2 -0
- package/lib/api/container.js.map +1 -0
- package/lib/api/identifier.d.ts +2 -0
- package/lib/api/identifier.js +2 -0
- package/lib/api/identifier.js.map +1 -0
- package/lib/api/injection-token.d.ts +7 -0
- package/lib/api/injection-token.js +7 -0
- package/lib/api/injection-token.js.map +1 -0
- package/lib/api/types.d.ts +2 -0
- package/lib/api/types.js +2 -0
- package/lib/api/types.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +5 -0
- package/lib/index.js.map +1 -0
- package/lib/runtime/binding-in-contract.d.ts +21 -0
- package/lib/runtime/binding-in-contract.js +25 -0
- package/lib/runtime/binding-in-contract.js.map +1 -0
- package/lib/runtime/binding-to-contract-impl.d.ts +38 -0
- package/lib/runtime/binding-to-contract-impl.js +46 -0
- package/lib/runtime/binding-to-contract-impl.js.map +1 -0
- package/lib/runtime/binding-with-contract.d.ts +33 -0
- package/lib/runtime/binding-with-contract.js +36 -0
- package/lib/runtime/binding-with-contract.js.map +1 -0
- package/lib/runtime/binding-with-in-contract-impl.d.ts +34 -0
- package/lib/runtime/binding-with-in-contract-impl.js +41 -0
- package/lib/runtime/binding-with-in-contract-impl.js.map +1 -0
- package/lib/runtime/binding.d.ts +22 -0
- package/lib/runtime/binding.js +21 -0
- package/lib/runtime/binding.js.map +1 -0
- package/lib/runtime/ioc-container.d.ts +102 -0
- package/lib/runtime/ioc-container.js +218 -0
- package/lib/runtime/ioc-container.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Andrii Sorokin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @kurdel/ioc
|
|
2
|
+
|
|
3
|
+
Lightweight **dependency injection container** used across the Kurdel framework.
|
|
4
|
+
Designed for modular architecture, request-scoped resolution, and type-safe tokens.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📦 Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @kurdel/ioc
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚀 Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createContainer, createToken } from '@kurdel/ioc';
|
|
20
|
+
|
|
21
|
+
interface UserRepo { findAll(): any[] }
|
|
22
|
+
class UserRepoImpl implements UserRepo {
|
|
23
|
+
findAll() { return [{ id: 1, name: 'Ada' }]; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const token = createToken<UserRepo>('UserRepo');
|
|
27
|
+
const ioc = createContainer();
|
|
28
|
+
|
|
29
|
+
ioc.bind(token).to(UserRepoImpl).inSingletonScope();
|
|
30
|
+
|
|
31
|
+
const repo = ioc.get(token);
|
|
32
|
+
console.log(repo.findAll());
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📄 License
|
|
38
|
+
|
|
39
|
+
MIT © Andrii Sorokin
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Newable } from '@kurdel/common';
|
|
2
|
+
import type { Identifier } from '../api/identifier.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fluent API returned by {@link Container.bind}.
|
|
5
|
+
* Allows binding an abstract identifier (interface, symbol, token)
|
|
6
|
+
* to a concrete implementation and configuring additional metadata.
|
|
7
|
+
*/
|
|
8
|
+
export interface BindingToContract<T> {
|
|
9
|
+
/**
|
|
10
|
+
* Bind this identifier to the given class constructor.
|
|
11
|
+
* @param impl - Class to instantiate when this identifier is resolved.
|
|
12
|
+
* @returns A fluent builder for configuring deps/scope.
|
|
13
|
+
*/
|
|
14
|
+
to(impl: Newable<T>): BindingWithInContract<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Bind this identifier to a pre-created instance.
|
|
17
|
+
* The same value will be returned for each resolution.
|
|
18
|
+
* @param value - Instance to associate with the identifier.
|
|
19
|
+
*/
|
|
20
|
+
toInstance(value: T): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fluent API returned by {@link Container.put} or {@link BindingToContract.to}.
|
|
24
|
+
* Used to configure constructor dependencies and scope.
|
|
25
|
+
*/
|
|
26
|
+
export interface BindingWithInContract<T> {
|
|
27
|
+
/**
|
|
28
|
+
* Define constructor dependencies for the bound class.
|
|
29
|
+
* Each key corresponds to a constructor parameter name,
|
|
30
|
+
* and the value is the identifier of the dependency.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* container.put(UserService).with({ repo: UserRepository });
|
|
34
|
+
*/
|
|
35
|
+
with(deps: Record<string, Identifier>): this;
|
|
36
|
+
/**
|
|
37
|
+
* Mark this binding as a singleton.
|
|
38
|
+
* The same instance will be reused across resolutions within this container
|
|
39
|
+
* (and possibly its child scopes, depending on the implementation).
|
|
40
|
+
*/
|
|
41
|
+
inSingletonScope(): this;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Node in a dependency graph produced by {@link Container.getGraph}.
|
|
45
|
+
*/
|
|
46
|
+
export interface DependencyNode {
|
|
47
|
+
/** Human-readable name or key of the binding. */
|
|
48
|
+
key: string;
|
|
49
|
+
/** Indicates whether this dependency originated from a parent container. */
|
|
50
|
+
fromParent?: boolean;
|
|
51
|
+
/** Nested dependency nodes (constructor deps). */
|
|
52
|
+
deps: DependencyNode[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Minimal Dependency Injection container contract.
|
|
56
|
+
*
|
|
57
|
+
* Defines all essential operations supported by the Kurdel IoC system.
|
|
58
|
+
* Implementations should follow SOLID principles and be runtime-agnostic.
|
|
59
|
+
*/
|
|
60
|
+
export interface Container {
|
|
61
|
+
/**
|
|
62
|
+
* Create a new **child (request-scoped)** container.
|
|
63
|
+
*
|
|
64
|
+
* The child delegates lookups to this container (its parent) when
|
|
65
|
+
* a binding is not found locally. Singleton bindings registered in the
|
|
66
|
+
* parent remain shared; bindings added to the child are isolated to the
|
|
67
|
+
* child’s lifetime.
|
|
68
|
+
*
|
|
69
|
+
* @returns A new child container.
|
|
70
|
+
*/
|
|
71
|
+
createScope(): Container;
|
|
72
|
+
/**
|
|
73
|
+
* Bind an abstract identifier (symbol/interface) to a concrete class.
|
|
74
|
+
* Returns a fluent API to configure its dependencies and lifetime.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* container.bind<IDb>(DBToken).to(SqliteDb).inSingletonScope();
|
|
78
|
+
*/
|
|
79
|
+
bind<T>(key: Identifier<T>): BindingToContract<T>;
|
|
80
|
+
/**
|
|
81
|
+
* Register a concrete class (constructor) directly in the container.
|
|
82
|
+
* Returns a fluent API to configure constructor dependencies and lifetime.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* container.put(UserService).with({ repo: UserRepository });
|
|
86
|
+
*/
|
|
87
|
+
put<T>(ctor: Newable<T>): BindingWithInContract<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Register a factory function that produces an instance each time
|
|
90
|
+
* the dependency is resolved, unless the binding is marked as singleton.
|
|
91
|
+
*
|
|
92
|
+
* @param key - Identifier to bind.
|
|
93
|
+
* @param factory - Function returning the instance.
|
|
94
|
+
*/
|
|
95
|
+
toFactory<T>(key: Identifier<T>, factory: () => T): void;
|
|
96
|
+
/**
|
|
97
|
+
* Register a ready-made instance for the identifier.
|
|
98
|
+
* The instance will be reused for all subsequent resolutions.
|
|
99
|
+
*
|
|
100
|
+
* @param key - Identifier for the instance.
|
|
101
|
+
* @param value - The actual instance to store.
|
|
102
|
+
*/
|
|
103
|
+
set<T>(key: Identifier<T>, value: T): void;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve and return an instance for the given identifier.
|
|
106
|
+
* Implementations must recursively resolve declared dependencies.
|
|
107
|
+
*
|
|
108
|
+
* @typeParam T - Expected instance type.
|
|
109
|
+
* @param key - Identifier (token/class) to resolve.
|
|
110
|
+
* @throws If the identifier is not registered in this container hierarchy.
|
|
111
|
+
*/
|
|
112
|
+
get<T>(key: Identifier<T>): T;
|
|
113
|
+
/**
|
|
114
|
+
* Check whether an identifier is bound in this container or any parent.
|
|
115
|
+
*
|
|
116
|
+
* @param key - Identifier to check.
|
|
117
|
+
* @returns `true` if found locally or in the parent chain.
|
|
118
|
+
*/
|
|
119
|
+
has(key: Identifier): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Build and return a dependency graph for this container.
|
|
122
|
+
* Useful for introspection and debugging.
|
|
123
|
+
*
|
|
124
|
+
* @param rootKey - Optional starting identifier (defaults to all local bindings).
|
|
125
|
+
* @returns Dependency tree(s) describing how bindings reference each other.
|
|
126
|
+
*/
|
|
127
|
+
getGraph(rootKey?: Identifier): DependencyNode[];
|
|
128
|
+
/**
|
|
129
|
+
* Print a visual representation of the dependency graph to the console.
|
|
130
|
+
* Intended for developer diagnostics and debugging.
|
|
131
|
+
*
|
|
132
|
+
* @param rootKey - Optional starting identifier.
|
|
133
|
+
*/
|
|
134
|
+
printGraph(rootKey?: Identifier): void;
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/api/container.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifier.js","sourceRoot":"","sources":["../../src/api/identifier.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type InjectionToken<T> = symbol & {
|
|
2
|
+
__type?: T;
|
|
3
|
+
};
|
|
4
|
+
export type TokenFor<T> = InjectionToken<T>;
|
|
5
|
+
export declare function createToken<T>(key: string, global?: boolean): InjectionToken<T>;
|
|
6
|
+
export declare const createGlobalToken: <T>(key: string) => InjectionToken<T>;
|
|
7
|
+
export declare const createLocalToken: <T>(key: string) => InjectionToken<T>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function createToken(key, global = false) {
|
|
2
|
+
const s = global ? Symbol.for(key) : Symbol(key);
|
|
3
|
+
return s;
|
|
4
|
+
}
|
|
5
|
+
export const createGlobalToken = (key) => createToken(key, true);
|
|
6
|
+
export const createLocalToken = (key) => createToken(key, false);
|
|
7
|
+
//# sourceMappingURL=injection-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection-token.js","sourceRoot":"","sources":["../../src/api/injection-token.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,WAAW,CAAI,GAAW,EAAE,MAAM,GAAG,KAAK;IACxD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,CAAsB,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAI,GAAW,EAAE,EAAE,CAAC,WAAW,CAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AAC/E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAI,GAAW,EAAE,EAAE,CAAC,WAAW,CAAI,GAAG,EAAE,KAAK,CAAC,CAAC"}
|
package/lib/api/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":""}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AAErC,cAAc,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Binding } from '../runtime/binding.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fluent contract returned after `.to()` or `.toInstance()`.
|
|
4
|
+
*
|
|
5
|
+
* Allows you to configure the binding scope:
|
|
6
|
+
* - transient (default) → new instance every time
|
|
7
|
+
* - singleton → one instance cached and reused
|
|
8
|
+
*/
|
|
9
|
+
export declare class BindingInContract<T> {
|
|
10
|
+
private binding;
|
|
11
|
+
constructor(binding: Binding<T>);
|
|
12
|
+
/**
|
|
13
|
+
* Make this binding a singleton.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* container.bind<IDatabase>(IDatabase).to(SQLiteDatabase).inSingletonScope();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
inSingletonScope(): this;
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent contract returned after `.to()` or `.toInstance()`.
|
|
3
|
+
*
|
|
4
|
+
* Allows you to configure the binding scope:
|
|
5
|
+
* - transient (default) → new instance every time
|
|
6
|
+
* - singleton → one instance cached and reused
|
|
7
|
+
*/
|
|
8
|
+
export class BindingInContract {
|
|
9
|
+
constructor(binding) {
|
|
10
|
+
this.binding = binding;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Make this binding a singleton.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* container.bind<IDatabase>(IDatabase).to(SQLiteDatabase).inSingletonScope();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
inSingletonScope() {
|
|
21
|
+
this.binding.scope = 'Singleton';
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=binding-in-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-in-contract.js","sourceRoot":"","sources":["../../src/runtime/binding-in-contract.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,iBAAiB;IAG5B,YAAY,OAAmB;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB;QACd,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,WAAW,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Newable } from '@kurdel/common';
|
|
2
|
+
import type { BindingToContract, BindingWithInContract } from '../api/container.js';
|
|
3
|
+
import type { Binding } from '../runtime/binding.js';
|
|
4
|
+
/**
|
|
5
|
+
* Contract returned by `IoCContainer.bind`.
|
|
6
|
+
*
|
|
7
|
+
* Provides methods to bind an abstract identifier (interface, symbol, etc.)
|
|
8
|
+
* to a concrete implementation or to an existing instance.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BindingToContractImpl<T> implements BindingToContract<T> {
|
|
11
|
+
private binding;
|
|
12
|
+
constructor(binding: Binding<T>);
|
|
13
|
+
/**
|
|
14
|
+
* Bind the identifier to a class constructor.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* container.bind(IDatabase).to(SQLiteDatabase);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @param constructor The class to instantiate when resolving this identifier.
|
|
22
|
+
* @returns {BindingWithInContract<T>} Allows chaining `.inSingletonScope()`.
|
|
23
|
+
*/
|
|
24
|
+
to(constructor: Newable<T>): BindingWithInContract<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Bind the identifier to an existing instance.
|
|
27
|
+
* Marks this binding as a singleton.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const db = new SQLiteDatabase();
|
|
32
|
+
* container.bind(IDatabase).toInstance(db);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @param instance The instance to use when resolving this identifier.
|
|
36
|
+
*/
|
|
37
|
+
toInstance(instance: T): void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BindingWithInContractImpl } from '../runtime/binding-with-in-contract-impl.js';
|
|
2
|
+
/**
|
|
3
|
+
* Contract returned by `IoCContainer.bind`.
|
|
4
|
+
*
|
|
5
|
+
* Provides methods to bind an abstract identifier (interface, symbol, etc.)
|
|
6
|
+
* to a concrete implementation or to an existing instance.
|
|
7
|
+
*/
|
|
8
|
+
export class BindingToContractImpl {
|
|
9
|
+
constructor(binding) {
|
|
10
|
+
this.binding = binding;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Bind the identifier to a class constructor.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* container.bind(IDatabase).to(SQLiteDatabase);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @param constructor The class to instantiate when resolving this identifier.
|
|
21
|
+
* @returns {BindingWithInContract<T>} Allows chaining `.inSingletonScope()`.
|
|
22
|
+
*/
|
|
23
|
+
to(constructor) {
|
|
24
|
+
this.binding.boundEntity = constructor;
|
|
25
|
+
return new BindingWithInContractImpl(this.binding);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Bind the identifier to an existing instance.
|
|
29
|
+
* Marks this binding as a singleton.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const db = new SQLiteDatabase();
|
|
34
|
+
* container.bind(IDatabase).toInstance(db);
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param instance The instance to use when resolving this identifier.
|
|
38
|
+
*/
|
|
39
|
+
toInstance(instance) {
|
|
40
|
+
this.binding.boundEntity = instance;
|
|
41
|
+
this.binding.cache = instance;
|
|
42
|
+
this.binding.scope = 'Singleton';
|
|
43
|
+
this.binding.activated = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=binding-to-contract-impl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-to-contract-impl.js","sourceRoot":"","sources":["../../src/runtime/binding-to-contract-impl.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AAEzF;;;;;GAKG;AACH,MAAM,OAAO,qBAAqB;IAGhC,YAAY,OAAmB;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;;;;;OAUG;IACH,EAAE,CAAC,WAAuB;QACxB,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;QACvC,OAAO,IAAI,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,QAAW;QACpB,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,QAAQ,CAAC;QACpC,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,WAAW,CAAC;QACjC,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Identifier } from '../api/identifier.js';
|
|
2
|
+
import type { Binding } from '../runtime/binding.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fluent contract returned from IoCContainer.put() when configuring dependencies.
|
|
5
|
+
*
|
|
6
|
+
* Allows you to declare dependencies as a key-to-identifier map.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* ioc.put(UserController).with({
|
|
11
|
+
* userService: UserService,
|
|
12
|
+
* logger: Logger,
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare class BindingWithContract<T> {
|
|
17
|
+
private binding;
|
|
18
|
+
constructor(binding: Binding<T>);
|
|
19
|
+
/**
|
|
20
|
+
* Declare dependencies for this binding as a map of identifiers.
|
|
21
|
+
* Each entry will be resolved from the container and injected into
|
|
22
|
+
* the constructor as an object.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* ioc.put(UserController).with({
|
|
27
|
+
* userService: UserService,
|
|
28
|
+
* logger: Logger,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
with(deps: Record<string, Identifier>): this;
|
|
33
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent contract returned from IoCContainer.put() when configuring dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Allows you to declare dependencies as a key-to-identifier map.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* ioc.put(UserController).with({
|
|
9
|
+
* userService: UserService,
|
|
10
|
+
* logger: Logger,
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class BindingWithContract {
|
|
15
|
+
constructor(binding) {
|
|
16
|
+
this.binding = binding;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Declare dependencies for this binding as a map of identifiers.
|
|
20
|
+
* Each entry will be resolved from the container and injected into
|
|
21
|
+
* the constructor as an object.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* ioc.put(UserController).with({
|
|
26
|
+
* userService: UserService,
|
|
27
|
+
* logger: Logger,
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
with(deps) {
|
|
32
|
+
this.binding.depsMap = deps;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=binding-with-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-with-contract.js","sourceRoot":"","sources":["../../src/runtime/binding-with-contract.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,mBAAmB;IAG9B,YAAY,OAAmB;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,IAAgC;QACnC,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BindingWithInContract } from '../api/container.js';
|
|
2
|
+
import type { Identifier } from '../api/identifier.js';
|
|
3
|
+
import type { Binding } from '../runtime/binding.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fluent contract returned from IoCContainer.put().
|
|
6
|
+
*
|
|
7
|
+
* Allows you to configure additional options for the binding:
|
|
8
|
+
*
|
|
9
|
+
* - declare dependencies with .with({ key: Identifier })
|
|
10
|
+
* - control scope with .inSingletonScope()
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* ioc.put(UserController)
|
|
15
|
+
* .with({ userService: UserService, logger: Logger })
|
|
16
|
+
* .inSingletonScope();
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class BindingWithInContractImpl<T> implements BindingWithInContract<T> {
|
|
20
|
+
private bindingInContract;
|
|
21
|
+
private bindingWithContract;
|
|
22
|
+
constructor(binding: Binding<T>);
|
|
23
|
+
/**
|
|
24
|
+
* Make this binding a singleton — only one instance will be created
|
|
25
|
+
* and cached in the container.
|
|
26
|
+
*/
|
|
27
|
+
inSingletonScope(): this;
|
|
28
|
+
/**
|
|
29
|
+
* Declare dependencies for this binding as a map of identifiers.
|
|
30
|
+
* Each entry will be resolved from the container and injected into
|
|
31
|
+
* the constructor as an object.
|
|
32
|
+
*/
|
|
33
|
+
with(deps: Record<string, Identifier>): this;
|
|
34
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BindingWithContract } from '../runtime/binding-with-contract.js';
|
|
2
|
+
import { BindingInContract } from '../runtime/binding-in-contract.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fluent contract returned from IoCContainer.put().
|
|
5
|
+
*
|
|
6
|
+
* Allows you to configure additional options for the binding:
|
|
7
|
+
*
|
|
8
|
+
* - declare dependencies with .with({ key: Identifier })
|
|
9
|
+
* - control scope with .inSingletonScope()
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* ioc.put(UserController)
|
|
14
|
+
* .with({ userService: UserService, logger: Logger })
|
|
15
|
+
* .inSingletonScope();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class BindingWithInContractImpl {
|
|
19
|
+
constructor(binding) {
|
|
20
|
+
this.bindingInContract = new BindingInContract(binding);
|
|
21
|
+
this.bindingWithContract = new BindingWithContract(binding);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Make this binding a singleton — only one instance will be created
|
|
25
|
+
* and cached in the container.
|
|
26
|
+
*/
|
|
27
|
+
inSingletonScope() {
|
|
28
|
+
this.bindingInContract.inSingletonScope();
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Declare dependencies for this binding as a map of identifiers.
|
|
33
|
+
* Each entry will be resolved from the container and injected into
|
|
34
|
+
* the constructor as an object.
|
|
35
|
+
*/
|
|
36
|
+
with(deps) {
|
|
37
|
+
this.bindingWithContract.with(deps);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=binding-with-in-contract-impl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-with-in-contract-impl.js","sourceRoot":"","sources":["../../src/runtime/binding-with-in-contract-impl.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,yBAAyB;IAIpC,YAAY,OAAmB;QAC7B,IAAI,CAAC,iBAAiB,GAAG,IAAI,iBAAiB,CAAI,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,mBAAmB,GAAG,IAAI,mBAAmB,CAAI,OAAO,CAAC,CAAC;IACjE,CAAC;IAED;;;OAGG;IACH,gBAAgB;QACd,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,IAAgC;QACnC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Newable } from '@kurdel/common';
|
|
2
|
+
import type { Identifier } from '../api/identifier.js';
|
|
3
|
+
import type { ScopeType } from '../api/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Binding
|
|
6
|
+
*
|
|
7
|
+
* Stores information about how a dependency is resolved:
|
|
8
|
+
* - boundEntity → class or instance
|
|
9
|
+
* - depsMap → dependencies for constructor injection
|
|
10
|
+
* - toFactory → custom factory function
|
|
11
|
+
* - scope → lifecycle (Transient or Singleton)
|
|
12
|
+
* - cache → cached instance (for Singleton)
|
|
13
|
+
*/
|
|
14
|
+
export declare class Binding<T> {
|
|
15
|
+
boundEntity: Newable<T> | T | null;
|
|
16
|
+
depsMap?: Record<string, Identifier>;
|
|
17
|
+
toFactory?: () => T;
|
|
18
|
+
scope: ScopeType;
|
|
19
|
+
cache: T | null;
|
|
20
|
+
activated: boolean;
|
|
21
|
+
constructor();
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding
|
|
3
|
+
*
|
|
4
|
+
* Stores information about how a dependency is resolved:
|
|
5
|
+
* - boundEntity → class or instance
|
|
6
|
+
* - depsMap → dependencies for constructor injection
|
|
7
|
+
* - toFactory → custom factory function
|
|
8
|
+
* - scope → lifecycle (Transient or Singleton)
|
|
9
|
+
* - cache → cached instance (for Singleton)
|
|
10
|
+
*/
|
|
11
|
+
export class Binding {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.boundEntity = null;
|
|
14
|
+
this.scope = 'Transient';
|
|
15
|
+
this.cache = null;
|
|
16
|
+
this.activated = false;
|
|
17
|
+
this.depsMap = undefined;
|
|
18
|
+
this.toFactory = undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/runtime/binding.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,MAAM,OAAO,OAAO;IAQlB;QACE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Newable } from '@kurdel/common';
|
|
2
|
+
import type { Identifier } from '../api/identifier.js';
|
|
3
|
+
import type { Container, BindingToContract, BindingWithInContract } from '../api/container.js';
|
|
4
|
+
interface DependencyNode {
|
|
5
|
+
key: string;
|
|
6
|
+
fromParent?: boolean;
|
|
7
|
+
deps: DependencyNode[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Simple Inversion of Control (IoC) container.
|
|
11
|
+
*
|
|
12
|
+
* Supports two registration styles:
|
|
13
|
+
* - `bind` for interfaces or symbols → bind an identifier to implementation
|
|
14
|
+
* - `put` for concrete classes → register classes with dependencies
|
|
15
|
+
*
|
|
16
|
+
* Provides dependency resolution with support for constructor injection
|
|
17
|
+
* and singleton scope.
|
|
18
|
+
*/
|
|
19
|
+
export declare class IoCContainer implements Container {
|
|
20
|
+
private readonly dictionary;
|
|
21
|
+
private readonly parent?;
|
|
22
|
+
constructor(parent?: IoCContainer);
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new **request-scoped** child container.
|
|
25
|
+
*
|
|
26
|
+
* The child delegates lookups to this container (its parent) when a binding
|
|
27
|
+
* is not found locally. Singleton bindings registered in the parent remain
|
|
28
|
+
* shared; bindings added to the child are isolated to the child’s lifetime.
|
|
29
|
+
*
|
|
30
|
+
* @returns A new `IoCContainer` whose parent is this container.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const root = new IoCContainer();
|
|
34
|
+
* const scope = root.createScope(); // per-request container
|
|
35
|
+
* // scope.get(...) will fall back to root if not found locally
|
|
36
|
+
*/
|
|
37
|
+
createScope(): IoCContainer;
|
|
38
|
+
/**
|
|
39
|
+
* Bind an identifier (interface or symbol) to an implementation.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* container.bind<IDatabase>(IDatabase).to(SQLiteDatabase);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
bind<T>(key: Identifier<T>): BindingToContract<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Register a concrete class in the container.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* container.put(UserService);
|
|
53
|
+
* container.put(UserController).with({ userService: UserService });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
put<T>(constructor: Newable<T>): BindingWithInContract<T>;
|
|
57
|
+
/** @inheritdoc */
|
|
58
|
+
toFactory<T>(key: Identifier<T>, factory: () => T): void;
|
|
59
|
+
/** @inheritdoc */
|
|
60
|
+
set<T>(key: Identifier<T>, value: T): void;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve an instance bound to the given identifier.
|
|
63
|
+
*
|
|
64
|
+
* Resolution rules:
|
|
65
|
+
* 1) If the binding is not present locally, delegate to the parent container.
|
|
66
|
+
* 2) If the binding has a `toFactory`, invoke it (respecting singleton scope).
|
|
67
|
+
* 3) If the binding has a concrete `boundEntity`, recursively resolve its deps
|
|
68
|
+
* and instantiate it; cache singletons.
|
|
69
|
+
*
|
|
70
|
+
* @typeParam T - Resolved instance type.
|
|
71
|
+
* @param key - Identifier (token/class) to resolve.
|
|
72
|
+
* @returns The resolved instance of type `T`.
|
|
73
|
+
* @throws If no binding was found in this container hierarchy.
|
|
74
|
+
*/
|
|
75
|
+
get<T>(key: Identifier<T>): T;
|
|
76
|
+
/**
|
|
77
|
+
* Check whether a binding exists for the given identifier **in this container**.
|
|
78
|
+
*
|
|
79
|
+
* Note: this implementation does not consult a parent container.
|
|
80
|
+
* If you use hierarchical scoping, prefer a version that also checks `parent.has(key)`
|
|
81
|
+
* to mirror `get()` fallback behavior.
|
|
82
|
+
*
|
|
83
|
+
* @param key - Identifier (token/class) to look up.
|
|
84
|
+
* @returns `true` if the identifier is bound in this container.
|
|
85
|
+
*/
|
|
86
|
+
has(key: Identifier): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Build a dependency graph for debugging and visualization.
|
|
89
|
+
*
|
|
90
|
+
* Traverses constructor and factory bindings, following `depsMap`
|
|
91
|
+
* recursively across parent containers.
|
|
92
|
+
*
|
|
93
|
+
* @param rootKey - Optional starting identifier (defaults to all local bindings).
|
|
94
|
+
* @returns Dependency tree(s) describing how bindings reference each other.
|
|
95
|
+
*/
|
|
96
|
+
getGraph(rootKey?: Identifier): DependencyNode[];
|
|
97
|
+
/** @inheritdoc */
|
|
98
|
+
printGraph(rootKey?: Identifier): void;
|
|
99
|
+
/** Returns a human-readable label for an identifier (for diagnostics). */
|
|
100
|
+
private keyLabel;
|
|
101
|
+
}
|
|
102
|
+
export {};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Binding } from '../runtime/binding.js';
|
|
2
|
+
import { BindingToContractImpl } from '../runtime/binding-to-contract-impl.js';
|
|
3
|
+
import { BindingWithInContractImpl } from '../runtime/binding-with-in-contract-impl.js';
|
|
4
|
+
/**
|
|
5
|
+
* Simple Inversion of Control (IoC) container.
|
|
6
|
+
*
|
|
7
|
+
* Supports two registration styles:
|
|
8
|
+
* - `bind` for interfaces or symbols → bind an identifier to implementation
|
|
9
|
+
* - `put` for concrete classes → register classes with dependencies
|
|
10
|
+
*
|
|
11
|
+
* Provides dependency resolution with support for constructor injection
|
|
12
|
+
* and singleton scope.
|
|
13
|
+
*/
|
|
14
|
+
export class IoCContainer {
|
|
15
|
+
constructor(parent) {
|
|
16
|
+
this.dictionary = new Map();
|
|
17
|
+
this.parent = parent;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new **request-scoped** child container.
|
|
21
|
+
*
|
|
22
|
+
* The child delegates lookups to this container (its parent) when a binding
|
|
23
|
+
* is not found locally. Singleton bindings registered in the parent remain
|
|
24
|
+
* shared; bindings added to the child are isolated to the child’s lifetime.
|
|
25
|
+
*
|
|
26
|
+
* @returns A new `IoCContainer` whose parent is this container.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const root = new IoCContainer();
|
|
30
|
+
* const scope = root.createScope(); // per-request container
|
|
31
|
+
* // scope.get(...) will fall back to root if not found locally
|
|
32
|
+
*/
|
|
33
|
+
createScope() {
|
|
34
|
+
return new IoCContainer(this);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Bind an identifier (interface or symbol) to an implementation.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* container.bind<IDatabase>(IDatabase).to(SQLiteDatabase);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
bind(key) {
|
|
45
|
+
const binding = new Binding();
|
|
46
|
+
if (this.dictionary.has(key)) {
|
|
47
|
+
throw new Error(`Dependency ${key.toString()} already registered.`);
|
|
48
|
+
}
|
|
49
|
+
this.dictionary.set(key, binding);
|
|
50
|
+
return new BindingToContractImpl(binding);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a concrete class in the container.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* container.put(UserService);
|
|
58
|
+
* container.put(UserController).with({ userService: UserService });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
put(constructor) {
|
|
62
|
+
if (this.dictionary.has(constructor)) {
|
|
63
|
+
throw new Error(`Dependency ${constructor.name.toString()} already registered.`);
|
|
64
|
+
}
|
|
65
|
+
const binding = new Binding();
|
|
66
|
+
binding.boundEntity = constructor;
|
|
67
|
+
this.dictionary.set(constructor, binding);
|
|
68
|
+
return new BindingWithInContractImpl(binding);
|
|
69
|
+
}
|
|
70
|
+
/** @inheritdoc */
|
|
71
|
+
toFactory(key, factory) {
|
|
72
|
+
const binding = new Binding();
|
|
73
|
+
binding.toFactory = factory;
|
|
74
|
+
this.dictionary.set(key, binding);
|
|
75
|
+
}
|
|
76
|
+
/** @inheritdoc */
|
|
77
|
+
set(key, value) {
|
|
78
|
+
if (this.dictionary.has(key)) {
|
|
79
|
+
throw new Error(`Dependency ${String(key)} already registered.`);
|
|
80
|
+
}
|
|
81
|
+
const b = new Binding();
|
|
82
|
+
b.boundEntity = value;
|
|
83
|
+
b.scope = 'Singleton';
|
|
84
|
+
b.activated = true;
|
|
85
|
+
b.cache = value;
|
|
86
|
+
this.dictionary.set(key, b);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve an instance bound to the given identifier.
|
|
90
|
+
*
|
|
91
|
+
* Resolution rules:
|
|
92
|
+
* 1) If the binding is not present locally, delegate to the parent container.
|
|
93
|
+
* 2) If the binding has a `toFactory`, invoke it (respecting singleton scope).
|
|
94
|
+
* 3) If the binding has a concrete `boundEntity`, recursively resolve its deps
|
|
95
|
+
* and instantiate it; cache singletons.
|
|
96
|
+
*
|
|
97
|
+
* @typeParam T - Resolved instance type.
|
|
98
|
+
* @param key - Identifier (token/class) to resolve.
|
|
99
|
+
* @returns The resolved instance of type `T`.
|
|
100
|
+
* @throws If no binding was found in this container hierarchy.
|
|
101
|
+
*/
|
|
102
|
+
get(key) {
|
|
103
|
+
const local = this.dictionary.get(key);
|
|
104
|
+
if (!local) {
|
|
105
|
+
if (this.parent)
|
|
106
|
+
return this.parent.get(key);
|
|
107
|
+
throw new Error(`No dependency found for ${String(key)}`);
|
|
108
|
+
}
|
|
109
|
+
// factory binding
|
|
110
|
+
if (local.toFactory) {
|
|
111
|
+
if (local.scope === 'Singleton') {
|
|
112
|
+
if (!local.activated) {
|
|
113
|
+
local.cache = local.toFactory();
|
|
114
|
+
local.activated = true;
|
|
115
|
+
}
|
|
116
|
+
return local.cache;
|
|
117
|
+
}
|
|
118
|
+
return local.toFactory();
|
|
119
|
+
}
|
|
120
|
+
if (!local.boundEntity) {
|
|
121
|
+
throw new Error(`No dependency found for ${String(key)}`);
|
|
122
|
+
}
|
|
123
|
+
const { boundEntity, depsMap } = local;
|
|
124
|
+
// value binding
|
|
125
|
+
if (typeof boundEntity !== 'function') {
|
|
126
|
+
return boundEntity;
|
|
127
|
+
}
|
|
128
|
+
const Ctor = boundEntity;
|
|
129
|
+
const resolvedDeps = depsMap
|
|
130
|
+
? Object.fromEntries(Object.entries(depsMap).map(([k, dep]) => [k, this.get(dep)]))
|
|
131
|
+
: {};
|
|
132
|
+
if (local.scope === 'Singleton') {
|
|
133
|
+
if (!local.activated) {
|
|
134
|
+
local.cache = new Ctor(resolvedDeps);
|
|
135
|
+
local.activated = true;
|
|
136
|
+
}
|
|
137
|
+
return local.cache;
|
|
138
|
+
}
|
|
139
|
+
return new Ctor(resolvedDeps);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Check whether a binding exists for the given identifier **in this container**.
|
|
143
|
+
*
|
|
144
|
+
* Note: this implementation does not consult a parent container.
|
|
145
|
+
* If you use hierarchical scoping, prefer a version that also checks `parent.has(key)`
|
|
146
|
+
* to mirror `get()` fallback behavior.
|
|
147
|
+
*
|
|
148
|
+
* @param key - Identifier (token/class) to look up.
|
|
149
|
+
* @returns `true` if the identifier is bound in this container.
|
|
150
|
+
*/
|
|
151
|
+
has(key) {
|
|
152
|
+
return this.dictionary.has(key);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Build a dependency graph for debugging and visualization.
|
|
156
|
+
*
|
|
157
|
+
* Traverses constructor and factory bindings, following `depsMap`
|
|
158
|
+
* recursively across parent containers.
|
|
159
|
+
*
|
|
160
|
+
* @param rootKey - Optional starting identifier (defaults to all local bindings).
|
|
161
|
+
* @returns Dependency tree(s) describing how bindings reference each other.
|
|
162
|
+
*/
|
|
163
|
+
getGraph(rootKey) {
|
|
164
|
+
const roots = rootKey ? [rootKey] : Array.from(this.dictionary.keys());
|
|
165
|
+
const walk = (key, path = new Set(), fromParent = false) => {
|
|
166
|
+
if (path.has(key)) {
|
|
167
|
+
return { key: this.keyLabel(key) + ' (circular)', deps: [] };
|
|
168
|
+
}
|
|
169
|
+
const binding = this.dictionary.get(key) ?? this.parent?.dictionary.get(key);
|
|
170
|
+
if (!binding) {
|
|
171
|
+
return { key: this.keyLabel(key) + ' (unbound)', deps: [] };
|
|
172
|
+
}
|
|
173
|
+
const isFromParent = fromParent || !this.dictionary.has(key);
|
|
174
|
+
const deps = binding.depsMap ? Object.values(binding.depsMap) : [];
|
|
175
|
+
const newPath = new Set(path);
|
|
176
|
+
newPath.add(key);
|
|
177
|
+
const depsNodes = deps.map(dep => walk(dep, newPath, isFromParent));
|
|
178
|
+
const labelParts = [this.keyLabel(key)];
|
|
179
|
+
if (isFromParent)
|
|
180
|
+
labelParts.push('[parent]');
|
|
181
|
+
if (binding.toFactory)
|
|
182
|
+
labelParts.push('[factory]');
|
|
183
|
+
if (binding.scope === 'Singleton')
|
|
184
|
+
labelParts.push('[singleton]');
|
|
185
|
+
if (binding.boundEntity && typeof binding.boundEntity !== 'function')
|
|
186
|
+
labelParts.push('[instance]');
|
|
187
|
+
return {
|
|
188
|
+
key: labelParts.join(' '),
|
|
189
|
+
fromParent: isFromParent,
|
|
190
|
+
deps: depsNodes,
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
return roots.map(k => walk(k));
|
|
194
|
+
}
|
|
195
|
+
/** @inheritdoc */
|
|
196
|
+
printGraph(rootKey) {
|
|
197
|
+
const graph = this.getGraph(rootKey);
|
|
198
|
+
const render = (node, prefix = '') => {
|
|
199
|
+
const line = `${prefix}- ${node.key}\n`;
|
|
200
|
+
const nextPrefix = prefix + ' ';
|
|
201
|
+
return line + node.deps.map(d => render(d, nextPrefix)).join('');
|
|
202
|
+
};
|
|
203
|
+
for (const root of graph) {
|
|
204
|
+
console.log(render(root));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Returns a human-readable label for an identifier (for diagnostics). */
|
|
208
|
+
keyLabel(key) {
|
|
209
|
+
if (typeof key === 'string')
|
|
210
|
+
return key;
|
|
211
|
+
if (typeof key === 'symbol')
|
|
212
|
+
return key.description ?? String(key);
|
|
213
|
+
if (typeof key === 'function')
|
|
214
|
+
return key.name || '[AnonymousClass]';
|
|
215
|
+
return String(key);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=ioc-container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ioc-container.js","sourceRoot":"","sources":["../../src/runtime/ioc-container.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kurdel/ioc",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Dependency injection container for Kurdel",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./lib/index.js",
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"lib"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepack": "npm run build",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:ui": "vitest",
|
|
18
|
+
"test:watch": "vitest --watch",
|
|
19
|
+
"coverage": "vitest run --coverage",
|
|
20
|
+
"clean": "rimraf lib ../../.cache/tsconfig.ioc.build.tsbuildinfo",
|
|
21
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
22
|
+
"build:force": "npm run clean && tsc -p tsconfig.build.json --force && tsc-alias -p tsconfig.build.json"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"kurdel",
|
|
26
|
+
"dependency-injection",
|
|
27
|
+
"ioc",
|
|
28
|
+
"typescript"
|
|
29
|
+
],
|
|
30
|
+
"author": "Andrii Sorokin",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/ignorantic/kurdel.git",
|
|
38
|
+
"directory": "packages/ioc"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/ignorantic/kurdel/tree/main/packages/ioc#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/ignorantic/kurdel/issues"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"tag": "beta"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.9.0",
|
|
50
|
+
"rimraf": "^6.0.1",
|
|
51
|
+
"tsc-alias": "^1.8.16"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@kurdel/common": "0.1.0-beta.1"
|
|
55
|
+
}
|
|
56
|
+
}
|