@resolid/di 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -55
- package/dist/index.d.ts +32 -29
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Resolid: DI Container Package
|
|
2
2
|
|
|
3
|
-

|
|
4
|
+

|
|
4
5
|
|
|
5
6
|
<b>[Documentation](https://www.resolid.tech/docs/di)</b> | [Framework Bundle](https://github.com/resolid/framework)
|
|
6
7
|
|
|
@@ -31,87 +32,125 @@ yarn add @resolid/di
|
|
|
31
32
|
bun add @resolid/di
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
### Usage
|
|
35
|
-
|
|
36
|
-
#### Creating a container
|
|
35
|
+
### Basic Usage
|
|
37
36
|
|
|
38
37
|
```js
|
|
39
38
|
import { createContainer } from "@resolid/di";
|
|
40
39
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
const VALUE = Symbol("VALUE");
|
|
41
|
+
const FUNCTION = Symbol("FUNCTION");
|
|
42
|
+
const FACTORY = Symbol("FACTORY");
|
|
43
|
+
|
|
44
|
+
const container = createContainer([
|
|
45
|
+
{ name: VALUE, value: "Hello World" },
|
|
46
|
+
{
|
|
47
|
+
name: FUNCTION,
|
|
48
|
+
callable: () => {
|
|
49
|
+
return "Hello World from callable";
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: FACTORY,
|
|
54
|
+
factory: async ({ resolver, config }) => {
|
|
55
|
+
const value = await resolver.resolve(VALUE);
|
|
56
|
+
|
|
57
|
+
return value + " " + config.message || "from factory!";
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Scope of a binding.
|
|
61
|
+
* singleton (default): Only one instance is created and shared for all resolves.
|
|
62
|
+
* transient: A new instance is created on each resolve.
|
|
63
|
+
*/
|
|
64
|
+
scope: "singleton",
|
|
65
|
+
/**
|
|
66
|
+
* Optional configuration object passed to a factory.
|
|
67
|
+
* Type is inferred from the factory definition.
|
|
68
|
+
* Used to provide parameters for instance creation.
|
|
69
|
+
*/
|
|
70
|
+
config: { message: " from factory with config" },
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
49
73
|
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
const valueResult = await container.resolve(VALUE);
|
|
75
|
+
console.log(valueResult); // Output: "Hello World"
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
const callable = await container.resolve(FUNCTION);
|
|
78
|
+
const callableResult = callable();
|
|
79
|
+
console.log(callableResult); // Output: "Hello World from callable"
|
|
54
80
|
|
|
55
|
-
const
|
|
81
|
+
const factoryResult = await container.resolve(FACTORY);
|
|
82
|
+
console.log(factoryResult); // Output: "Hello World from factory with config"
|
|
56
83
|
```
|
|
57
84
|
|
|
58
|
-
|
|
85
|
+
### Lazy Resolve
|
|
59
86
|
|
|
60
|
-
|
|
61
|
-
const FUNC_TOKEN = Symbol("func");
|
|
87
|
+
You can create bindings that are resolved lazily, useful for circular dependencies or heavy computations:
|
|
62
88
|
|
|
63
|
-
|
|
89
|
+
```js
|
|
90
|
+
const LAZY_A = Symbol("LAZY_A");
|
|
91
|
+
const LAZY_B = Symbol("LAZY_B");
|
|
92
|
+
|
|
93
|
+
const container = createContainer([
|
|
94
|
+
{
|
|
95
|
+
name: LAZY_A,
|
|
96
|
+
factory: ({ resolver }) => {
|
|
97
|
+
const b = resolver.lazyResolve(LAZY_B);
|
|
98
|
+
return `A depends on ${b.value}`;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: LAZY_B,
|
|
103
|
+
factory: ({ resolver }) => {
|
|
104
|
+
const a = resolver.lazyResolve(LAZY_A);
|
|
105
|
+
return `B depends on ${a.value}`;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
64
109
|
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
// Accessing value after construction is safe
|
|
111
|
+
await container.resolve(LAZY_A);
|
|
112
|
+
await container.resolve(LAZY_B);
|
|
67
113
|
```
|
|
68
114
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Bind a factory, can with bellow options:
|
|
72
|
-
|
|
73
|
-
- scope
|
|
74
|
-
- singleton (default): One instance per container.
|
|
75
|
-
- transient: A new instance each time resolved.
|
|
115
|
+
> Note: Access `lazyResolve.value` **only after all bindings are constructed**, otherwise it will throw an error.
|
|
76
116
|
|
|
77
|
-
|
|
117
|
+
### Circular Dependency Detection
|
|
78
118
|
|
|
79
119
|
```js
|
|
80
|
-
const
|
|
120
|
+
const A = Symbol("A");
|
|
121
|
+
const B = Symbol("B");
|
|
81
122
|
|
|
82
|
-
container
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
container.bind(FACTORY_TOKEN).toFactory(
|
|
87
|
-
({ resolver, config }) => {
|
|
88
|
-
const dep = resolver.resolve(TOKEN);
|
|
89
|
-
return `Factory resolved: ${dep.message} ${config.message}`;
|
|
123
|
+
const container = createContainer([
|
|
124
|
+
{
|
|
125
|
+
name: A,
|
|
126
|
+
factory: ({ resolver }) => resolver.resolve(B),
|
|
90
127
|
},
|
|
91
|
-
{
|
|
92
|
-
|
|
128
|
+
{
|
|
129
|
+
name: B,
|
|
130
|
+
factory: ({ resolver }) => resolver.resolve(A),
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
93
133
|
|
|
94
|
-
|
|
134
|
+
await container.resolve(A);
|
|
135
|
+
// Throws Error: Circular dependency detected: A -> B -> A
|
|
95
136
|
```
|
|
96
137
|
|
|
97
|
-
|
|
138
|
+
### Dispose
|
|
98
139
|
|
|
99
140
|
```js
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
class Resource {
|
|
142
|
+
async dispose() {
|
|
143
|
+
console.log("Resource disposed");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
104
146
|
|
|
105
|
-
|
|
106
|
-
const lazy = resolver.lazyResolve(LAZY_DEP);
|
|
147
|
+
const RESOURCE = Symbol("RESOURCE");
|
|
107
148
|
|
|
108
|
-
|
|
109
|
-
getLazyData: () => lazy.value.data,
|
|
110
|
-
};
|
|
111
|
-
});
|
|
149
|
+
const container = createContainer([{ name: RESOURCE, value: new Resource() }]);
|
|
112
150
|
|
|
113
|
-
|
|
114
|
-
|
|
151
|
+
// Dispose all singleton instances
|
|
152
|
+
await container.dispose();
|
|
153
|
+
// Output: "Resource disposed"
|
|
115
154
|
```
|
|
116
155
|
|
|
117
156
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
//#region src/types/index.d.ts
|
|
2
|
-
type Scope = "singleton" | "transient";
|
|
3
|
-
type BindingConfig = Record<string, unknown>;
|
|
4
|
-
//#endregion
|
|
5
1
|
//#region src/container/index.d.ts
|
|
6
|
-
type Resolve = (name: symbol) =>
|
|
2
|
+
type Resolve = <T>(name: symbol) => Promise<T>;
|
|
7
3
|
type LazyResolve = <T = unknown>(name: symbol) => Readonly<{
|
|
8
4
|
value: T;
|
|
9
5
|
}>;
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
type Scope = "singleton" | "transient";
|
|
7
|
+
type BindingDefinition<Config extends Record<string, unknown> = Record<string, unknown>> = {
|
|
8
|
+
name: symbol;
|
|
9
|
+
value: unknown;
|
|
10
|
+
callable?: never;
|
|
11
|
+
factory?: never;
|
|
12
|
+
scope?: never;
|
|
13
|
+
config?: never;
|
|
14
|
+
} | {
|
|
15
|
+
name: symbol;
|
|
16
|
+
callable: (...args: unknown[]) => unknown;
|
|
17
|
+
value?: never;
|
|
18
|
+
factory?: never;
|
|
19
|
+
scope?: never;
|
|
20
|
+
config?: never;
|
|
21
|
+
} | {
|
|
22
|
+
name: symbol;
|
|
23
|
+
factory: (options: {
|
|
24
|
+
resolver: {
|
|
25
|
+
resolve: Resolve;
|
|
26
|
+
lazyResolve: LazyResolve;
|
|
27
|
+
};
|
|
28
|
+
config?: Config;
|
|
29
|
+
}) => unknown | Promise<unknown>;
|
|
23
30
|
scope?: Scope;
|
|
24
|
-
config?:
|
|
25
|
-
|
|
31
|
+
config?: Config;
|
|
32
|
+
value?: never;
|
|
33
|
+
callable?: never;
|
|
34
|
+
};
|
|
26
35
|
type Disposable = {
|
|
27
36
|
dispose: () => Promise<void> | void;
|
|
28
37
|
};
|
|
29
38
|
type Container = {
|
|
30
|
-
|
|
31
|
-
toValue: ToValue;
|
|
32
|
-
toFunction: ToFunction;
|
|
33
|
-
toFactory: ToFactory;
|
|
34
|
-
};
|
|
35
|
-
resolve: <T>(name: symbol) => Promise<T>;
|
|
36
|
-
lazyResolve: LazyResolve;
|
|
39
|
+
resolve: Resolve;
|
|
37
40
|
} & Disposable;
|
|
38
|
-
declare const createContainer: () => Container;
|
|
41
|
+
declare const createContainer: (definitions: readonly BindingDefinition[]) => Container;
|
|
39
42
|
//#endregion
|
|
40
|
-
export {
|
|
43
|
+
export { BindingDefinition, Container, Scope, createContainer };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=
|
|
1
|
+
const e=e=>{let n=new t;return n.register(e),{resolve:e=>n.resolve(e),dispose:()=>n.dispose()}};var t=class e{#bindings;#singletons;#constructing;#lazyResolveQueue=[];constructor(e,t,n){this.#bindings=e?.getBindings()??new Map,this.#singletons=e?.getSingletons()??new Map,this.#constructing=t?[...n??e?.getConstructing()??[],t]:[]}getBindings(){return this.#bindings}getSingletons(){return this.#singletons}getConstructing(){return this.#constructing}register(e){for(let t of e)t.callable?this.#bindings.set(t.name,{factory:()=>t.callable,scope:`singleton`}):t.factory?this.#bindings.set(t.name,{factory:(e,n)=>t.factory({resolver:{resolve:e,lazyResolve:n},config:t.config}),scope:t.scope??`singleton`,config:t.config}):this.#bindings.set(t.name,{factory:()=>t.value,scope:`singleton`})}async#resolveBinding(t,n){let r=this.#bindings.get(t);if(!r)throw Error(`No binding found for ${t.description??t.toString()}.`);let i=r.scope===`singleton`;if(i&&this.#singletons.has(t))return this.#singletons.get(t);let a=new e(this,t,n),o=await r.factory(e=>a.resolve(e),e=>a.lazyResolve(e));return i&&this.#singletons.set(t,o),await a.dequeueLazyResolves(),o}async dequeueLazyResolves(){for(let e of this.#lazyResolveQueue)try{e.resolve(await this.#resolveBinding(e.name,[]))}catch(t){throw Error(`Failed to resolve lazy binding ${e.name.description??e.name.toString()}: ${t instanceof Error?t.message:String(t)}`)}}async resolve(e){if(this.#constructing.includes(e))throw Error(`Circular dependency detected: ${[...this.#constructing,e].map(e=>e.description??e.toString()).join(` -> `)}`);return this.#resolveBinding(e)}lazyResolve(e){let t;return new Promise(t=>{this.#lazyResolveQueue.push({name:e,resolve:e=>{t(e)}})}).then(e=>{t=e}),{get value(){if(!t)throw Error(`Lazy binding is not yet resolved. Avoid accessing it before container construction finishes.`);return t}}}async dispose(){let e=[];for(let[t,n]of this.#singletons)if(typeof n.dispose==`function`)try{await n.dispose()}catch(n){e.push({name:t,error:n})}if(this.#singletons.clear(),e.length>0){let t=e.map(({name:e,error:t})=>`${e.description??e.toString()}: ${t instanceof Error?t.message:String(t)}`).join(`; `);throw Error(`Failed to dispose ${e.length} binding(s):\n${t}`)}}};export{e as createContainer};
|