@resolid/di 0.6.0 → 0.7.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 +10 -64
- package/dist/index.d.mts +71 -0
- package/dist/index.mjs +1 -0
- package/package.json +8 -10
- package/dist/index.d.ts +0 -119
- package/dist/index.js +0 -1
package/README.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TypeScript Dependency Injection Container
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
6
|
<b>[Documentation](https://www.resolid.tech/docs/di)</b> | [Framework Bundle](https://github.com/resolid/framework)
|
|
7
7
|
|
|
8
|
-
## TypeScript Dependency Injection Container
|
|
9
|
-
|
|
10
8
|
A lightweight, fully-typed Dependency Injection (DI) container for TypeScript.
|
|
11
9
|
Supports singleton & transient scopes, lazy resolution, optional dependencies, and disposable resources. Fully functional with async factories
|
|
12
10
|
and avoids circular dependency issues.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
## Features
|
|
15
13
|
|
|
16
14
|
- Fully typed with TypeScript, no any.
|
|
17
15
|
- Supports singleton and transient scopes.
|
|
@@ -19,10 +17,9 @@ and avoids circular dependency issues.
|
|
|
19
17
|
- Optional dependency resolution.
|
|
20
18
|
- Detects circular dependencies.
|
|
21
19
|
- Handles disposable instances with dispose() support.
|
|
22
|
-
- Fully async-compatible with inject() and injectAsync() functions.
|
|
23
20
|
- Context-aware injection with InjectionContext.
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
## Installation
|
|
26
23
|
|
|
27
24
|
```shell
|
|
28
25
|
pnpm add @resolid/di
|
|
@@ -34,7 +31,9 @@ yarn add @resolid/di
|
|
|
34
31
|
bun add @resolid/di
|
|
35
32
|
```
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Basic
|
|
38
37
|
|
|
39
38
|
```typescript
|
|
40
39
|
import { Container, inject } from "@resolid/di";
|
|
@@ -95,65 +94,13 @@ container.add({
|
|
|
95
94
|
});
|
|
96
95
|
```
|
|
97
96
|
|
|
98
|
-
### Asynchronous Dependencies
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { Container, injectAsync } from "@resolid/di";
|
|
102
|
-
|
|
103
|
-
class AsyncDatabaseService {
|
|
104
|
-
async connect(): Promise<void> {
|
|
105
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
106
|
-
console.log("Database connected");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
class AsyncUserService {
|
|
111
|
-
private db : AsyncDatabaseService
|
|
112
|
-
|
|
113
|
-
constructor(db: AsyncDatabaseService) {
|
|
114
|
-
this.db = db;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async getUser(id: number): Promise<string> {
|
|
118
|
-
return `User ${id}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const container = new Container();
|
|
123
|
-
|
|
124
|
-
container.add({
|
|
125
|
-
token: AsyncDatabaseService,
|
|
126
|
-
factory: async () => {
|
|
127
|
-
const db = new AsyncDatabaseService();
|
|
128
|
-
await db.connect();
|
|
129
|
-
return db;
|
|
130
|
-
},
|
|
131
|
-
async: true,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
container.add({
|
|
135
|
-
token: AsyncUserService,
|
|
136
|
-
factory: async () => {
|
|
137
|
-
const db = await injectAsync(AsyncDatabaseService);
|
|
138
|
-
return new AsyncUserService(db)
|
|
139
|
-
},
|
|
140
|
-
async: true,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Resolve async services
|
|
144
|
-
const userService = await container.getAsync(AsyncUserService);
|
|
145
|
-
const user = await userService.getUser(1);
|
|
146
|
-
|
|
147
|
-
console.log(user); // Output: User 1
|
|
148
|
-
```
|
|
149
|
-
|
|
150
97
|
### Optional Dependencies
|
|
151
98
|
|
|
152
99
|
```typescript
|
|
153
100
|
import { Container, inject } from "@resolid/di";
|
|
154
101
|
|
|
155
102
|
class AnalyticsService {
|
|
156
|
-
private logger?: LogService
|
|
103
|
+
private logger?: LogService;
|
|
157
104
|
|
|
158
105
|
constructor(logger?: LogService) {
|
|
159
106
|
this.logger = logger;
|
|
@@ -186,7 +133,7 @@ analytics.track("page_view"); // Output: Analytics (no logger): page_view
|
|
|
186
133
|
import { Container, inject } from "@resolid/di";
|
|
187
134
|
|
|
188
135
|
class ReportService {
|
|
189
|
-
private getLogger
|
|
136
|
+
private getLogger: () => LogService;
|
|
190
137
|
|
|
191
138
|
constructor(private getLogger: () => LogService) {
|
|
192
139
|
this.getLogger = getLogger;
|
|
@@ -255,11 +202,10 @@ const container = new Container();
|
|
|
255
202
|
|
|
256
203
|
container.add({
|
|
257
204
|
token: DatabaseConnection,
|
|
258
|
-
factory:
|
|
259
|
-
async: true,
|
|
205
|
+
factory: () => new DatabaseConnection(),
|
|
260
206
|
});
|
|
261
207
|
|
|
262
|
-
const db =
|
|
208
|
+
const db = container.get(DatabaseConnection);
|
|
263
209
|
|
|
264
210
|
// Dispose all singleton instances
|
|
265
211
|
await container.dispose(); // Output: Database connection closed
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/shared/index.d.ts
|
|
2
|
+
type Token<T = unknown> = symbol | (new (...args: any[]) => T) | {
|
|
3
|
+
prototype: T;
|
|
4
|
+
name: string;
|
|
5
|
+
};
|
|
6
|
+
interface Disposable {
|
|
7
|
+
dispose: () => Promise<void> | void;
|
|
8
|
+
}
|
|
9
|
+
interface Resolver {
|
|
10
|
+
get: (<T>(token: Token<T>) => T) & (<T>(token: Token<T>, options: {
|
|
11
|
+
optional: true;
|
|
12
|
+
}) => T | undefined) & (<T>(token: Token<T>, options: {
|
|
13
|
+
lazy: true;
|
|
14
|
+
}) => () => T) & (<T>(token: Token<T>, options: {
|
|
15
|
+
lazy: true;
|
|
16
|
+
optional: true;
|
|
17
|
+
}) => () => T | undefined) & (<T>(token: Token<T>, options: {
|
|
18
|
+
lazy?: false;
|
|
19
|
+
optional?: boolean;
|
|
20
|
+
}) => T | undefined) & (<T>(token: Token<T>, options?: {
|
|
21
|
+
lazy?: boolean;
|
|
22
|
+
optional?: boolean;
|
|
23
|
+
}) => T | undefined | (() => T | undefined));
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/container/index.d.ts
|
|
27
|
+
type Scope = "singleton" | "transient";
|
|
28
|
+
type Provider<T = unknown> = {
|
|
29
|
+
token: Token<T>;
|
|
30
|
+
factory: () => T;
|
|
31
|
+
scope?: Scope;
|
|
32
|
+
};
|
|
33
|
+
declare class Container implements Resolver, Disposable {
|
|
34
|
+
private readonly _providers;
|
|
35
|
+
private readonly _singletons;
|
|
36
|
+
private readonly _constructing;
|
|
37
|
+
private _checkCircularDependency;
|
|
38
|
+
private _resolve;
|
|
39
|
+
add(provider: Provider): void;
|
|
40
|
+
get<T>(token: Token<T>): T;
|
|
41
|
+
get<T>(token: Token<T>, options: {
|
|
42
|
+
optional: true;
|
|
43
|
+
}): T | undefined;
|
|
44
|
+
get<T>(token: Token<T>, options: {
|
|
45
|
+
lazy: true;
|
|
46
|
+
}): () => T;
|
|
47
|
+
get<T>(token: Token<T>, options: {
|
|
48
|
+
lazy: true;
|
|
49
|
+
optional: true;
|
|
50
|
+
}): () => T | undefined;
|
|
51
|
+
get<T>(token: Token<T>, options: {
|
|
52
|
+
lazy?: false;
|
|
53
|
+
optional?: boolean;
|
|
54
|
+
}): T | undefined;
|
|
55
|
+
dispose(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/inject/index.d.ts
|
|
59
|
+
declare function inject<T>(token: Token<T>): T;
|
|
60
|
+
declare function inject<T>(token: Token<T>, options: {
|
|
61
|
+
optional: true;
|
|
62
|
+
}): T | undefined;
|
|
63
|
+
declare function inject<T>(token: Token<T>, options: {
|
|
64
|
+
lazy: true;
|
|
65
|
+
}): () => T;
|
|
66
|
+
declare function inject<T>(token: Token<T>, options: {
|
|
67
|
+
lazy: true;
|
|
68
|
+
optional: true;
|
|
69
|
+
}): () => T | undefined;
|
|
70
|
+
//#endregion
|
|
71
|
+
export { Container, Provider, Scope, type Token, inject };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=class extends Error{constructor(){super(`inject() must be called within a injection context`)}},t=class t{static contextStack=[];resolver;constructor(e){this.resolver=e}static current(){let t=this.contextStack[this.contextStack.length-1];if(t===void 0)throw new e;return t}run(e){t.contextStack.push(this);try{return e(this.resolver)}finally{t.contextStack.pop()}}};function n(e){return typeof e==`symbol`?e.description??String(e):e.name}var r=class{_providers=new Map;_singletons=new Map;_constructing=[];_checkCircularDependency(e){if(this._constructing.includes(e))throw Error(`Circular dependency detected ${[...this._constructing,e].map(n).join(` -> `)}`)}_resolve(e,r){this._checkCircularDependency(e);let i=this._providers.get(e);if(i===void 0){if(!r)throw Error(`No provider found for ${n(e)}`);return}let a=i.scope!==`transient`;if(a&&this._singletons.has(e))return this._singletons.get(e);this._constructing.push(e);try{let n=new t(this).run(()=>i.factory());return a&&this._singletons.set(e,n),n}finally{this._constructing.pop()}}add(e){this._providers.set(e.token,e)}get(e,t){return t?.lazy??!1?()=>this.get(e,{...t,lazy:!1}):this._resolve(e,t?.optional??!1)}async dispose(){let e=0,t=``;for(let[r,i]of this._singletons)if(typeof i.dispose==`function`)try{await i.dispose()}catch(i){e++,t+=`${n(r)}: ${i instanceof Error?i.message:i}; `}if(this._singletons.clear(),e>0)throw Error(`Failed to dispose ${e} provider(s):\n${t.slice(0,-2)}`)}};function i(n,r){try{return t.current().run(e=>e.get(n,r))}catch(t){if(t instanceof e&&r?.optional==1)return;throw t}}export{r as Container,i as inject};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@resolid/di",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "TypeScript Dependency Injection Container",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"resolid",
|
|
8
8
|
"container",
|
|
@@ -26,20 +26,18 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"types": "./dist/index.d.
|
|
30
|
-
"import": "./dist/index.
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"import": "./dist/index.mjs"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
"main": "./dist/index.
|
|
34
|
-
"types": "./dist/index.d.
|
|
33
|
+
"main": "./dist/index.mjs",
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
35
|
"files": [
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"typescript": "^5.9.3",
|
|
42
|
-
"vitest": "^4.0.3"
|
|
39
|
+
"tsdown": "^0.16.8",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
43
41
|
},
|
|
44
42
|
"engines": {
|
|
45
43
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
package/dist/index.d.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
//#region src/shared/index.d.ts
|
|
2
|
-
type Class<T> = new (...args: any[]) => T;
|
|
3
|
-
type Token<T = unknown> = symbol | Class<T>;
|
|
4
|
-
interface Disposable {
|
|
5
|
-
dispose: () => Promise<void> | void;
|
|
6
|
-
}
|
|
7
|
-
interface Resolver {
|
|
8
|
-
get: (<T>(token: Token<T>) => T) & (<T>(token: Token<T>, options: {
|
|
9
|
-
optional: true;
|
|
10
|
-
}) => T | undefined) & (<T>(token: Token<T>, options: {
|
|
11
|
-
lazy: true;
|
|
12
|
-
}) => () => T) & (<T>(token: Token<T>, options: {
|
|
13
|
-
lazy: true;
|
|
14
|
-
optional: true;
|
|
15
|
-
}) => () => T | undefined) & (<T>(token: Token<T>, options: {
|
|
16
|
-
lazy?: false;
|
|
17
|
-
optional?: boolean;
|
|
18
|
-
}) => T | undefined) & (<T>(token: Token<T>, options?: {
|
|
19
|
-
lazy?: boolean;
|
|
20
|
-
optional?: boolean;
|
|
21
|
-
}) => T | undefined | (() => T | undefined));
|
|
22
|
-
getAsync: (<T>(token: Token<T>) => Promise<T>) & (<T>(token: Token<T>, options: {
|
|
23
|
-
optional: true;
|
|
24
|
-
}) => Promise<T | undefined>) & (<T>(token: Token<T>, options: {
|
|
25
|
-
lazy: true;
|
|
26
|
-
}) => () => Promise<T>) & (<T>(token: Token<T>, options: {
|
|
27
|
-
lazy: true;
|
|
28
|
-
optional: true;
|
|
29
|
-
}) => () => Promise<T | undefined>) & (<T>(token: Token<T>, options: {
|
|
30
|
-
lazy?: false;
|
|
31
|
-
optional?: boolean;
|
|
32
|
-
}) => Promise<T | undefined>) & (<T>(token: Token<T>, options?: {
|
|
33
|
-
lazy?: boolean;
|
|
34
|
-
optional?: boolean;
|
|
35
|
-
}) => Promise<T | undefined> | (() => Promise<T | undefined>));
|
|
36
|
-
}
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/container/index.d.ts
|
|
39
|
-
type Scope = "singleton" | "transient";
|
|
40
|
-
type SyncProvider<T = unknown> = {
|
|
41
|
-
token: Token<T>;
|
|
42
|
-
factory: (resolver: Resolver) => T;
|
|
43
|
-
async?: false;
|
|
44
|
-
scope?: Scope;
|
|
45
|
-
};
|
|
46
|
-
type AsyncProvider<T = unknown> = {
|
|
47
|
-
token: Token<T>;
|
|
48
|
-
factory: (resolver: Resolver) => Promise<T>;
|
|
49
|
-
async?: true;
|
|
50
|
-
scope?: Scope;
|
|
51
|
-
};
|
|
52
|
-
type Provider<T = unknown> = SyncProvider<T> | AsyncProvider<T>;
|
|
53
|
-
declare class Container implements Resolver, Disposable {
|
|
54
|
-
private readonly _providers;
|
|
55
|
-
private readonly _singletons;
|
|
56
|
-
private readonly _constructing;
|
|
57
|
-
private _checkCircularDependency;
|
|
58
|
-
private _resolveProvide;
|
|
59
|
-
private _resolve;
|
|
60
|
-
private _resolveAsync;
|
|
61
|
-
add(provider: Provider): void;
|
|
62
|
-
get<T>(token: Token<T>): T;
|
|
63
|
-
get<T>(token: Token<T>, options: {
|
|
64
|
-
optional: true;
|
|
65
|
-
}): T | undefined;
|
|
66
|
-
get<T>(token: Token<T>, options: {
|
|
67
|
-
lazy: true;
|
|
68
|
-
}): () => T;
|
|
69
|
-
get<T>(token: Token<T>, options: {
|
|
70
|
-
lazy: true;
|
|
71
|
-
optional: true;
|
|
72
|
-
}): () => T | undefined;
|
|
73
|
-
get<T>(token: Token<T>, options: {
|
|
74
|
-
lazy?: false;
|
|
75
|
-
optional?: boolean;
|
|
76
|
-
}): T | undefined;
|
|
77
|
-
getAsync<T>(token: Token<T>): Promise<T>;
|
|
78
|
-
getAsync<T>(token: Token<T>, options: {
|
|
79
|
-
optional: true;
|
|
80
|
-
}): Promise<T | undefined>;
|
|
81
|
-
getAsync<T>(token: Token<T>, options: {
|
|
82
|
-
lazy: true;
|
|
83
|
-
}): () => Promise<T>;
|
|
84
|
-
getAsync<T>(token: Token<T>, options: {
|
|
85
|
-
lazy: true;
|
|
86
|
-
optional: true;
|
|
87
|
-
}): () => Promise<T | undefined>;
|
|
88
|
-
getAsync<T>(token: Token<T>, options: {
|
|
89
|
-
lazy?: false;
|
|
90
|
-
optional?: boolean;
|
|
91
|
-
}): Promise<T | undefined>;
|
|
92
|
-
dispose(): Promise<void>;
|
|
93
|
-
}
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/inject/index.d.ts
|
|
96
|
-
declare function inject<T>(token: Token<T>): T;
|
|
97
|
-
declare function inject<T>(token: Token<T>, options: {
|
|
98
|
-
optional: true;
|
|
99
|
-
}): T | undefined;
|
|
100
|
-
declare function inject<T>(token: Token<T>, options: {
|
|
101
|
-
lazy: true;
|
|
102
|
-
}): () => T;
|
|
103
|
-
declare function inject<T>(token: Token<T>, options: {
|
|
104
|
-
lazy: true;
|
|
105
|
-
optional: true;
|
|
106
|
-
}): () => T | undefined;
|
|
107
|
-
declare function injectAsync<T>(token: Token<T>): Promise<T>;
|
|
108
|
-
declare function injectAsync<T>(token: Token<T>, options: {
|
|
109
|
-
optional: true;
|
|
110
|
-
}): Promise<T | undefined>;
|
|
111
|
-
declare function injectAsync<T>(token: Token<T>, options: {
|
|
112
|
-
lazy: true;
|
|
113
|
-
}): () => Promise<T>;
|
|
114
|
-
declare function injectAsync<T>(token: Token<T>, options: {
|
|
115
|
-
lazy: true;
|
|
116
|
-
optional: true;
|
|
117
|
-
}): () => Promise<T | undefined>;
|
|
118
|
-
//#endregion
|
|
119
|
-
export { Container, Provider, type Resolver, Scope, type Token, inject, injectAsync };
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=class extends Error{constructor(){super(`inject() / injectAsync() must be called within a injection context`)}},t=class t{static contextStack=[];resolver;constructor(e){this.resolver=e}static current(){let t=this.contextStack[this.contextStack.length-1];if(t===void 0)throw new e;return t}run(e){t.contextStack.push(this);try{return e(this.resolver)}finally{t.contextStack.pop()}}async runAsync(e){t.contextStack.push(this);try{return await e(this.resolver)}finally{t.contextStack.pop()}}};function n(e){return typeof e==`symbol`?e.description??String(e):e.name}var r=class{_providers=new Map;_singletons=new Map;_constructing=[];_checkCircularDependency(e){if(this._constructing.includes(e))throw Error(`Circular dependency detected ${[...this._constructing,e].map(n).join(` -> `)}`)}_resolveProvide(e,t){let r=this._providers.get(e);if(r===void 0&&!t)throw Error(`No provider found for ${n(e)}`);return r}_resolve(e,r){this._checkCircularDependency(e);let i=this._resolveProvide(e,r);if(i===void 0)return;if(i.async)throw Error(`Provider for ${n(e)} is async, please use injectAsync()`);let a=i.scope!==`transient`;if(a&&this._singletons.has(e))return this._singletons.get(e);this._constructing.push(e);try{let n=new t(this).run(()=>i.factory(this));return a&&this._singletons.set(e,n),n}finally{this._constructing.pop()}}async _resolveAsync(e,n){this._checkCircularDependency(e);let r=this._resolveProvide(e,n);if(r===void 0)return;let i=r.scope!==`transient`;if(i&&this._singletons.has(e))return this._singletons.get(e);this._constructing.push(e);try{let n=await new t(this).runAsync(async()=>await r.factory(this));return i&&this._singletons.set(e,n),n}finally{this._constructing.pop()}}add(e){this._providers.set(e.token,e)}get(e,t){return t?.lazy??!1?()=>this.get(e,{...t,lazy:!1}):this._resolve(e,t?.optional??!1)}getAsync(e,t){return t?.lazy??!1?()=>this.getAsync(e,{...t,lazy:!1}):this._resolveAsync(e,t?.optional??!1)}async dispose(){let e=0,t=``;for(let[r,i]of this._singletons)if(typeof i.dispose==`function`)try{await i.dispose()}catch(i){e++,t+=`${n(r)}: ${i instanceof Error?i.message:i}; `}if(this._singletons.clear(),e>0)throw Error(`Failed to dispose ${e} provider(s):\n${t.slice(0,-2)}`)}};function i(n,r){try{return t.current().run(e=>e.get(n,r))}catch(t){if(t instanceof e&&r?.optional==1)return;throw t}}function a(n,r){try{return r?.lazy?t.current().run(e=>e.getAsync(n,{...r,lazy:!0})):t.current().runAsync(e=>e.getAsync(n,{...r,lazy:!1}))}catch(t){return t instanceof e&&r?.optional===!0?r?.lazy?()=>Promise.resolve(void 0):Promise.resolve(void 0):Promise.reject(t)}}export{r as Container,i as inject,a as injectAsync};
|