di-craft 0.0.9 → 0.0.10
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 +170 -2
- package/dist/index.cjs +153 -0
- package/dist/index.d.cts +59 -1
- package/dist/index.d.mts +59 -1
- package/dist/index.mjs +146 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,186 @@
|
|
|
1
1
|
# di-craft
|
|
2
2
|
|
|
3
|
-
A tiny
|
|
3
|
+
A tiny, type-safe dependency injection container for TypeScript.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import {
|
|
7
|
+
createContainer,
|
|
8
|
+
createToken,
|
|
9
|
+
provideFactory,
|
|
10
|
+
provideValue,
|
|
11
|
+
type Provider,
|
|
12
|
+
} from "di-craft";
|
|
13
|
+
|
|
14
|
+
const CONFIG = createToken<Config>("config");
|
|
15
|
+
const LOGGER = createToken<Logger>("logger");
|
|
16
|
+
const USERS = createToken<UserService>("users");
|
|
17
|
+
|
|
18
|
+
const providers: Provider[] = [
|
|
19
|
+
provideValue(CONFIG, loadConfig()),
|
|
20
|
+
provideFactory(LOGGER, {
|
|
21
|
+
deps: { config: CONFIG },
|
|
22
|
+
useFactory: ({ config }) => new Logger(config.level),
|
|
23
|
+
}),
|
|
24
|
+
provideFactory(USERS, {
|
|
25
|
+
deps: { logger: LOGGER },
|
|
26
|
+
useFactory: ({ logger }) => new UserService(logger),
|
|
27
|
+
}),
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const container = createContainer(providers);
|
|
31
|
+
|
|
32
|
+
const users = container.get(USERS); // UserService, fully typed
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Philosophy
|
|
36
|
+
|
|
37
|
+
`di-craft` is a small DI container with no magic.
|
|
38
|
+
|
|
39
|
+
- No decorators
|
|
40
|
+
- No `reflect-metadata`
|
|
41
|
+
- No framework dependencies
|
|
42
|
+
|
|
43
|
+
Just **tokens**, **providers**, a **container**, **scopes**, and **cycle detection**.
|
|
4
44
|
|
|
5
45
|
## Features
|
|
6
46
|
|
|
7
47
|
- Zero runtime dependencies
|
|
8
48
|
- No decorators
|
|
9
|
-
- No reflect-metadata
|
|
49
|
+
- No `reflect-metadata`
|
|
50
|
+
- Framework agnostic
|
|
10
51
|
- Type-safe tokens
|
|
11
52
|
- Explicit factories
|
|
12
53
|
- Singleton and transient scopes
|
|
54
|
+
- Circular dependency detection
|
|
55
|
+
- Tree-shakable, tiny bundle size
|
|
56
|
+
- Ships both ESM and CommonJS builds
|
|
13
57
|
|
|
14
58
|
## Install
|
|
15
59
|
|
|
16
60
|
```bash
|
|
17
61
|
bun add di-craft
|
|
62
|
+
npm install di-craft
|
|
63
|
+
pnpm add di-craft
|
|
64
|
+
yarn add di-craft
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requires Node.js `>= 20`.
|
|
68
|
+
|
|
69
|
+
## Core concepts
|
|
70
|
+
|
|
71
|
+
### Tokens
|
|
72
|
+
|
|
73
|
+
A token is a unique, type-carrying key. Identity is based on an internal `symbol`, **not** on the name — two tokens with the same name are still different.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const PORT = createToken<number>("port");
|
|
77
|
+
|
|
78
|
+
PORT.name; // "port" — used only for error messages
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The type argument flows everywhere: providers must produce a matching value, and `container.get(PORT)` returns `number`.
|
|
82
|
+
|
|
83
|
+
### Providers
|
|
84
|
+
|
|
85
|
+
A provider tells the container how to produce the value for a token.
|
|
86
|
+
|
|
87
|
+
`provideValue` — register an existing value:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
provideValue(PORT, 3000);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`provideFactory` — build the value lazily, with optional dependencies and scope:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
provideFactory(HTTP, {
|
|
97
|
+
deps: { config: CONFIG }, // optional, keyed map of tokens
|
|
98
|
+
scope: "singleton", // optional, defaults to "singleton"
|
|
99
|
+
useFactory: ({ config }) => new HttpClient(config.apiUrl),
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The keys in `deps` become the keys of the object passed to `useFactory`, each resolved to its token's type.
|
|
104
|
+
|
|
105
|
+
### Container
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const container = createContainer(providers); // providers are optional
|
|
109
|
+
|
|
110
|
+
container.register(provideValue(PORT, 3000)); // register more at any time
|
|
111
|
+
container.has(PORT); // true
|
|
112
|
+
container.get(PORT); // 3000
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Registering the same token twice throws `DuplicateProviderError`.
|
|
116
|
+
|
|
117
|
+
### Scopes
|
|
118
|
+
|
|
119
|
+
| Scope | Behavior |
|
|
120
|
+
| ---------------------- | ---------------------------------------------------------------- |
|
|
121
|
+
| `singleton` (default) | The factory runs once; the same instance is returned every time. |
|
|
122
|
+
| `transient` | The factory runs on every `get`, producing a fresh instance. |
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
provideFactory(ID, {
|
|
126
|
+
scope: "transient",
|
|
127
|
+
useFactory: () => crypto.randomUUID(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
container.get(ID) !== container.get(ID); // true
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A transient provider that depends on a singleton still reuses the shared singleton instance.
|
|
134
|
+
|
|
135
|
+
### Cycle detection
|
|
136
|
+
|
|
137
|
+
If providers form a dependency cycle, resolution throws `CircularDependencyError` with the full path instead of overflowing the stack.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// A -> B -> A
|
|
141
|
+
container.get(A); // throws: Circular dependency detected: A -> B -> A
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Error handling
|
|
145
|
+
|
|
146
|
+
All errors extend the shared `DiError` base class, so you can catch any container error with a single check:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { DiError, MissingProviderError } from "di-craft";
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
container.get(SOME_TOKEN);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof MissingProviderError) {
|
|
155
|
+
// a specific failure
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (error instanceof DiError) {
|
|
159
|
+
// any di-craft error
|
|
160
|
+
}
|
|
161
|
+
}
|
|
18
162
|
```
|
|
163
|
+
|
|
164
|
+
| Error | Thrown when |
|
|
165
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
166
|
+
| `MissingProviderError` | A token is resolved but no provider is registered. |
|
|
167
|
+
| `DuplicateProviderError` | A token is registered more than once. |
|
|
168
|
+
| `CircularDependencyError` | Providers form a dependency cycle. |
|
|
169
|
+
| `InvalidDependencyError` | A declared dependency token is missing/undefined. |
|
|
170
|
+
|
|
171
|
+
## API reference
|
|
172
|
+
|
|
173
|
+
| Export | Description |
|
|
174
|
+
| ----------------------- | ---------------------------------------------------------- |
|
|
175
|
+
| `createToken<T>(name)` | Create a unique, typed token. |
|
|
176
|
+
| `provideValue(token, value)` | Provider that returns an existing value. |
|
|
177
|
+
| `provideFactory(token, options)` | Provider that builds a value via a factory. |
|
|
178
|
+
| `createContainer(providers?)` | Create a container, optionally seeded with providers. |
|
|
179
|
+
|
|
180
|
+
Exported types: `Container`, `Token`, `Provider`, `ValueProvider`, `FactoryProvider`, `Scope`.
|
|
181
|
+
|
|
182
|
+
Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`, `CircularDependencyError`, `InvalidDependencyError`.
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,149 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/error/error.ts
|
|
3
|
+
var DiError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "DiError";
|
|
7
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/registry/errors.ts
|
|
12
|
+
var DuplicateProviderError = class extends DiError {
|
|
13
|
+
constructor(tokenName) {
|
|
14
|
+
super(`Provider for token "${tokenName}" is already registered`);
|
|
15
|
+
this.name = "DuplicateProviderError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/registry/registry.ts
|
|
20
|
+
var RegistryClass = class {
|
|
21
|
+
providers = /* @__PURE__ */ new Map();
|
|
22
|
+
register(provider) {
|
|
23
|
+
if (this.providers.has(provider.provide.id)) throw new DuplicateProviderError(provider.provide.name);
|
|
24
|
+
this.providers.set(provider.provide.id, provider);
|
|
25
|
+
}
|
|
26
|
+
get(token) {
|
|
27
|
+
return this.providers.get(token.id);
|
|
28
|
+
}
|
|
29
|
+
has(token) {
|
|
30
|
+
return this.providers.has(token.id);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const createRegistry = () => new RegistryClass();
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/resolver/errors.ts
|
|
36
|
+
var MissingProviderError = class extends DiError {
|
|
37
|
+
constructor(tokenName) {
|
|
38
|
+
super(`Provider for token "${tokenName}" is not registered`);
|
|
39
|
+
this.name = "MissingProviderError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var InvalidDependencyError = class extends DiError {
|
|
43
|
+
constructor(dependencyKey) {
|
|
44
|
+
super(`Invalid dependency "${dependencyKey}"`);
|
|
45
|
+
this.name = "InvalidDependencyError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var CircularDependencyError = class extends DiError {
|
|
49
|
+
constructor(tokenNames) {
|
|
50
|
+
super(`Circular dependency detected: ${tokenNames.join(" -> ")}`);
|
|
51
|
+
this.name = "CircularDependencyError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/provider/provider.ts
|
|
56
|
+
const provideValue = (token, useValue) => ({
|
|
57
|
+
provide: token,
|
|
58
|
+
useValue
|
|
59
|
+
});
|
|
60
|
+
const provideFactory = (token, options) => {
|
|
61
|
+
return {
|
|
62
|
+
provide: token,
|
|
63
|
+
useFactory: options.useFactory,
|
|
64
|
+
...options.deps ? { deps: options.deps } : {},
|
|
65
|
+
...options.scope ? { scope: options.scope } : {}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const isValueProvider = (provider) => {
|
|
69
|
+
return "useValue" in provider;
|
|
70
|
+
};
|
|
71
|
+
const isFactoryProvider = (provider) => {
|
|
72
|
+
return "useFactory" in provider;
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/resolver/resolver.ts
|
|
76
|
+
var ResolverClass = class {
|
|
77
|
+
registry;
|
|
78
|
+
instances = /* @__PURE__ */ new Map();
|
|
79
|
+
constructor(registry) {
|
|
80
|
+
this.registry = registry;
|
|
81
|
+
}
|
|
82
|
+
resolve(token) {
|
|
83
|
+
return this.resolveToken(token, {
|
|
84
|
+
resolving: /* @__PURE__ */ new Set(),
|
|
85
|
+
path: []
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
resolveToken(token, context) {
|
|
89
|
+
const provider = this.registry.get(token);
|
|
90
|
+
if (!provider) throw new MissingProviderError(token.name);
|
|
91
|
+
if (isValueProvider(provider)) return provider.useValue;
|
|
92
|
+
if (isFactoryProvider(provider)) {
|
|
93
|
+
const scope = provider.scope ?? "singleton";
|
|
94
|
+
if (scope === "singleton" && this.instances.has(token.id)) return this.instances.get(token.id);
|
|
95
|
+
if (context.resolving.has(token.id)) {
|
|
96
|
+
const cycleStartIndex = context.path.findIndex((pathToken) => pathToken.id === token.id);
|
|
97
|
+
throw new CircularDependencyError([...context.path.slice(cycleStartIndex), token].map((cycleToken) => cycleToken.name));
|
|
98
|
+
}
|
|
99
|
+
context.resolving.add(token.id);
|
|
100
|
+
context.path.push(token);
|
|
101
|
+
try {
|
|
102
|
+
const deps = this.resolveDeps(provider.deps, context);
|
|
103
|
+
const value = provider.useFactory(deps);
|
|
104
|
+
if (scope === "singleton") this.instances.set(token.id, value);
|
|
105
|
+
return value;
|
|
106
|
+
} finally {
|
|
107
|
+
context.path.pop();
|
|
108
|
+
context.resolving.delete(token.id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw new MissingProviderError(token.name);
|
|
112
|
+
}
|
|
113
|
+
resolveDeps(deps, context) {
|
|
114
|
+
if (!deps) return {};
|
|
115
|
+
const resolvedDeps = {};
|
|
116
|
+
for (const key of Object.keys(deps)) {
|
|
117
|
+
const token = deps[key];
|
|
118
|
+
if (token === void 0) throw new InvalidDependencyError(String(key));
|
|
119
|
+
resolvedDeps[key] = this.resolveToken(token, context);
|
|
120
|
+
}
|
|
121
|
+
return resolvedDeps;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const createResolver = (registry) => new ResolverClass(registry);
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/container/container.ts
|
|
127
|
+
var ContainerClass = class {
|
|
128
|
+
registry;
|
|
129
|
+
resolver;
|
|
130
|
+
constructor(providers = []) {
|
|
131
|
+
this.registry = createRegistry();
|
|
132
|
+
for (const provider of providers) this.registry.register(provider);
|
|
133
|
+
this.resolver = createResolver(this.registry);
|
|
134
|
+
}
|
|
135
|
+
register(provider) {
|
|
136
|
+
this.registry.register(provider);
|
|
137
|
+
}
|
|
138
|
+
get(token) {
|
|
139
|
+
return this.resolver.resolve(token);
|
|
140
|
+
}
|
|
141
|
+
has(token) {
|
|
142
|
+
return this.registry.has(token);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const createContainer = (providers = []) => new ContainerClass(providers);
|
|
146
|
+
//#endregion
|
|
2
147
|
//#region src/token/token.ts
|
|
3
148
|
var TokenClass = class {
|
|
4
149
|
name;
|
|
@@ -10,4 +155,12 @@ var TokenClass = class {
|
|
|
10
155
|
};
|
|
11
156
|
const createToken = (name) => new TokenClass(name);
|
|
12
157
|
//#endregion
|
|
158
|
+
exports.CircularDependencyError = CircularDependencyError;
|
|
159
|
+
exports.DiError = DiError;
|
|
160
|
+
exports.DuplicateProviderError = DuplicateProviderError;
|
|
161
|
+
exports.InvalidDependencyError = InvalidDependencyError;
|
|
162
|
+
exports.MissingProviderError = MissingProviderError;
|
|
163
|
+
exports.createContainer = createContainer;
|
|
13
164
|
exports.createToken = createToken;
|
|
165
|
+
exports.provideFactory = provideFactory;
|
|
166
|
+
exports.provideValue = provideValue;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,4 +8,62 @@ type Token<T> = {
|
|
|
8
8
|
//#region src/token/token.d.ts
|
|
9
9
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
10
10
|
//#endregion
|
|
11
|
-
|
|
11
|
+
//#region src/provider/types.d.ts
|
|
12
|
+
type DepsMap = Record<string, Token<unknown>>;
|
|
13
|
+
type TokenValue<TToken> = TToken extends Token<infer TValue> ? TValue : never;
|
|
14
|
+
type ResolveDeps<TDeps extends DepsMap> = { readonly [TKey in keyof TDeps]: TokenValue<TDeps[TKey]> };
|
|
15
|
+
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
16
|
+
type ValueProvider<T> = {
|
|
17
|
+
readonly provide: Token<T>;
|
|
18
|
+
readonly useValue: T;
|
|
19
|
+
};
|
|
20
|
+
type FactoryProvider<T, TDeps extends DepsMap = Record<never, never>> = {
|
|
21
|
+
readonly provide: Token<T>;
|
|
22
|
+
readonly deps?: TDeps;
|
|
23
|
+
readonly scope?: Scope;
|
|
24
|
+
readonly useFactory: Factory<T, TDeps>;
|
|
25
|
+
};
|
|
26
|
+
type AnyFactoryProvider = FactoryProvider<unknown, any>;
|
|
27
|
+
type Provider = ValueProvider<unknown> | AnyFactoryProvider;
|
|
28
|
+
type Scope = "singleton" | "transient";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/provider/provider.d.ts
|
|
31
|
+
declare const provideValue: <T>(token: Token<T>, useValue: T) => ValueProvider<T>;
|
|
32
|
+
declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(token: Token<T>, options: {
|
|
33
|
+
readonly deps?: TDeps;
|
|
34
|
+
readonly scope?: Scope;
|
|
35
|
+
readonly useFactory: Factory<T, TDeps>;
|
|
36
|
+
}) => FactoryProvider<T, TDeps>;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/container/types.d.ts
|
|
39
|
+
type Container = {
|
|
40
|
+
register(provider: Provider): void;
|
|
41
|
+
get<T>(token: Token<T>): T;
|
|
42
|
+
has(token: Token<unknown>): boolean;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/container/container.d.ts
|
|
46
|
+
declare const createContainer: (providers?: readonly Provider[]) => Container;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/error/error.d.ts
|
|
49
|
+
declare class DiError extends Error {
|
|
50
|
+
constructor(message: string);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/registry/errors.d.ts
|
|
54
|
+
declare class DuplicateProviderError extends DiError {
|
|
55
|
+
constructor(tokenName: string);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/resolver/errors.d.ts
|
|
59
|
+
declare class MissingProviderError extends DiError {
|
|
60
|
+
constructor(tokenName: string);
|
|
61
|
+
}
|
|
62
|
+
declare class InvalidDependencyError extends DiError {
|
|
63
|
+
constructor(dependencyKey: string);
|
|
64
|
+
}
|
|
65
|
+
declare class CircularDependencyError extends DiError {
|
|
66
|
+
constructor(tokenNames: string[]);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { CircularDependencyError, type Container, DiError, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type Scope, type Token, type ValueProvider, createContainer, createToken, provideFactory, provideValue };
|
package/dist/index.d.mts
CHANGED
|
@@ -8,4 +8,62 @@ type Token<T> = {
|
|
|
8
8
|
//#region src/token/token.d.ts
|
|
9
9
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
10
10
|
//#endregion
|
|
11
|
-
|
|
11
|
+
//#region src/provider/types.d.ts
|
|
12
|
+
type DepsMap = Record<string, Token<unknown>>;
|
|
13
|
+
type TokenValue<TToken> = TToken extends Token<infer TValue> ? TValue : never;
|
|
14
|
+
type ResolveDeps<TDeps extends DepsMap> = { readonly [TKey in keyof TDeps]: TokenValue<TDeps[TKey]> };
|
|
15
|
+
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
16
|
+
type ValueProvider<T> = {
|
|
17
|
+
readonly provide: Token<T>;
|
|
18
|
+
readonly useValue: T;
|
|
19
|
+
};
|
|
20
|
+
type FactoryProvider<T, TDeps extends DepsMap = Record<never, never>> = {
|
|
21
|
+
readonly provide: Token<T>;
|
|
22
|
+
readonly deps?: TDeps;
|
|
23
|
+
readonly scope?: Scope;
|
|
24
|
+
readonly useFactory: Factory<T, TDeps>;
|
|
25
|
+
};
|
|
26
|
+
type AnyFactoryProvider = FactoryProvider<unknown, any>;
|
|
27
|
+
type Provider = ValueProvider<unknown> | AnyFactoryProvider;
|
|
28
|
+
type Scope = "singleton" | "transient";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/provider/provider.d.ts
|
|
31
|
+
declare const provideValue: <T>(token: Token<T>, useValue: T) => ValueProvider<T>;
|
|
32
|
+
declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(token: Token<T>, options: {
|
|
33
|
+
readonly deps?: TDeps;
|
|
34
|
+
readonly scope?: Scope;
|
|
35
|
+
readonly useFactory: Factory<T, TDeps>;
|
|
36
|
+
}) => FactoryProvider<T, TDeps>;
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/container/types.d.ts
|
|
39
|
+
type Container = {
|
|
40
|
+
register(provider: Provider): void;
|
|
41
|
+
get<T>(token: Token<T>): T;
|
|
42
|
+
has(token: Token<unknown>): boolean;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/container/container.d.ts
|
|
46
|
+
declare const createContainer: (providers?: readonly Provider[]) => Container;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/error/error.d.ts
|
|
49
|
+
declare class DiError extends Error {
|
|
50
|
+
constructor(message: string);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/registry/errors.d.ts
|
|
54
|
+
declare class DuplicateProviderError extends DiError {
|
|
55
|
+
constructor(tokenName: string);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/resolver/errors.d.ts
|
|
59
|
+
declare class MissingProviderError extends DiError {
|
|
60
|
+
constructor(tokenName: string);
|
|
61
|
+
}
|
|
62
|
+
declare class InvalidDependencyError extends DiError {
|
|
63
|
+
constructor(dependencyKey: string);
|
|
64
|
+
}
|
|
65
|
+
declare class CircularDependencyError extends DiError {
|
|
66
|
+
constructor(tokenNames: string[]);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { CircularDependencyError, type Container, DiError, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type Scope, type Token, type ValueProvider, createContainer, createToken, provideFactory, provideValue };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
|
+
//#region src/error/error.ts
|
|
2
|
+
var DiError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "DiError";
|
|
6
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/registry/errors.ts
|
|
11
|
+
var DuplicateProviderError = class extends DiError {
|
|
12
|
+
constructor(tokenName) {
|
|
13
|
+
super(`Provider for token "${tokenName}" is already registered`);
|
|
14
|
+
this.name = "DuplicateProviderError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/registry/registry.ts
|
|
19
|
+
var RegistryClass = class {
|
|
20
|
+
providers = /* @__PURE__ */ new Map();
|
|
21
|
+
register(provider) {
|
|
22
|
+
if (this.providers.has(provider.provide.id)) throw new DuplicateProviderError(provider.provide.name);
|
|
23
|
+
this.providers.set(provider.provide.id, provider);
|
|
24
|
+
}
|
|
25
|
+
get(token) {
|
|
26
|
+
return this.providers.get(token.id);
|
|
27
|
+
}
|
|
28
|
+
has(token) {
|
|
29
|
+
return this.providers.has(token.id);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const createRegistry = () => new RegistryClass();
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/resolver/errors.ts
|
|
35
|
+
var MissingProviderError = class extends DiError {
|
|
36
|
+
constructor(tokenName) {
|
|
37
|
+
super(`Provider for token "${tokenName}" is not registered`);
|
|
38
|
+
this.name = "MissingProviderError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var InvalidDependencyError = class extends DiError {
|
|
42
|
+
constructor(dependencyKey) {
|
|
43
|
+
super(`Invalid dependency "${dependencyKey}"`);
|
|
44
|
+
this.name = "InvalidDependencyError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var CircularDependencyError = class extends DiError {
|
|
48
|
+
constructor(tokenNames) {
|
|
49
|
+
super(`Circular dependency detected: ${tokenNames.join(" -> ")}`);
|
|
50
|
+
this.name = "CircularDependencyError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/provider/provider.ts
|
|
55
|
+
const provideValue = (token, useValue) => ({
|
|
56
|
+
provide: token,
|
|
57
|
+
useValue
|
|
58
|
+
});
|
|
59
|
+
const provideFactory = (token, options) => {
|
|
60
|
+
return {
|
|
61
|
+
provide: token,
|
|
62
|
+
useFactory: options.useFactory,
|
|
63
|
+
...options.deps ? { deps: options.deps } : {},
|
|
64
|
+
...options.scope ? { scope: options.scope } : {}
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const isValueProvider = (provider) => {
|
|
68
|
+
return "useValue" in provider;
|
|
69
|
+
};
|
|
70
|
+
const isFactoryProvider = (provider) => {
|
|
71
|
+
return "useFactory" in provider;
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/resolver/resolver.ts
|
|
75
|
+
var ResolverClass = class {
|
|
76
|
+
registry;
|
|
77
|
+
instances = /* @__PURE__ */ new Map();
|
|
78
|
+
constructor(registry) {
|
|
79
|
+
this.registry = registry;
|
|
80
|
+
}
|
|
81
|
+
resolve(token) {
|
|
82
|
+
return this.resolveToken(token, {
|
|
83
|
+
resolving: /* @__PURE__ */ new Set(),
|
|
84
|
+
path: []
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
resolveToken(token, context) {
|
|
88
|
+
const provider = this.registry.get(token);
|
|
89
|
+
if (!provider) throw new MissingProviderError(token.name);
|
|
90
|
+
if (isValueProvider(provider)) return provider.useValue;
|
|
91
|
+
if (isFactoryProvider(provider)) {
|
|
92
|
+
const scope = provider.scope ?? "singleton";
|
|
93
|
+
if (scope === "singleton" && this.instances.has(token.id)) return this.instances.get(token.id);
|
|
94
|
+
if (context.resolving.has(token.id)) {
|
|
95
|
+
const cycleStartIndex = context.path.findIndex((pathToken) => pathToken.id === token.id);
|
|
96
|
+
throw new CircularDependencyError([...context.path.slice(cycleStartIndex), token].map((cycleToken) => cycleToken.name));
|
|
97
|
+
}
|
|
98
|
+
context.resolving.add(token.id);
|
|
99
|
+
context.path.push(token);
|
|
100
|
+
try {
|
|
101
|
+
const deps = this.resolveDeps(provider.deps, context);
|
|
102
|
+
const value = provider.useFactory(deps);
|
|
103
|
+
if (scope === "singleton") this.instances.set(token.id, value);
|
|
104
|
+
return value;
|
|
105
|
+
} finally {
|
|
106
|
+
context.path.pop();
|
|
107
|
+
context.resolving.delete(token.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new MissingProviderError(token.name);
|
|
111
|
+
}
|
|
112
|
+
resolveDeps(deps, context) {
|
|
113
|
+
if (!deps) return {};
|
|
114
|
+
const resolvedDeps = {};
|
|
115
|
+
for (const key of Object.keys(deps)) {
|
|
116
|
+
const token = deps[key];
|
|
117
|
+
if (token === void 0) throw new InvalidDependencyError(String(key));
|
|
118
|
+
resolvedDeps[key] = this.resolveToken(token, context);
|
|
119
|
+
}
|
|
120
|
+
return resolvedDeps;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const createResolver = (registry) => new ResolverClass(registry);
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/container/container.ts
|
|
126
|
+
var ContainerClass = class {
|
|
127
|
+
registry;
|
|
128
|
+
resolver;
|
|
129
|
+
constructor(providers = []) {
|
|
130
|
+
this.registry = createRegistry();
|
|
131
|
+
for (const provider of providers) this.registry.register(provider);
|
|
132
|
+
this.resolver = createResolver(this.registry);
|
|
133
|
+
}
|
|
134
|
+
register(provider) {
|
|
135
|
+
this.registry.register(provider);
|
|
136
|
+
}
|
|
137
|
+
get(token) {
|
|
138
|
+
return this.resolver.resolve(token);
|
|
139
|
+
}
|
|
140
|
+
has(token) {
|
|
141
|
+
return this.registry.has(token);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const createContainer = (providers = []) => new ContainerClass(providers);
|
|
145
|
+
//#endregion
|
|
1
146
|
//#region src/token/token.ts
|
|
2
147
|
var TokenClass = class {
|
|
3
148
|
name;
|
|
@@ -9,4 +154,4 @@ var TokenClass = class {
|
|
|
9
154
|
};
|
|
10
155
|
const createToken = (name) => new TokenClass(name);
|
|
11
156
|
//#endregion
|
|
12
|
-
export { createToken };
|
|
157
|
+
export { CircularDependencyError, DiError, DuplicateProviderError, InvalidDependencyError, MissingProviderError, createContainer, createToken, provideFactory, provideValue };
|