@resolid/di 0.2.2 → 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 CHANGED
@@ -1,6 +1,7 @@
1
1
  # Resolid: DI Container Package
2
2
 
3
- ![GitHub License](https://badgen.net/github/license/resolid/framework) ![NPM Version](https://badgen.net/npm/v/@resolid/di)
3
+ ![GitHub License](https://img.shields.io/github/license/resolid/framework)
4
+ ![NPM Version](https://img.shields.io/npm/v/%40resolid/di)
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 container = createContainer();
42
- ```
43
-
44
- #### Binding dependencies
45
-
46
- You can bind values, functions, or factories:
47
-
48
- 1. Bind a value
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
- ```js
51
- const TOKEN = Symbol("token");
74
+ const valueResult = await container.resolve(VALUE);
75
+ console.log(valueResult); // Output: "Hello World"
52
76
 
53
- container.bind(TOKEN).toValue({ message: "Hello World" });
77
+ const callable = await container.resolve(FUNCTION);
78
+ const callableResult = callable();
79
+ console.log(callableResult); // Output: "Hello World from callable"
54
80
 
55
- const result = await container.resolve(TOKEN); // result = "Hello World"
81
+ const factoryResult = await container.resolve(FACTORY);
82
+ console.log(factoryResult); // Output: "Hello World from factory with config"
56
83
  ```
57
84
 
58
- 2. Bind a function
85
+ ### Lazy Resolve
59
86
 
60
- ```js
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
- container.bind(FUNC_TOKEN).toFunction(() => console.log("Hello from function"));
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
- const fn = await container.resolve(FUNC_TOKEN);
66
- const result = fn(); // result = "Hello from function"
110
+ // Accessing value after construction is safe
111
+ await container.resolve(LAZY_A);
112
+ await container.resolve(LAZY_B);
67
113
  ```
68
114
 
69
- 3. Bind a factory
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
- - config is optional
117
+ ### Circular Dependency Detection
78
118
 
79
119
  ```js
80
- const TOKEN = Symbol("token");
120
+ const A = Symbol("A");
121
+ const B = Symbol("B");
81
122
 
82
- container.bind(TOKEN).toValue({ message: "Hello" });
83
-
84
- const FACTORY_TOKEN = Symbol("factory");
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
- { scope: "singleton", config: { message: "World." } },
92
- );
128
+ {
129
+ name: B,
130
+ factory: ({ resolver }) => resolver.resolve(A),
131
+ },
132
+ ]);
93
133
 
94
- const result = await container.resolve(FACTORY_TOKEN); // result = "Factory resolved: Hello world."
134
+ await container.resolve(A);
135
+ // Throws Error: Circular dependency detected: A -> B -> A
95
136
  ```
96
137
 
97
- 4. Circular dependencies
138
+ ### Dispose
98
139
 
99
140
  ```js
100
- const LAZY_DEP = Symbol("lazyDep");
101
- const CONSUMER = Symbol("consumer");
102
-
103
- container.bind(LAZY_DEP).toValue({ data: "lazy data" });
141
+ class Resource {
142
+ async dispose() {
143
+ console.log("Resource disposed");
144
+ }
145
+ }
104
146
 
105
- container.bind(CONSUMER).toFactory(({ resolver }) => {
106
- const lazy = resolver.lazyResolve(LAZY_DEP);
147
+ const RESOURCE = Symbol("RESOURCE");
107
148
 
108
- return {
109
- getLazyData: () => lazy.value.data,
110
- };
111
- });
149
+ const container = createContainer([{ name: RESOURCE, value: new Resource() }]);
112
150
 
113
- const consumer = await container.resolve(CONSUMER);
114
- // consumer.getLazyData()).toBe("lazy data");
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,37 +1,43 @@
1
1
  //#region src/container/index.d.ts
2
- type Scope = "singleton" | "transient";
3
- type BindingConfig = Record<string, unknown>;
4
2
  type Resolve = <T>(name: symbol) => Promise<T>;
5
3
  type LazyResolve = <T = unknown>(name: symbol) => Readonly<{
6
4
  value: T;
7
5
  }>;
8
- type Resolver = {
9
- resolve: Resolve;
10
- lazyResolve: LazyResolve;
11
- };
12
- type ToValue = (value: unknown) => void;
13
- type ToFunction = (fn: (...args: unknown[]) => unknown) => void;
14
- type ToFactory = (fn: ({
15
- resolver,
16
- config
17
- }: {
18
- resolver: Resolver;
19
- config?: BindingConfig;
20
- }) => unknown | Promise<unknown>, options?: {
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>;
21
30
  scope?: Scope;
22
- config?: BindingConfig;
23
- }) => void;
31
+ config?: Config;
32
+ value?: never;
33
+ callable?: never;
34
+ };
24
35
  type Disposable = {
25
36
  dispose: () => Promise<void> | void;
26
37
  };
27
38
  type Container = {
28
- bind: (name: symbol) => {
29
- toValue: ToValue;
30
- toFunction: ToFunction;
31
- toFactory: ToFactory;
32
- };
33
39
  resolve: Resolve;
34
40
  } & Disposable;
35
- declare const createContainer: () => Container;
41
+ declare const createContainer: (definitions: readonly BindingDefinition[]) => Container;
36
42
  //#endregion
37
- export { BindingConfig, Container, Scope, createContainer };
43
+ export { BindingDefinition, Container, Scope, createContainer };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- const e=()=>{let e=new t;return{bind:t=>e.bind(t),resolve:t=>e.resolve(t),dispose:()=>e.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}bind(e){return{toValue:t=>{this.#bindings.set(e,{factory:()=>t,scope:`singleton`})},toFunction:t=>{this.#bindings.set(e,{factory:()=>t,scope:`singleton`})},toFactory:(t,n)=>{this.#bindings.set(e,{factory:(e,r)=>t({resolver:{resolve:e,lazyResolve:r},config:n?.config}),scope:n?.scope??`singleton`,config:n?.config})}}}async#resolveBinding(t,n){let r=this.#bindings.get(t);if(!r)throw Error(`No binding found for name: ${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 resolver for name ${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. Do not use lazy-resolved bindings before the binding construction ends.`);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(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): ${t}`)}}};export{e as createContainer};
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};
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
- "version": "0.2.2",
7
+ "version": "0.3.0",
8
8
  "description": "The Resolid DI Container package.",
9
9
  "author": {
10
10
  "name": "Huijie Wei",