di-craft 0.0.11 → 0.0.13
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 +107 -69
- package/dist/index.cjs +2 -240
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -9
- package/dist/index.d.mts +22 -9
- package/dist/index.mjs +2 -229
- package/dist/index.mjs.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -11,12 +11,14 @@ A tiny, type-safe dependency injection container for TypeScript.
|
|
|
11
11
|
- [Core concepts](#core-concepts)
|
|
12
12
|
- [Tokens](#tokens)
|
|
13
13
|
- [Providers](#providers)
|
|
14
|
+
- [Optional dependencies](#optional-dependencies)
|
|
14
15
|
- [Container](#container)
|
|
15
16
|
- [Scopes](#scopes)
|
|
16
17
|
- [Disposal](#disposal)
|
|
17
18
|
- [Child containers](#child-containers)
|
|
18
19
|
- [Cycle detection](#cycle-detection)
|
|
19
|
-
- [
|
|
20
|
+
- [Async dependencies](#async-dependencies)
|
|
21
|
+
- [Dependency injection vs service location](#dependency-injection-vs-service-location)
|
|
20
22
|
- [Error handling](#error-handling)
|
|
21
23
|
- [API reference](#api-reference)
|
|
22
24
|
- [License](#license)
|
|
@@ -67,6 +69,7 @@ framework coupling. You work with just **tokens**, **providers**, a **container*
|
|
|
67
69
|
|
|
68
70
|
- Zero runtime dependencies
|
|
69
71
|
- Type-safe tokens and factories
|
|
72
|
+
- Optional dependencies via `optional()`
|
|
70
73
|
- Singleton, transient, and scoped lifetimes
|
|
71
74
|
- Hierarchical child containers
|
|
72
75
|
- Deterministic disposal with `onDispose` hooks
|
|
@@ -121,6 +124,37 @@ provideFactory(HTTP, {
|
|
|
121
124
|
|
|
122
125
|
The keys in `deps` become the keys of the object passed to `useFactory`, each resolved to its token's type.
|
|
123
126
|
|
|
127
|
+
### Optional dependencies
|
|
128
|
+
|
|
129
|
+
Wrap a token with `optional` to mark a dependency as not required. When no
|
|
130
|
+
provider for it is registered anywhere in the container chain, the factory
|
|
131
|
+
receives `undefined` instead of the resolution throwing `MissingProviderError`.
|
|
132
|
+
The inferred type is widened to `T | undefined`, so TypeScript forces you to
|
|
133
|
+
handle the absent case:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { optional, provideFactory } from "di-craft";
|
|
137
|
+
|
|
138
|
+
provideFactory(USERS, {
|
|
139
|
+
deps: { logger: optional(LOGGER) }, // LOGGER may or may not be registered
|
|
140
|
+
useFactory: ({ logger }) => {
|
|
141
|
+
logger?.info("creating users service"); // logger: Logger | undefined
|
|
142
|
+
return new UsersService();
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The same descriptor works at the top level — `optional` can be passed anywhere a
|
|
148
|
+
dependency is accepted, including `container.get`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const logger = container.get(optional(LOGGER)); // Logger | undefined
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Optional only affects the token itself: if a provider _is_ registered, it is
|
|
155
|
+
resolved normally and its own errors (cycles, missing nested deps) still
|
|
156
|
+
surface.
|
|
157
|
+
|
|
124
158
|
### Container
|
|
125
159
|
|
|
126
160
|
The container holds your providers and resolves values on demand. Create one
|
|
@@ -129,7 +163,7 @@ dispose:
|
|
|
129
163
|
|
|
130
164
|
- `register(provider, options?)` — add a provider at any time.
|
|
131
165
|
- `has(token)` — whether a provider for the token is registered.
|
|
132
|
-
- `get(token)` — resolve the value, building and caching it as its scope dictates.
|
|
166
|
+
- `get(token)` — resolve the value, building and caching it as its scope dictates. Accepts `optional(token)` to get `undefined` instead of throwing when absent.
|
|
133
167
|
- `dispose()` — run `onDispose` hooks and release resolved instances.
|
|
134
168
|
|
|
135
169
|
```ts
|
|
@@ -150,7 +184,10 @@ container.register(provideValue(API, fakeApi), { allowOverride: true });
|
|
|
150
184
|
```
|
|
151
185
|
|
|
152
186
|
Overriding a token whose value was already resolved as a singleton drops the
|
|
153
|
-
cached instance, so the next `get` rebuilds it from the new provider.
|
|
187
|
+
cached instance, so the next `get` rebuilds it from the new provider. If that
|
|
188
|
+
resolved instance has an `onDispose` hook, the override throws
|
|
189
|
+
`InvalidProviderError` instead of silently dropping it — call `dispose()` first
|
|
190
|
+
so the resource is released, then register the replacement.
|
|
154
191
|
|
|
155
192
|
### Scopes
|
|
156
193
|
|
|
@@ -173,7 +210,12 @@ provideFactory(ID, {
|
|
|
173
210
|
container.get(ID) !== container.get(ID); // true
|
|
174
211
|
```
|
|
175
212
|
|
|
176
|
-
A
|
|
213
|
+
A provider may only depend on dependencies that live **at least as long** as
|
|
214
|
+
itself, so a longer-lived instance never captures a shorter-lived one. A
|
|
215
|
+
transient may depend on anything; a scoped may depend on scoped or singleton; a
|
|
216
|
+
singleton may depend only on singletons (and values). Violating this throws
|
|
217
|
+
`InvalidProviderError` at resolution. A transient that depends on a singleton, for
|
|
218
|
+
example, reuses the shared singleton instance.
|
|
177
219
|
|
|
178
220
|
### Disposal
|
|
179
221
|
|
|
@@ -201,7 +243,8 @@ Details:
|
|
|
201
243
|
- Hooks run in reverse creation order (dependents before their dependencies).
|
|
202
244
|
- `dispose()` returns a promise and awaits async hooks.
|
|
203
245
|
- It is idempotent — calling it again is a no-op.
|
|
204
|
-
- Only resolved singletons are disposed;
|
|
246
|
+
- Only resolved singletons (and scoped instances on their container) are disposed; never-resolved instances are not tracked.
|
|
247
|
+
- `onDispose` is only meaningful for cached instances. Declaring it on a `transient` provider throws `InvalidProviderError`, since transient instances are never tracked and the hook could never run.
|
|
205
248
|
|
|
206
249
|
### Child containers
|
|
207
250
|
|
|
@@ -247,86 +290,79 @@ If providers form a dependency cycle, resolution throws `CircularDependencyError
|
|
|
247
290
|
container.get(A); // throws: Circular dependency detected: A -> B -> A
|
|
248
291
|
```
|
|
249
292
|
|
|
250
|
-
|
|
293
|
+
### Async dependencies
|
|
251
294
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
295
|
+
di-craft resolves synchronously by design — there is no `getAsync`, and async
|
|
296
|
+
never colors the rest of your graph. Asynchronous values are handled with one of
|
|
297
|
+
two patterns, which together cover the vast majority of cases.
|
|
298
|
+
|
|
299
|
+
**Resolve first, then register.** Do the async work at your composition root and
|
|
300
|
+
register the resolved value. Simplest and most common:
|
|
257
301
|
|
|
258
302
|
```ts
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
createContainer,
|
|
263
|
-
createChildContainer,
|
|
264
|
-
createToken,
|
|
265
|
-
provideValue,
|
|
266
|
-
provideFactory,
|
|
267
|
-
Scopes,
|
|
268
|
-
} from "di-craft";
|
|
303
|
+
const db = await connectDatabase(config);
|
|
304
|
+
container.register(provideValue(DB, db));
|
|
305
|
+
```
|
|
269
306
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const USERS = createToken<UserService>("users");
|
|
307
|
+
**Promise as value (lazy).** Register a factory that returns a promise. The
|
|
308
|
+
container caches it like any other singleton, so the async work runs once and
|
|
309
|
+
every consumer awaits the same promise:
|
|
274
310
|
|
|
275
|
-
|
|
311
|
+
```ts
|
|
312
|
+
const POOL = createToken<Promise<Pool>>("pool");
|
|
276
313
|
|
|
277
|
-
|
|
278
|
-
const root = createContainer([
|
|
279
|
-
provideFactory(DB, {
|
|
280
|
-
useFactory: () => createPool(process.env.DATABASE_URL),
|
|
281
|
-
onDispose: (pool) => pool.end(), // closed only on shutdown
|
|
282
|
-
}),
|
|
283
|
-
provideFactory(REQUEST_ID, {
|
|
284
|
-
scope: Scopes.Scoped, // one id per request
|
|
285
|
-
deps: { request: REQUEST }, // resolves the value registered on the child
|
|
286
|
-
useFactory: ({ request }) => request.id, // Fastify's built-in request id
|
|
287
|
-
}),
|
|
288
|
-
provideFactory(USERS, {
|
|
289
|
-
scope: Scopes.Scoped, // one service per request...
|
|
290
|
-
deps: { db: DB }, // ...but reuses the shared pool
|
|
291
|
-
useFactory: ({ db }) => new UserService(db),
|
|
292
|
-
}),
|
|
293
|
-
]);
|
|
314
|
+
container.register(provideFactory(POOL, { useFactory: () => createPool() }));
|
|
294
315
|
|
|
295
|
-
|
|
296
|
-
|
|
316
|
+
const pool = await container.get(POOL);
|
|
317
|
+
```
|
|
297
318
|
|
|
298
|
-
|
|
299
|
-
// A fresh child per request, seeded with the request object.
|
|
300
|
-
containers.set(request, createChildContainer(root, [provideValue(REQUEST, request)]));
|
|
301
|
-
});
|
|
319
|
+
A factory that depends on `POOL` receives the promise and awaits it itself:
|
|
302
320
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
321
|
+
```ts
|
|
322
|
+
provideFactory(USERS, {
|
|
323
|
+
deps: { pool: POOL },
|
|
324
|
+
useFactory: async ({ pool }) => new UsersRepo(await pool),
|
|
306
325
|
});
|
|
326
|
+
// USERS is now Token<Promise<UsersRepo>> — consumers await it too.
|
|
327
|
+
```
|
|
307
328
|
|
|
308
|
-
|
|
309
|
-
const container = containers.get(request)!;
|
|
310
|
-
const users = container.get(USERS);
|
|
311
|
-
const id = container.get(REQUEST_ID); // unique to this request
|
|
329
|
+
## Dependency injection vs service location
|
|
312
330
|
|
|
313
|
-
|
|
314
|
-
|
|
331
|
+
di-craft is built for **dependency injection**: dependencies are declared up front
|
|
332
|
+
and handed to your code. The opposite is **service location**, where code reaches
|
|
333
|
+
into a container at runtime to pull what it needs, hiding its real dependencies.
|
|
315
334
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
335
|
+
Two habits keep usage canonical: call `container.get()` only at the **composition
|
|
336
|
+
root** (entrypoint, framework hooks, route handlers), and never pass the container
|
|
337
|
+
into your classes or functions. di-craft enforces the key half for you — **a
|
|
338
|
+
factory only ever receives its declared `deps`, never the container** — so a
|
|
339
|
+
provider physically cannot locate arbitrary services.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// Dependency injection — deps are explicit, the class never sees the container.
|
|
343
|
+
provideFactory(USERS, {
|
|
344
|
+
deps: { repo: REPO, logger: LOGGER },
|
|
345
|
+
useFactory: ({ repo, logger }) => new UserService(repo, logger),
|
|
319
346
|
});
|
|
320
347
|
|
|
321
|
-
|
|
348
|
+
const users = container.get(USERS); // resolved at the root, then injected down
|
|
322
349
|
```
|
|
323
350
|
|
|
324
|
-
|
|
351
|
+
```ts
|
|
352
|
+
// Service location (anti-pattern) — the container is smuggled into domain code.
|
|
353
|
+
class UserService {
|
|
354
|
+
constructor(private container: Container) {}
|
|
355
|
+
|
|
356
|
+
list() {
|
|
357
|
+
const repo = this.container.get(REPO); // hidden, runtime-only dependency
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
325
361
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
362
|
+
The second form compiles, but it hides dependencies and defeats DI. No runtime flag
|
|
363
|
+
can forbid it — `get()` is the same call the composition root relies on — so keep
|
|
364
|
+
resolution at the edges by convention, or enforce it with a lint rule that allows
|
|
365
|
+
`.get()` only in your composition-root files.
|
|
330
366
|
|
|
331
367
|
## Error handling
|
|
332
368
|
|
|
@@ -354,6 +390,7 @@ try {
|
|
|
354
390
|
| `DuplicateProviderError` | A token is registered more than once. |
|
|
355
391
|
| `CircularDependencyError` | Providers form a dependency cycle. |
|
|
356
392
|
| `InvalidDependencyError` | A declared dependency token is missing/undefined. |
|
|
393
|
+
| `InvalidProviderError` | A provider is misconfigured (`onDispose` on a transient, or a dependency that outlives its consumer) or an override would drop a live disposable instance. |
|
|
357
394
|
|
|
358
395
|
## API reference
|
|
359
396
|
|
|
@@ -362,13 +399,14 @@ try {
|
|
|
362
399
|
| `createToken<T>(name)` | Create a unique, typed token. |
|
|
363
400
|
| `provideValue(token, value)` | Provider that returns an existing value. |
|
|
364
401
|
| `provideFactory(token, options)` | Provider that builds a value via a factory. |
|
|
402
|
+
| `optional(token)` | Mark a dependency as optional (resolves to `undefined` when absent). |
|
|
365
403
|
| `createContainer(providers?)` | Create a container, optionally seeded with providers. |
|
|
366
404
|
| `createChildContainer(parent, providers?)` | Create a child container that inherits from `parent`. |
|
|
367
405
|
| `Scopes` | Object of scope values (`Scopes.Singleton`, `Scopes.Transient`, `Scopes.Scoped`). |
|
|
368
406
|
|
|
369
|
-
Exported types: `Container`, `Token`, `Provider`, `ValueProvider`, `FactoryProvider`, `Scope`, `DisposeHook`, `RegisterOptions`.
|
|
407
|
+
Exported types: `Container`, `Token`, `Provider`, `ValueProvider`, `FactoryProvider`, `Dependency`, `OptionalDependency`, `Scope`, `DisposeHook`, `RegisterOptions`.
|
|
370
408
|
|
|
371
|
-
Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`, `CircularDependencyError`, `InvalidDependencyError`.
|
|
409
|
+
Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`, `CircularDependencyError`, `InvalidDependencyError`, `InvalidProviderError`.
|
|
372
410
|
|
|
373
411
|
## License
|
|
374
412
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,240 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,
|
|
2
|
-
//#
|
|
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, options) {
|
|
23
|
-
if (!options?.allowOverride && 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/scope/scope.ts
|
|
56
|
-
const Scopes = {
|
|
57
|
-
Singleton: "singleton",
|
|
58
|
-
Transient: "transient",
|
|
59
|
-
Scoped: "scoped"
|
|
60
|
-
};
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/provider/provider.ts
|
|
63
|
-
const provideValue = (token, useValue) => ({
|
|
64
|
-
provide: token,
|
|
65
|
-
useValue
|
|
66
|
-
});
|
|
67
|
-
const provideFactory = (token, options) => {
|
|
68
|
-
return {
|
|
69
|
-
provide: token,
|
|
70
|
-
useFactory: options.useFactory,
|
|
71
|
-
scope: options.scope ?? Scopes.Singleton,
|
|
72
|
-
...options.deps ? { deps: options.deps } : {},
|
|
73
|
-
...options.onDispose ? { onDispose: options.onDispose } : {}
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
const isValueProvider = (provider) => {
|
|
77
|
-
return "useValue" in provider;
|
|
78
|
-
};
|
|
79
|
-
const isFactoryProvider = (provider) => {
|
|
80
|
-
return "useFactory" in provider;
|
|
81
|
-
};
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region src/store/store.ts
|
|
84
|
-
var StoreClass = class {
|
|
85
|
-
instances = /* @__PURE__ */ new Map();
|
|
86
|
-
get(token) {
|
|
87
|
-
return this.instances.get(token.id);
|
|
88
|
-
}
|
|
89
|
-
set(token, record) {
|
|
90
|
-
this.instances.set(token.id, record);
|
|
91
|
-
}
|
|
92
|
-
delete(token) {
|
|
93
|
-
this.instances.delete(token.id);
|
|
94
|
-
}
|
|
95
|
-
async dispose() {
|
|
96
|
-
const records = [...this.instances.values()].reverse();
|
|
97
|
-
this.instances.clear();
|
|
98
|
-
for (const record of records) if (record.onDispose) await record.onDispose(record.value);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
const createStore = () => new StoreClass();
|
|
102
|
-
//#endregion
|
|
103
|
-
//#region src/resolver/context.ts
|
|
104
|
-
var ResolutionContext = class {
|
|
105
|
-
resolving = /* @__PURE__ */ new Set();
|
|
106
|
-
path = [];
|
|
107
|
-
enter(token) {
|
|
108
|
-
if (this.resolving.has(token.id)) {
|
|
109
|
-
const cycleStartIndex = this.path.findIndex((pathToken) => pathToken.id === token.id);
|
|
110
|
-
throw new CircularDependencyError([...this.path.slice(cycleStartIndex), token].map((cycleToken) => cycleToken.name));
|
|
111
|
-
}
|
|
112
|
-
this.resolving.add(token.id);
|
|
113
|
-
this.path.push(token);
|
|
114
|
-
}
|
|
115
|
-
exit(token) {
|
|
116
|
-
this.path.pop();
|
|
117
|
-
this.resolving.delete(token.id);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/resolver/resolver.ts
|
|
122
|
-
var ResolverClass = class {
|
|
123
|
-
registry;
|
|
124
|
-
store = createStore();
|
|
125
|
-
parent;
|
|
126
|
-
constructor(registry, parent) {
|
|
127
|
-
this.registry = registry;
|
|
128
|
-
this.parent = parent;
|
|
129
|
-
}
|
|
130
|
-
resolve(token) {
|
|
131
|
-
return this.resolveToken(token, new ResolutionContext());
|
|
132
|
-
}
|
|
133
|
-
invalidate(token) {
|
|
134
|
-
this.store.delete(token);
|
|
135
|
-
}
|
|
136
|
-
dispose() {
|
|
137
|
-
return this.store.dispose();
|
|
138
|
-
}
|
|
139
|
-
resolveToken(token, context) {
|
|
140
|
-
const owner = this.findOwner(token);
|
|
141
|
-
if (!owner) throw new MissingProviderError(token.name);
|
|
142
|
-
const provider = owner.registry.get(token);
|
|
143
|
-
if (!provider) throw new MissingProviderError(token.name);
|
|
144
|
-
if (isValueProvider(provider)) return provider.useValue;
|
|
145
|
-
if (isFactoryProvider(provider)) {
|
|
146
|
-
const host = this.selectHost(provider.scope, owner);
|
|
147
|
-
if (host) {
|
|
148
|
-
const cached = host.store.get(token);
|
|
149
|
-
if (cached) return cached.value;
|
|
150
|
-
}
|
|
151
|
-
context.enter(token);
|
|
152
|
-
try {
|
|
153
|
-
const deps = (host ?? this).resolveDeps(provider.deps, context);
|
|
154
|
-
const value = provider.useFactory(deps);
|
|
155
|
-
if (host) host.store.set(token, {
|
|
156
|
-
value,
|
|
157
|
-
...provider.onDispose ? { onDispose: provider.onDispose } : {}
|
|
158
|
-
});
|
|
159
|
-
return value;
|
|
160
|
-
} finally {
|
|
161
|
-
context.exit(token);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
throw new MissingProviderError(token.name);
|
|
165
|
-
}
|
|
166
|
-
findOwner(token) {
|
|
167
|
-
let resolver = this;
|
|
168
|
-
while (resolver) {
|
|
169
|
-
if (resolver.registry.has(token)) return resolver;
|
|
170
|
-
resolver = resolver.parent;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
selectHost(scope, owner) {
|
|
174
|
-
if (scope === Scopes.Transient) return;
|
|
175
|
-
if (scope === Scopes.Scoped) return this;
|
|
176
|
-
return owner;
|
|
177
|
-
}
|
|
178
|
-
resolveDeps(deps, context) {
|
|
179
|
-
if (!deps) return {};
|
|
180
|
-
const resolvedDeps = {};
|
|
181
|
-
for (const key of Object.keys(deps)) {
|
|
182
|
-
const token = deps[key];
|
|
183
|
-
if (token === void 0) throw new InvalidDependencyError(String(key));
|
|
184
|
-
resolvedDeps[key] = this.resolveToken(token, context);
|
|
185
|
-
}
|
|
186
|
-
return resolvedDeps;
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
const createResolver = (registry, parent) => new ResolverClass(registry, parent);
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/container/container.ts
|
|
192
|
-
var ContainerClass = class {
|
|
193
|
-
registry;
|
|
194
|
-
resolver;
|
|
195
|
-
parent;
|
|
196
|
-
constructor(providers = [], parent) {
|
|
197
|
-
this.parent = parent;
|
|
198
|
-
this.registry = createRegistry();
|
|
199
|
-
for (const provider of providers) this.registry.register(provider);
|
|
200
|
-
this.resolver = createResolver(this.registry, parent?.resolver);
|
|
201
|
-
}
|
|
202
|
-
register(provider, options) {
|
|
203
|
-
this.registry.register(provider, options);
|
|
204
|
-
if (options?.allowOverride) this.resolver.invalidate(provider.provide);
|
|
205
|
-
}
|
|
206
|
-
get(token) {
|
|
207
|
-
return this.resolver.resolve(token);
|
|
208
|
-
}
|
|
209
|
-
has(token) {
|
|
210
|
-
return this.registry.has(token) || (this.parent?.has(token) ?? false);
|
|
211
|
-
}
|
|
212
|
-
dispose() {
|
|
213
|
-
return this.resolver.dispose();
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
const createContainer = (providers = []) => new ContainerClass(providers);
|
|
217
|
-
const createChildContainer = (parent, providers = []) => new ContainerClass(providers, parent);
|
|
218
|
-
//#endregion
|
|
219
|
-
//#region src/token/token.ts
|
|
220
|
-
var TokenClass = class {
|
|
221
|
-
name;
|
|
222
|
-
id;
|
|
223
|
-
constructor(name) {
|
|
224
|
-
this.name = name;
|
|
225
|
-
this.id = Symbol(name);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
const createToken = (name) => new TokenClass(name);
|
|
229
|
-
//#endregion
|
|
230
|
-
exports.CircularDependencyError = CircularDependencyError;
|
|
231
|
-
exports.DiError = DiError;
|
|
232
|
-
exports.DuplicateProviderError = DuplicateProviderError;
|
|
233
|
-
exports.InvalidDependencyError = InvalidDependencyError;
|
|
234
|
-
exports.MissingProviderError = MissingProviderError;
|
|
235
|
-
exports.Scopes = Scopes;
|
|
236
|
-
exports.createChildContainer = createChildContainer;
|
|
237
|
-
exports.createContainer = createContainer;
|
|
238
|
-
exports.createToken = createToken;
|
|
239
|
-
exports.provideFactory = provideFactory;
|
|
240
|
-
exports.provideValue = provideValue;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{constructor(e){super(e),this.name=`DiError`,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},t=class extends e{constructor(e){super(e),this.name=`InvalidProviderError`}};const n={Singleton:`singleton`,Transient:`transient`,Scoped:`scoped`},r=(e,t)=>({provide:e,useValue:t}),i=(e,r)=>{if(r.scope===n.Transient&&r.onDispose)throw new t(`onDispose is not supported for transient providers (token "${e.name}"): transient instances are not tracked, so the hook would never run.`);return{provide:e,useFactory:r.useFactory,scope:r.scope??n.Singleton,...r.deps?{deps:r.deps}:{},...r.onDispose?{onDispose:r.onDispose}:{}}},a=e=>({token:e,optional:!0}),o=e=>e.optional===!0,s=e=>`useValue`in e;var c=class extends e{constructor(e){super(`Provider for token "${e}" is already registered`),this.name=`DuplicateProviderError`}},l=class{providers=new Map;register(e,t){let n=this.providers.has(e.provide.id);if(n&&!t?.allowOverride)throw new c(e.provide.name);return this.providers.set(e.provide.id,e),n}get(e){return this.providers.get(e.id)}has(e){return this.providers.has(e.id)}};const u=()=>new l;var d=class extends e{constructor(e){super(`Provider for token "${e}" is not registered`),this.name=`MissingProviderError`}},f=class extends e{constructor(e){super(`Invalid dependency "${e}"`),this.name=`InvalidDependencyError`}},p=class extends e{constructor(e){super(`Circular dependency detected: ${e.join(` -> `)}`),this.name=`CircularDependencyError`}},m=class{instances=new Map;get(e){return this.instances.get(e.id)}set(e,t){this.instances.set(e.id,t)}delete(e){this.instances.delete(e.id)}async dispose(){let e=[...this.instances.values()].reverse();this.instances.clear();for(let t of e)t.onDispose&&await t.onDispose(t.value)}};const h=()=>new m;var g=class{resolving=new Set;path=[];enter(e){if(this.resolving.has(e.id)){let t=this.path.findIndex(t=>t.id===e.id);throw new p([...this.path.slice(t),e].map(e=>e.name))}this.resolving.add(e.id),this.path.push(e)}exit(e){this.path.pop(),this.resolving.delete(e.id)}};const _=e=>e===n.Scoped?1:e===n.Transient?2:0;var v=class{registry;store=h();parent;constructor(e,t){this.registry=e,this.parent=t}resolve(e){return this.resolveToken(e,void 0)}resolveOptional(e){return this.lookupOptional(e,void 0)}invalidate(e){this.store.delete(e)}hasDisposableInstance(e){return this.store.get(e)?.onDispose!==void 0}dispose(){return this.store.dispose()}resolveToken(e,r,i,a){let o=this,c;for(;o&&(c=o.registry.get(e),!c);)o=o.parent;if(!c||!o)throw new d(e.name);if(s(c))return c.useValue;if(i!==void 0&&_(c.scope)>_(i))throw new t(`"${a}" (${i}) cannot depend on "${e.name}" (${c.scope??n.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`);let l=this.selectHost(c.scope,o);if(l){let t=l.store.get(e);if(t)return t.value}let u=r??new g;u.enter(e);try{let t=(l??this).resolveDeps(c.deps,u,c.scope,e.name),n=c.useFactory(t);return l&&l.store.set(e,{value:n,...c.onDispose?{onDispose:c.onDispose}:{}}),n}finally{u.exit(e)}}selectHost(e,t){if(e!==n.Transient)return e===n.Scoped?this:t}resolveDeps(e,t,n,r){if(!e)return{};let i={};for(let a of Object.keys(e)){let s=e[a];if(s===void 0)throw new f(String(a));i[a]=o(s)?this.lookupOptional(s.token,t,n,r):this.resolveToken(s,t,n,r)}return i}lookupOptional(e,t,n,r){let i=this;for(;i;){if(i.registry.has(e))return this.resolveToken(e,t,n,r);i=i.parent}}};const y=(e,t)=>new v(e,t);var b=class{registry;resolver;parent;constructor(e=[],t){this.parent=t,this.registry=u();for(let t of e)this.registry.register(t);this.resolver=y(this.registry,t?.resolver)}register(e,n){if(n?.allowOverride&&this.resolver.hasDisposableInstance(e.provide))throw new t(`Cannot override token "${e.provide.name}": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`);this.registry.register(e,n)&&this.resolver.invalidate(e.provide)}get(e){return o(e)?this.resolver.resolveOptional(e.token):this.resolver.resolve(e)}has(e){return this.registry.has(e)||(this.parent?.has(e)??!1)}dispose(){return this.resolver.dispose()}};const x=(e=[])=>new b(e),S=(e,t=[])=>new b(t,e);var C=class{name;id;constructor(e){this.name=e,this.id=Symbol(e)}};const w=e=>new C(e);exports.CircularDependencyError=p,exports.DiError=e,exports.DuplicateProviderError=c,exports.InvalidDependencyError=f,exports.InvalidProviderError=t,exports.MissingProviderError=d,exports.Scopes=n,exports.createChildContainer=S,exports.createContainer=x,exports.createToken=w,exports.optional=a,exports.provideFactory=i,exports.provideValue=r;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/error/error.ts","../src/provider/errors.ts","../src/scope/scope.ts","../src/provider/provider.ts","../src/registry/errors.ts","../src/registry/registry.ts","../src/resolver/errors.ts","../src/store/store.ts","../src/resolver/context.ts","../src/resolver/resolver.ts","../src/container/container.ts","../src/token/token.ts"],"sourcesContent":["export class DiError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"DiError\";\n\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\t}\n}\n","import { DiError } from \"../error\";\n\nexport class InvalidProviderError extends DiError {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"InvalidProviderError\";\n\t}\n}\n","export const Scopes = {\n\tSingleton: \"singleton\",\n\tTransient: \"transient\",\n\tScoped: \"scoped\",\n} as const;\n\nexport type Scope = (typeof Scopes)[keyof typeof Scopes];\n","import { type Scope, Scopes } from \"../scope\";\nimport type { Token } from \"../token\";\nimport { InvalidProviderError } from \"./errors\";\nimport type {\n\tAnyFactoryProvider,\n\tDependency,\n\tDepsMap,\n\tDisposeHook,\n\tFactory,\n\tFactoryProvider,\n\tOptionalDependency,\n\tProvider,\n\tValueProvider,\n} from \"./types\";\n\nexport const provideValue = <T>(\n\ttoken: Token<T>,\n\tuseValue: T,\n): ValueProvider<T> => ({\n\tprovide: token,\n\tuseValue,\n});\n\nexport const provideFactory = <T, TDeps extends DepsMap = Record<never, never>>(\n\ttoken: Token<T>,\n\toptions: {\n\t\treadonly deps?: TDeps;\n\t\treadonly scope?: Scope;\n\t\treadonly useFactory: Factory<T, TDeps>;\n\t\treadonly onDispose?: DisposeHook<T>;\n\t},\n): FactoryProvider<T, TDeps> => {\n\t// Transient instances aren't cached, so onDispose could never run.\n\tif (options.scope === Scopes.Transient && options.onDispose) {\n\t\tthrow new InvalidProviderError(\n\t\t\t`onDispose is not supported for transient providers (token \"${token.name}\"): transient instances are not tracked, so the hook would never run.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tprovide: token,\n\t\tuseFactory: options.useFactory,\n\t\tscope: options.scope ?? Scopes.Singleton,\n\t\t...(options.deps ? { deps: options.deps } : {}),\n\t\t...(options.onDispose ? { onDispose: options.onDispose } : {}),\n\t};\n};\n\n// Marks a dependency optional: resolves to undefined when no provider exists.\nexport const optional = <T>(token: Token<T>): OptionalDependency<T> => ({\n\ttoken,\n\toptional: true,\n});\n\nexport const isOptionalDependency = <T>(\n\tdependency: Dependency<T>,\n): dependency is OptionalDependency<T> => {\n\treturn (dependency as OptionalDependency<T>).optional === true;\n};\n\nexport const isValueProvider = (\n\tprovider: Provider,\n): provider is ValueProvider<unknown> => {\n\treturn \"useValue\" in provider;\n};\n\nexport const isFactoryProvider = (\n\tprovider: Provider,\n): provider is AnyFactoryProvider => {\n\treturn \"useFactory\" in provider;\n};\n","import { DiError } from \"../error\";\n\nexport class DuplicateProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is already registered`);\n\n\t\tthis.name = \"DuplicateProviderError\";\n\t}\n}\n","import type { Provider } from \"../provider\";\nimport type { Token } from \"../token\";\nimport { DuplicateProviderError } from \"./errors\";\nimport type { RegisterOptions, Registry } from \"./types\";\n\nclass RegistryClass implements Registry {\n\tprivate readonly providers = new Map<symbol, Provider>();\n\n\tregister(provider: Provider, options?: RegisterOptions): boolean {\n\t\tconst existed = this.providers.has(provider.provide.id);\n\n\t\tif (existed && !options?.allowOverride) {\n\t\t\tthrow new DuplicateProviderError(provider.provide.name);\n\t\t}\n\n\t\tthis.providers.set(provider.provide.id, provider);\n\n\t\t// true if an existing provider was replaced (override).\n\t\treturn existed;\n\t}\n\n\tget(token: Token<unknown>): Provider | undefined {\n\t\treturn this.providers.get(token.id);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.providers.has(token.id);\n\t}\n}\n\nexport const createRegistry = (): Registry => new RegistryClass();\n","import { DiError } from \"../error\";\n\nexport class MissingProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is not registered`);\n\n\t\tthis.name = \"MissingProviderError\";\n\t}\n}\n\nexport class InvalidDependencyError extends DiError {\n\tconstructor(dependencyKey: string) {\n\t\tsuper(`Invalid dependency \"${dependencyKey}\"`);\n\n\t\tthis.name = \"InvalidDependencyError\";\n\t}\n}\n\nexport class CircularDependencyError extends DiError {\n\tconstructor(tokenNames: string[]) {\n\t\tsuper(`Circular dependency detected: ${tokenNames.join(\" -> \")}`);\n\n\t\tthis.name = \"CircularDependencyError\";\n\t}\n}\n","import type { Token } from \"../token\";\nimport type { InstanceRecord, Store } from \"./types\";\n\nclass StoreClass implements Store {\n\tprivate readonly instances = new Map<symbol, InstanceRecord>();\n\n\tget(token: Token<unknown>): InstanceRecord | undefined {\n\t\treturn this.instances.get(token.id);\n\t}\n\n\tset(token: Token<unknown>, record: InstanceRecord): void {\n\t\tthis.instances.set(token.id, record);\n\t}\n\n\tdelete(token: Token<unknown>): void {\n\t\tthis.instances.delete(token.id);\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\t// Snapshot and clear first so dispose() is idempotent and re-entrancy safe.\n\t\tconst records = [...this.instances.values()].reverse();\n\n\t\tthis.instances.clear();\n\n\t\tfor (const record of records) {\n\t\t\tif (record.onDispose) {\n\t\t\t\tawait record.onDispose(record.value);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport const createStore = (): Store => new StoreClass();\n","import type { Token } from \"../token\";\nimport { CircularDependencyError } from \"./errors\";\n\nexport class ResolutionContext {\n\tprivate readonly resolving = new Set<symbol>();\n\tprivate readonly path: Token<unknown>[] = [];\n\n\tenter(token: Token<unknown>): void {\n\t\tif (this.resolving.has(token.id)) {\n\t\t\tconst cycleStartIndex = this.path.findIndex(\n\t\t\t\t(pathToken) => pathToken.id === token.id,\n\t\t\t);\n\n\t\t\tconst cycle = [...this.path.slice(cycleStartIndex), token];\n\n\t\t\tthrow new CircularDependencyError(\n\t\t\t\tcycle.map((cycleToken) => cycleToken.name),\n\t\t\t);\n\t\t}\n\n\t\tthis.resolving.add(token.id);\n\t\tthis.path.push(token);\n\t}\n\n\texit(token: Token<unknown>): void {\n\t\tthis.path.pop();\n\t\tthis.resolving.delete(token.id);\n\t}\n}\n","import type { DepsMap, Provider, ResolveDeps } from \"../provider\";\nimport {\n\tInvalidProviderError,\n\tisOptionalDependency,\n\tisValueProvider,\n} from \"../provider\";\nimport type { Registry } from \"../registry\";\nimport { type Scope, Scopes } from \"../scope\";\nimport { createStore, type Store } from \"../store\";\nimport type { Token } from \"../token\";\nimport { ResolutionContext } from \"./context\";\nimport { InvalidDependencyError, MissingProviderError } from \"./errors\";\nimport type { Resolver } from \"./types\";\n\n// Lifetime ordering: longer-lived scopes rank lower. A provider may only depend\n// on dependencies that live at least as long as itself.\nconst lifetimeRank = (scope: Scope | undefined): number =>\n\tscope === Scopes.Scoped ? 1 : scope === Scopes.Transient ? 2 : 0;\n\nclass ResolverClass implements Resolver {\n\tprivate readonly registry: Registry;\n\tprivate readonly store: Store = createStore();\n\tprivate readonly parent: ResolverClass | undefined;\n\n\tconstructor(registry: Registry, parent?: ResolverClass) {\n\t\tthis.registry = registry;\n\t\tthis.parent = parent;\n\t}\n\n\tresolve<T>(token: Token<T>): T {\n\t\treturn this.resolveToken(token, undefined);\n\t}\n\n\tresolveOptional<T>(token: Token<T>): T | undefined {\n\t\treturn this.lookupOptional(token, undefined);\n\t}\n\n\tinvalidate(token: Token<unknown>): void {\n\t\tthis.store.delete(token);\n\t}\n\n\t// True if a locally-cached instance has an onDispose hook.\n\thasDisposableInstance(token: Token<unknown>): boolean {\n\t\treturn this.store.get(token)?.onDispose !== undefined;\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.store.dispose();\n\t}\n\n\t// context is allocated lazily by the first building factory (zero-alloc cache hits).\n\t// consumerScope/consumerName describe the provider depending on this token, if any.\n\tprivate resolveToken<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T {\n\t\t// Single walk up the parent chain, fetching the provider directly.\n\t\tlet owner: ResolverClass | undefined = this;\n\t\tlet provider: Provider | undefined;\n\n\t\twhile (owner) {\n\t\t\tprovider = owner.registry.get(token);\n\n\t\t\tif (provider) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\tif (!provider || !owner) {\n\t\t\tthrow new MissingProviderError(token.name);\n\t\t}\n\n\t\t// Values outlive every scope, so they are always a safe dependency.\n\t\tif (isValueProvider(provider)) {\n\t\t\treturn provider.useValue as T;\n\t\t}\n\n\t\t// Otherwise it is a factory provider (the only remaining variant).\n\t\t// Reject capturing a shorter-lived dependency into a longer-lived consumer.\n\t\tif (\n\t\t\tconsumerScope !== undefined &&\n\t\t\tlifetimeRank(provider.scope) > lifetimeRank(consumerScope)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`\"${consumerName}\" (${consumerScope}) cannot depend on \"${token.name}\" (${provider.scope ?? Scopes.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`,\n\t\t\t);\n\t\t}\n\n\t\t// Scope decides the host: singleton on owner, scoped on this, transient nowhere.\n\t\tconst host = this.selectHost(provider.scope, owner);\n\n\t\tif (host) {\n\t\t\tconst cached = host.store.get(token);\n\n\t\t\tif (cached) {\n\t\t\t\treturn cached.value as T;\n\t\t\t}\n\t\t}\n\n\t\t// Allocate cycle detection only now, threading it through the build.\n\t\tconst ctx = context ?? new ResolutionContext();\n\t\tctx.enter(token);\n\n\t\ttry {\n\t\t\t// Resolve deps from the host: scoped sees this container, singleton sees owner.\n\t\t\tconst deps = (host ?? this).resolveDeps(\n\t\t\t\tprovider.deps,\n\t\t\t\tctx,\n\t\t\t\tprovider.scope,\n\t\t\t\ttoken.name,\n\t\t\t);\n\t\t\tconst value = provider.useFactory(deps) as T;\n\n\t\t\tif (host) {\n\t\t\t\thost.store.set(token, {\n\t\t\t\t\tvalue,\n\t\t\t\t\t...(provider.onDispose ? { onDispose: provider.onDispose } : {}),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tctx.exit(token);\n\t\t}\n\t}\n\n\tprivate selectHost(\n\t\tscope: Scope | undefined,\n\t\towner: ResolverClass,\n\t): ResolverClass | undefined {\n\t\tif (scope === Scopes.Transient) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (scope === Scopes.Scoped) {\n\t\t\treturn this;\n\t\t}\n\n\t\treturn owner;\n\t}\n\n\tprivate resolveDeps<TDeps extends DepsMap>(\n\t\tdeps: TDeps | undefined,\n\t\tcontext: ResolutionContext,\n\t\tconsumerScope: Scope | undefined,\n\t\tconsumerName: string,\n\t): ResolveDeps<TDeps> {\n\t\tif (!deps) {\n\t\t\treturn {} as ResolveDeps<TDeps>;\n\t\t}\n\n\t\tconst resolvedDeps: Partial<ResolveDeps<TDeps>> = {};\n\n\t\tfor (const key of Object.keys(deps) as Array<keyof TDeps>) {\n\t\t\tconst dependency = deps[key];\n\n\t\t\tif (dependency === undefined) {\n\t\t\t\tthrow new InvalidDependencyError(String(key));\n\t\t\t}\n\n\t\t\tresolvedDeps[key] = (\n\t\t\t\tisOptionalDependency(dependency)\n\t\t\t\t\t? this.lookupOptional(\n\t\t\t\t\t\t\tdependency.token,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tconsumerScope,\n\t\t\t\t\t\t\tconsumerName,\n\t\t\t\t\t\t)\n\t\t\t\t\t: this.resolveToken(dependency, context, consumerScope, consumerName)\n\t\t\t) as ResolveDeps<TDeps>[typeof key];\n\t\t}\n\n\t\treturn resolvedDeps as ResolveDeps<TDeps>;\n\t}\n\n\t// Absent provider -> undefined; a present one resolves normally (its errors surface).\n\tprivate lookupOptional<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T | undefined {\n\t\tlet owner: ResolverClass | undefined = this;\n\n\t\twhile (owner) {\n\t\t\tif (owner.registry.has(token)) {\n\t\t\t\treturn this.resolveToken(token, context, consumerScope, consumerName);\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n\nexport const createResolver = (\n\tregistry: Registry,\n\tparent?: Resolver,\n): Resolver => new ResolverClass(registry, parent as ResolverClass | undefined);\n","import {\n\ttype Dependency,\n\tInvalidProviderError,\n\tisOptionalDependency,\n\ttype OptionalDependency,\n\ttype Provider,\n} from \"../provider\";\nimport {\n\tcreateRegistry,\n\ttype RegisterOptions,\n\ttype Registry,\n} from \"../registry\";\nimport { createResolver, type Resolver } from \"../resolver\";\nimport type { Token } from \"../token\";\nimport type { Container } from \"./types\";\n\nclass ContainerClass implements Container {\n\tprivate readonly registry: Registry;\n\tprivate readonly resolver: Resolver;\n\tprivate readonly parent: ContainerClass | undefined;\n\n\tconstructor(providers: readonly Provider[] = [], parent?: ContainerClass) {\n\t\tthis.parent = parent;\n\t\tthis.registry = createRegistry();\n\n\t\tfor (const provider of providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\n\t\tthis.resolver = createResolver(this.registry, parent?.resolver);\n\t}\n\n\tregister(provider: Provider, options?: RegisterOptions): void {\n\t\t// Don't silently drop a live disposable instance; require explicit dispose.\n\t\tif (\n\t\t\toptions?.allowOverride &&\n\t\t\tthis.resolver.hasDisposableInstance(provider.provide)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`Cannot override token \"${provider.provide.name}\": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`,\n\t\t\t);\n\t\t}\n\n\t\tconst overridden = this.registry.register(provider, options);\n\n\t\tif (overridden) {\n\t\t\tthis.resolver.invalidate(provider.provide);\n\t\t}\n\t}\n\n\tget<T>(token: Token<T>): T;\n\tget<T>(dependency: OptionalDependency<T>): T | undefined;\n\tget<T>(dependency: Dependency<T>): T | undefined {\n\t\tif (isOptionalDependency(dependency)) {\n\t\t\treturn this.resolver.resolveOptional(dependency.token);\n\t\t}\n\n\t\treturn this.resolver.resolve(dependency);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.registry.has(token) || (this.parent?.has(token) ?? false);\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.resolver.dispose();\n\t}\n}\n\nexport const createContainer = (\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers);\n\nexport const createChildContainer = (\n\tparent: Container,\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers, parent as ContainerClass);\n","import type { Token } from \"./types\";\n\nclass TokenClass<T> implements Token<T> {\n\treadonly name: string;\n\treadonly id: symbol;\n\n\tdeclare readonly __type?: T;\n\n\tconstructor(name: string) {\n\t\tthis.name = name;\n\t\tthis.id = Symbol(name);\n\t}\n}\n\nexport const createToken = <T>(name: string): Token<T> =>\n\tnew TokenClass<T>(name);\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//#region src/error/error.d.ts
|
|
2
|
+
declare class DiError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/provider/errors.d.ts
|
|
7
|
+
declare class InvalidProviderError extends DiError {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
1
11
|
//#region src/scope/scope.d.ts
|
|
2
12
|
declare const Scopes: {
|
|
3
13
|
readonly Singleton: "singleton";
|
|
@@ -17,9 +27,14 @@ type Token<T> = {
|
|
|
17
27
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
18
28
|
//#endregion
|
|
19
29
|
//#region src/provider/types.d.ts
|
|
20
|
-
type
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
type OptionalDependency<T> = {
|
|
31
|
+
readonly token: Token<T>;
|
|
32
|
+
readonly optional: true;
|
|
33
|
+
};
|
|
34
|
+
type Dependency<T> = Token<T> | OptionalDependency<T>;
|
|
35
|
+
type DepsMap = Record<string, Dependency<unknown>>;
|
|
36
|
+
type DependencyValue<TDep> = TDep extends OptionalDependency<infer T> ? T | undefined : TDep extends Token<infer T> ? T : never;
|
|
37
|
+
type ResolveDeps<TDeps extends DepsMap> = { readonly [TKey in keyof TDeps]: DependencyValue<TDeps[TKey]> };
|
|
23
38
|
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
24
39
|
type DisposeHook<T> = (instance: T) => void | Promise<void>;
|
|
25
40
|
type ValueProvider<T> = {
|
|
@@ -44,11 +59,7 @@ declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(
|
|
|
44
59
|
readonly useFactory: Factory<T, TDeps>;
|
|
45
60
|
readonly onDispose?: DisposeHook<T>;
|
|
46
61
|
}) => FactoryProvider<T, TDeps>;
|
|
47
|
-
|
|
48
|
-
//#region src/error/error.d.ts
|
|
49
|
-
declare class DiError extends Error {
|
|
50
|
-
constructor(message: string);
|
|
51
|
-
}
|
|
62
|
+
declare const optional: <T>(token: Token<T>) => OptionalDependency<T>;
|
|
52
63
|
//#endregion
|
|
53
64
|
//#region src/registry/errors.d.ts
|
|
54
65
|
declare class DuplicateProviderError extends DiError {
|
|
@@ -64,6 +75,7 @@ type RegisterOptions = {
|
|
|
64
75
|
type Container = {
|
|
65
76
|
register(provider: Provider, options?: RegisterOptions): void;
|
|
66
77
|
get<T>(token: Token<T>): T;
|
|
78
|
+
get<T>(dependency: OptionalDependency<T>): T | undefined;
|
|
67
79
|
has(token: Token<unknown>): boolean;
|
|
68
80
|
dispose(): Promise<void>;
|
|
69
81
|
};
|
|
@@ -83,4 +95,5 @@ declare class CircularDependencyError extends DiError {
|
|
|
83
95
|
constructor(tokenNames: string[]);
|
|
84
96
|
}
|
|
85
97
|
//#endregion
|
|
86
|
-
export { CircularDependencyError, type Container, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, provideFactory, provideValue };
|
|
98
|
+
export { CircularDependencyError, type Container, type Dependency, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, InvalidProviderError, MissingProviderError, type OptionalDependency, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, optional, provideFactory, provideValue };
|
|
99
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
//#region src/error/error.d.ts
|
|
2
|
+
declare class DiError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/provider/errors.d.ts
|
|
7
|
+
declare class InvalidProviderError extends DiError {
|
|
8
|
+
constructor(message: string);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
1
11
|
//#region src/scope/scope.d.ts
|
|
2
12
|
declare const Scopes: {
|
|
3
13
|
readonly Singleton: "singleton";
|
|
@@ -17,9 +27,14 @@ type Token<T> = {
|
|
|
17
27
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
18
28
|
//#endregion
|
|
19
29
|
//#region src/provider/types.d.ts
|
|
20
|
-
type
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
type OptionalDependency<T> = {
|
|
31
|
+
readonly token: Token<T>;
|
|
32
|
+
readonly optional: true;
|
|
33
|
+
};
|
|
34
|
+
type Dependency<T> = Token<T> | OptionalDependency<T>;
|
|
35
|
+
type DepsMap = Record<string, Dependency<unknown>>;
|
|
36
|
+
type DependencyValue<TDep> = TDep extends OptionalDependency<infer T> ? T | undefined : TDep extends Token<infer T> ? T : never;
|
|
37
|
+
type ResolveDeps<TDeps extends DepsMap> = { readonly [TKey in keyof TDeps]: DependencyValue<TDeps[TKey]> };
|
|
23
38
|
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
24
39
|
type DisposeHook<T> = (instance: T) => void | Promise<void>;
|
|
25
40
|
type ValueProvider<T> = {
|
|
@@ -44,11 +59,7 @@ declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(
|
|
|
44
59
|
readonly useFactory: Factory<T, TDeps>;
|
|
45
60
|
readonly onDispose?: DisposeHook<T>;
|
|
46
61
|
}) => FactoryProvider<T, TDeps>;
|
|
47
|
-
|
|
48
|
-
//#region src/error/error.d.ts
|
|
49
|
-
declare class DiError extends Error {
|
|
50
|
-
constructor(message: string);
|
|
51
|
-
}
|
|
62
|
+
declare const optional: <T>(token: Token<T>) => OptionalDependency<T>;
|
|
52
63
|
//#endregion
|
|
53
64
|
//#region src/registry/errors.d.ts
|
|
54
65
|
declare class DuplicateProviderError extends DiError {
|
|
@@ -64,6 +75,7 @@ type RegisterOptions = {
|
|
|
64
75
|
type Container = {
|
|
65
76
|
register(provider: Provider, options?: RegisterOptions): void;
|
|
66
77
|
get<T>(token: Token<T>): T;
|
|
78
|
+
get<T>(dependency: OptionalDependency<T>): T | undefined;
|
|
67
79
|
has(token: Token<unknown>): boolean;
|
|
68
80
|
dispose(): Promise<void>;
|
|
69
81
|
};
|
|
@@ -83,4 +95,5 @@ declare class CircularDependencyError extends DiError {
|
|
|
83
95
|
constructor(tokenNames: string[]);
|
|
84
96
|
}
|
|
85
97
|
//#endregion
|
|
86
|
-
export { CircularDependencyError, type Container, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, provideFactory, provideValue };
|
|
98
|
+
export { CircularDependencyError, type Container, type Dependency, DiError, type DisposeHook, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, InvalidProviderError, MissingProviderError, type OptionalDependency, type Provider, type RegisterOptions, type Scope, Scopes, type Token, type ValueProvider, createChildContainer, createContainer, createToken, optional, provideFactory, provideValue };
|
|
99
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,229 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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, options) {
|
|
22
|
-
if (!options?.allowOverride && 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/scope/scope.ts
|
|
55
|
-
const Scopes = {
|
|
56
|
-
Singleton: "singleton",
|
|
57
|
-
Transient: "transient",
|
|
58
|
-
Scoped: "scoped"
|
|
59
|
-
};
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region src/provider/provider.ts
|
|
62
|
-
const provideValue = (token, useValue) => ({
|
|
63
|
-
provide: token,
|
|
64
|
-
useValue
|
|
65
|
-
});
|
|
66
|
-
const provideFactory = (token, options) => {
|
|
67
|
-
return {
|
|
68
|
-
provide: token,
|
|
69
|
-
useFactory: options.useFactory,
|
|
70
|
-
scope: options.scope ?? Scopes.Singleton,
|
|
71
|
-
...options.deps ? { deps: options.deps } : {},
|
|
72
|
-
...options.onDispose ? { onDispose: options.onDispose } : {}
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
const isValueProvider = (provider) => {
|
|
76
|
-
return "useValue" in provider;
|
|
77
|
-
};
|
|
78
|
-
const isFactoryProvider = (provider) => {
|
|
79
|
-
return "useFactory" in provider;
|
|
80
|
-
};
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/store/store.ts
|
|
83
|
-
var StoreClass = class {
|
|
84
|
-
instances = /* @__PURE__ */ new Map();
|
|
85
|
-
get(token) {
|
|
86
|
-
return this.instances.get(token.id);
|
|
87
|
-
}
|
|
88
|
-
set(token, record) {
|
|
89
|
-
this.instances.set(token.id, record);
|
|
90
|
-
}
|
|
91
|
-
delete(token) {
|
|
92
|
-
this.instances.delete(token.id);
|
|
93
|
-
}
|
|
94
|
-
async dispose() {
|
|
95
|
-
const records = [...this.instances.values()].reverse();
|
|
96
|
-
this.instances.clear();
|
|
97
|
-
for (const record of records) if (record.onDispose) await record.onDispose(record.value);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const createStore = () => new StoreClass();
|
|
101
|
-
//#endregion
|
|
102
|
-
//#region src/resolver/context.ts
|
|
103
|
-
var ResolutionContext = class {
|
|
104
|
-
resolving = /* @__PURE__ */ new Set();
|
|
105
|
-
path = [];
|
|
106
|
-
enter(token) {
|
|
107
|
-
if (this.resolving.has(token.id)) {
|
|
108
|
-
const cycleStartIndex = this.path.findIndex((pathToken) => pathToken.id === token.id);
|
|
109
|
-
throw new CircularDependencyError([...this.path.slice(cycleStartIndex), token].map((cycleToken) => cycleToken.name));
|
|
110
|
-
}
|
|
111
|
-
this.resolving.add(token.id);
|
|
112
|
-
this.path.push(token);
|
|
113
|
-
}
|
|
114
|
-
exit(token) {
|
|
115
|
-
this.path.pop();
|
|
116
|
-
this.resolving.delete(token.id);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
//#endregion
|
|
120
|
-
//#region src/resolver/resolver.ts
|
|
121
|
-
var ResolverClass = class {
|
|
122
|
-
registry;
|
|
123
|
-
store = createStore();
|
|
124
|
-
parent;
|
|
125
|
-
constructor(registry, parent) {
|
|
126
|
-
this.registry = registry;
|
|
127
|
-
this.parent = parent;
|
|
128
|
-
}
|
|
129
|
-
resolve(token) {
|
|
130
|
-
return this.resolveToken(token, new ResolutionContext());
|
|
131
|
-
}
|
|
132
|
-
invalidate(token) {
|
|
133
|
-
this.store.delete(token);
|
|
134
|
-
}
|
|
135
|
-
dispose() {
|
|
136
|
-
return this.store.dispose();
|
|
137
|
-
}
|
|
138
|
-
resolveToken(token, context) {
|
|
139
|
-
const owner = this.findOwner(token);
|
|
140
|
-
if (!owner) throw new MissingProviderError(token.name);
|
|
141
|
-
const provider = owner.registry.get(token);
|
|
142
|
-
if (!provider) throw new MissingProviderError(token.name);
|
|
143
|
-
if (isValueProvider(provider)) return provider.useValue;
|
|
144
|
-
if (isFactoryProvider(provider)) {
|
|
145
|
-
const host = this.selectHost(provider.scope, owner);
|
|
146
|
-
if (host) {
|
|
147
|
-
const cached = host.store.get(token);
|
|
148
|
-
if (cached) return cached.value;
|
|
149
|
-
}
|
|
150
|
-
context.enter(token);
|
|
151
|
-
try {
|
|
152
|
-
const deps = (host ?? this).resolveDeps(provider.deps, context);
|
|
153
|
-
const value = provider.useFactory(deps);
|
|
154
|
-
if (host) host.store.set(token, {
|
|
155
|
-
value,
|
|
156
|
-
...provider.onDispose ? { onDispose: provider.onDispose } : {}
|
|
157
|
-
});
|
|
158
|
-
return value;
|
|
159
|
-
} finally {
|
|
160
|
-
context.exit(token);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
throw new MissingProviderError(token.name);
|
|
164
|
-
}
|
|
165
|
-
findOwner(token) {
|
|
166
|
-
let resolver = this;
|
|
167
|
-
while (resolver) {
|
|
168
|
-
if (resolver.registry.has(token)) return resolver;
|
|
169
|
-
resolver = resolver.parent;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
selectHost(scope, owner) {
|
|
173
|
-
if (scope === Scopes.Transient) return;
|
|
174
|
-
if (scope === Scopes.Scoped) return this;
|
|
175
|
-
return owner;
|
|
176
|
-
}
|
|
177
|
-
resolveDeps(deps, context) {
|
|
178
|
-
if (!deps) return {};
|
|
179
|
-
const resolvedDeps = {};
|
|
180
|
-
for (const key of Object.keys(deps)) {
|
|
181
|
-
const token = deps[key];
|
|
182
|
-
if (token === void 0) throw new InvalidDependencyError(String(key));
|
|
183
|
-
resolvedDeps[key] = this.resolveToken(token, context);
|
|
184
|
-
}
|
|
185
|
-
return resolvedDeps;
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
const createResolver = (registry, parent) => new ResolverClass(registry, parent);
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/container/container.ts
|
|
191
|
-
var ContainerClass = class {
|
|
192
|
-
registry;
|
|
193
|
-
resolver;
|
|
194
|
-
parent;
|
|
195
|
-
constructor(providers = [], parent) {
|
|
196
|
-
this.parent = parent;
|
|
197
|
-
this.registry = createRegistry();
|
|
198
|
-
for (const provider of providers) this.registry.register(provider);
|
|
199
|
-
this.resolver = createResolver(this.registry, parent?.resolver);
|
|
200
|
-
}
|
|
201
|
-
register(provider, options) {
|
|
202
|
-
this.registry.register(provider, options);
|
|
203
|
-
if (options?.allowOverride) this.resolver.invalidate(provider.provide);
|
|
204
|
-
}
|
|
205
|
-
get(token) {
|
|
206
|
-
return this.resolver.resolve(token);
|
|
207
|
-
}
|
|
208
|
-
has(token) {
|
|
209
|
-
return this.registry.has(token) || (this.parent?.has(token) ?? false);
|
|
210
|
-
}
|
|
211
|
-
dispose() {
|
|
212
|
-
return this.resolver.dispose();
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
const createContainer = (providers = []) => new ContainerClass(providers);
|
|
216
|
-
const createChildContainer = (parent, providers = []) => new ContainerClass(providers, parent);
|
|
217
|
-
//#endregion
|
|
218
|
-
//#region src/token/token.ts
|
|
219
|
-
var TokenClass = class {
|
|
220
|
-
name;
|
|
221
|
-
id;
|
|
222
|
-
constructor(name) {
|
|
223
|
-
this.name = name;
|
|
224
|
-
this.id = Symbol(name);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
const createToken = (name) => new TokenClass(name);
|
|
228
|
-
//#endregion
|
|
229
|
-
export { CircularDependencyError, DiError, DuplicateProviderError, InvalidDependencyError, MissingProviderError, Scopes, createChildContainer, createContainer, createToken, provideFactory, provideValue };
|
|
1
|
+
var e=class extends Error{constructor(e){super(e),this.name=`DiError`,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},t=class extends e{constructor(e){super(e),this.name=`InvalidProviderError`}};const n={Singleton:`singleton`,Transient:`transient`,Scoped:`scoped`},r=(e,t)=>({provide:e,useValue:t}),i=(e,r)=>{if(r.scope===n.Transient&&r.onDispose)throw new t(`onDispose is not supported for transient providers (token "${e.name}"): transient instances are not tracked, so the hook would never run.`);return{provide:e,useFactory:r.useFactory,scope:r.scope??n.Singleton,...r.deps?{deps:r.deps}:{},...r.onDispose?{onDispose:r.onDispose}:{}}},a=e=>({token:e,optional:!0}),o=e=>e.optional===!0,s=e=>`useValue`in e;var c=class extends e{constructor(e){super(`Provider for token "${e}" is already registered`),this.name=`DuplicateProviderError`}},l=class{providers=new Map;register(e,t){let n=this.providers.has(e.provide.id);if(n&&!t?.allowOverride)throw new c(e.provide.name);return this.providers.set(e.provide.id,e),n}get(e){return this.providers.get(e.id)}has(e){return this.providers.has(e.id)}};const u=()=>new l;var d=class extends e{constructor(e){super(`Provider for token "${e}" is not registered`),this.name=`MissingProviderError`}},f=class extends e{constructor(e){super(`Invalid dependency "${e}"`),this.name=`InvalidDependencyError`}},p=class extends e{constructor(e){super(`Circular dependency detected: ${e.join(` -> `)}`),this.name=`CircularDependencyError`}},m=class{instances=new Map;get(e){return this.instances.get(e.id)}set(e,t){this.instances.set(e.id,t)}delete(e){this.instances.delete(e.id)}async dispose(){let e=[...this.instances.values()].reverse();this.instances.clear();for(let t of e)t.onDispose&&await t.onDispose(t.value)}};const h=()=>new m;var g=class{resolving=new Set;path=[];enter(e){if(this.resolving.has(e.id)){let t=this.path.findIndex(t=>t.id===e.id);throw new p([...this.path.slice(t),e].map(e=>e.name))}this.resolving.add(e.id),this.path.push(e)}exit(e){this.path.pop(),this.resolving.delete(e.id)}};const _=e=>e===n.Scoped?1:e===n.Transient?2:0;var v=class{registry;store=h();parent;constructor(e,t){this.registry=e,this.parent=t}resolve(e){return this.resolveToken(e,void 0)}resolveOptional(e){return this.lookupOptional(e,void 0)}invalidate(e){this.store.delete(e)}hasDisposableInstance(e){return this.store.get(e)?.onDispose!==void 0}dispose(){return this.store.dispose()}resolveToken(e,r,i,a){let o=this,c;for(;o&&(c=o.registry.get(e),!c);)o=o.parent;if(!c||!o)throw new d(e.name);if(s(c))return c.useValue;if(i!==void 0&&_(c.scope)>_(i))throw new t(`"${a}" (${i}) cannot depend on "${e.name}" (${c.scope??n.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`);let l=this.selectHost(c.scope,o);if(l){let t=l.store.get(e);if(t)return t.value}let u=r??new g;u.enter(e);try{let t=(l??this).resolveDeps(c.deps,u,c.scope,e.name),n=c.useFactory(t);return l&&l.store.set(e,{value:n,...c.onDispose?{onDispose:c.onDispose}:{}}),n}finally{u.exit(e)}}selectHost(e,t){if(e!==n.Transient)return e===n.Scoped?this:t}resolveDeps(e,t,n,r){if(!e)return{};let i={};for(let a of Object.keys(e)){let s=e[a];if(s===void 0)throw new f(String(a));i[a]=o(s)?this.lookupOptional(s.token,t,n,r):this.resolveToken(s,t,n,r)}return i}lookupOptional(e,t,n,r){let i=this;for(;i;){if(i.registry.has(e))return this.resolveToken(e,t,n,r);i=i.parent}}};const y=(e,t)=>new v(e,t);var b=class{registry;resolver;parent;constructor(e=[],t){this.parent=t,this.registry=u();for(let t of e)this.registry.register(t);this.resolver=y(this.registry,t?.resolver)}register(e,n){if(n?.allowOverride&&this.resolver.hasDisposableInstance(e.provide))throw new t(`Cannot override token "${e.provide.name}": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`);this.registry.register(e,n)&&this.resolver.invalidate(e.provide)}get(e){return o(e)?this.resolver.resolveOptional(e.token):this.resolver.resolve(e)}has(e){return this.registry.has(e)||(this.parent?.has(e)??!1)}dispose(){return this.resolver.dispose()}};const x=(e=[])=>new b(e),S=(e,t=[])=>new b(t,e);var C=class{name;id;constructor(e){this.name=e,this.id=Symbol(e)}};const w=e=>new C(e);export{p as CircularDependencyError,e as DiError,c as DuplicateProviderError,f as InvalidDependencyError,t as InvalidProviderError,d as MissingProviderError,n as Scopes,S as createChildContainer,x as createContainer,w as createToken,a as optional,i as provideFactory,r as provideValue};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/error/error.ts","../src/provider/errors.ts","../src/scope/scope.ts","../src/provider/provider.ts","../src/registry/errors.ts","../src/registry/registry.ts","../src/resolver/errors.ts","../src/store/store.ts","../src/resolver/context.ts","../src/resolver/resolver.ts","../src/container/container.ts","../src/token/token.ts"],"sourcesContent":["export class DiError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"DiError\";\n\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\t}\n}\n","import { DiError } from \"../error\";\n\nexport class InvalidProviderError extends DiError {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = \"InvalidProviderError\";\n\t}\n}\n","export const Scopes = {\n\tSingleton: \"singleton\",\n\tTransient: \"transient\",\n\tScoped: \"scoped\",\n} as const;\n\nexport type Scope = (typeof Scopes)[keyof typeof Scopes];\n","import { type Scope, Scopes } from \"../scope\";\nimport type { Token } from \"../token\";\nimport { InvalidProviderError } from \"./errors\";\nimport type {\n\tAnyFactoryProvider,\n\tDependency,\n\tDepsMap,\n\tDisposeHook,\n\tFactory,\n\tFactoryProvider,\n\tOptionalDependency,\n\tProvider,\n\tValueProvider,\n} from \"./types\";\n\nexport const provideValue = <T>(\n\ttoken: Token<T>,\n\tuseValue: T,\n): ValueProvider<T> => ({\n\tprovide: token,\n\tuseValue,\n});\n\nexport const provideFactory = <T, TDeps extends DepsMap = Record<never, never>>(\n\ttoken: Token<T>,\n\toptions: {\n\t\treadonly deps?: TDeps;\n\t\treadonly scope?: Scope;\n\t\treadonly useFactory: Factory<T, TDeps>;\n\t\treadonly onDispose?: DisposeHook<T>;\n\t},\n): FactoryProvider<T, TDeps> => {\n\t// Transient instances aren't cached, so onDispose could never run.\n\tif (options.scope === Scopes.Transient && options.onDispose) {\n\t\tthrow new InvalidProviderError(\n\t\t\t`onDispose is not supported for transient providers (token \"${token.name}\"): transient instances are not tracked, so the hook would never run.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tprovide: token,\n\t\tuseFactory: options.useFactory,\n\t\tscope: options.scope ?? Scopes.Singleton,\n\t\t...(options.deps ? { deps: options.deps } : {}),\n\t\t...(options.onDispose ? { onDispose: options.onDispose } : {}),\n\t};\n};\n\n// Marks a dependency optional: resolves to undefined when no provider exists.\nexport const optional = <T>(token: Token<T>): OptionalDependency<T> => ({\n\ttoken,\n\toptional: true,\n});\n\nexport const isOptionalDependency = <T>(\n\tdependency: Dependency<T>,\n): dependency is OptionalDependency<T> => {\n\treturn (dependency as OptionalDependency<T>).optional === true;\n};\n\nexport const isValueProvider = (\n\tprovider: Provider,\n): provider is ValueProvider<unknown> => {\n\treturn \"useValue\" in provider;\n};\n\nexport const isFactoryProvider = (\n\tprovider: Provider,\n): provider is AnyFactoryProvider => {\n\treturn \"useFactory\" in provider;\n};\n","import { DiError } from \"../error\";\n\nexport class DuplicateProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is already registered`);\n\n\t\tthis.name = \"DuplicateProviderError\";\n\t}\n}\n","import type { Provider } from \"../provider\";\nimport type { Token } from \"../token\";\nimport { DuplicateProviderError } from \"./errors\";\nimport type { RegisterOptions, Registry } from \"./types\";\n\nclass RegistryClass implements Registry {\n\tprivate readonly providers = new Map<symbol, Provider>();\n\n\tregister(provider: Provider, options?: RegisterOptions): boolean {\n\t\tconst existed = this.providers.has(provider.provide.id);\n\n\t\tif (existed && !options?.allowOverride) {\n\t\t\tthrow new DuplicateProviderError(provider.provide.name);\n\t\t}\n\n\t\tthis.providers.set(provider.provide.id, provider);\n\n\t\t// true if an existing provider was replaced (override).\n\t\treturn existed;\n\t}\n\n\tget(token: Token<unknown>): Provider | undefined {\n\t\treturn this.providers.get(token.id);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.providers.has(token.id);\n\t}\n}\n\nexport const createRegistry = (): Registry => new RegistryClass();\n","import { DiError } from \"../error\";\n\nexport class MissingProviderError extends DiError {\n\tconstructor(tokenName: string) {\n\t\tsuper(`Provider for token \"${tokenName}\" is not registered`);\n\n\t\tthis.name = \"MissingProviderError\";\n\t}\n}\n\nexport class InvalidDependencyError extends DiError {\n\tconstructor(dependencyKey: string) {\n\t\tsuper(`Invalid dependency \"${dependencyKey}\"`);\n\n\t\tthis.name = \"InvalidDependencyError\";\n\t}\n}\n\nexport class CircularDependencyError extends DiError {\n\tconstructor(tokenNames: string[]) {\n\t\tsuper(`Circular dependency detected: ${tokenNames.join(\" -> \")}`);\n\n\t\tthis.name = \"CircularDependencyError\";\n\t}\n}\n","import type { Token } from \"../token\";\nimport type { InstanceRecord, Store } from \"./types\";\n\nclass StoreClass implements Store {\n\tprivate readonly instances = new Map<symbol, InstanceRecord>();\n\n\tget(token: Token<unknown>): InstanceRecord | undefined {\n\t\treturn this.instances.get(token.id);\n\t}\n\n\tset(token: Token<unknown>, record: InstanceRecord): void {\n\t\tthis.instances.set(token.id, record);\n\t}\n\n\tdelete(token: Token<unknown>): void {\n\t\tthis.instances.delete(token.id);\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\t// Snapshot and clear first so dispose() is idempotent and re-entrancy safe.\n\t\tconst records = [...this.instances.values()].reverse();\n\n\t\tthis.instances.clear();\n\n\t\tfor (const record of records) {\n\t\t\tif (record.onDispose) {\n\t\t\t\tawait record.onDispose(record.value);\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport const createStore = (): Store => new StoreClass();\n","import type { Token } from \"../token\";\nimport { CircularDependencyError } from \"./errors\";\n\nexport class ResolutionContext {\n\tprivate readonly resolving = new Set<symbol>();\n\tprivate readonly path: Token<unknown>[] = [];\n\n\tenter(token: Token<unknown>): void {\n\t\tif (this.resolving.has(token.id)) {\n\t\t\tconst cycleStartIndex = this.path.findIndex(\n\t\t\t\t(pathToken) => pathToken.id === token.id,\n\t\t\t);\n\n\t\t\tconst cycle = [...this.path.slice(cycleStartIndex), token];\n\n\t\t\tthrow new CircularDependencyError(\n\t\t\t\tcycle.map((cycleToken) => cycleToken.name),\n\t\t\t);\n\t\t}\n\n\t\tthis.resolving.add(token.id);\n\t\tthis.path.push(token);\n\t}\n\n\texit(token: Token<unknown>): void {\n\t\tthis.path.pop();\n\t\tthis.resolving.delete(token.id);\n\t}\n}\n","import type { DepsMap, Provider, ResolveDeps } from \"../provider\";\nimport {\n\tInvalidProviderError,\n\tisOptionalDependency,\n\tisValueProvider,\n} from \"../provider\";\nimport type { Registry } from \"../registry\";\nimport { type Scope, Scopes } from \"../scope\";\nimport { createStore, type Store } from \"../store\";\nimport type { Token } from \"../token\";\nimport { ResolutionContext } from \"./context\";\nimport { InvalidDependencyError, MissingProviderError } from \"./errors\";\nimport type { Resolver } from \"./types\";\n\n// Lifetime ordering: longer-lived scopes rank lower. A provider may only depend\n// on dependencies that live at least as long as itself.\nconst lifetimeRank = (scope: Scope | undefined): number =>\n\tscope === Scopes.Scoped ? 1 : scope === Scopes.Transient ? 2 : 0;\n\nclass ResolverClass implements Resolver {\n\tprivate readonly registry: Registry;\n\tprivate readonly store: Store = createStore();\n\tprivate readonly parent: ResolverClass | undefined;\n\n\tconstructor(registry: Registry, parent?: ResolverClass) {\n\t\tthis.registry = registry;\n\t\tthis.parent = parent;\n\t}\n\n\tresolve<T>(token: Token<T>): T {\n\t\treturn this.resolveToken(token, undefined);\n\t}\n\n\tresolveOptional<T>(token: Token<T>): T | undefined {\n\t\treturn this.lookupOptional(token, undefined);\n\t}\n\n\tinvalidate(token: Token<unknown>): void {\n\t\tthis.store.delete(token);\n\t}\n\n\t// True if a locally-cached instance has an onDispose hook.\n\thasDisposableInstance(token: Token<unknown>): boolean {\n\t\treturn this.store.get(token)?.onDispose !== undefined;\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.store.dispose();\n\t}\n\n\t// context is allocated lazily by the first building factory (zero-alloc cache hits).\n\t// consumerScope/consumerName describe the provider depending on this token, if any.\n\tprivate resolveToken<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T {\n\t\t// Single walk up the parent chain, fetching the provider directly.\n\t\tlet owner: ResolverClass | undefined = this;\n\t\tlet provider: Provider | undefined;\n\n\t\twhile (owner) {\n\t\t\tprovider = owner.registry.get(token);\n\n\t\t\tif (provider) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\tif (!provider || !owner) {\n\t\t\tthrow new MissingProviderError(token.name);\n\t\t}\n\n\t\t// Values outlive every scope, so they are always a safe dependency.\n\t\tif (isValueProvider(provider)) {\n\t\t\treturn provider.useValue as T;\n\t\t}\n\n\t\t// Otherwise it is a factory provider (the only remaining variant).\n\t\t// Reject capturing a shorter-lived dependency into a longer-lived consumer.\n\t\tif (\n\t\t\tconsumerScope !== undefined &&\n\t\t\tlifetimeRank(provider.scope) > lifetimeRank(consumerScope)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`\"${consumerName}\" (${consumerScope}) cannot depend on \"${token.name}\" (${provider.scope ?? Scopes.Singleton}): a longer-lived provider would capture a shorter-lived one. Widen the dependency's scope or narrow the consumer's.`,\n\t\t\t);\n\t\t}\n\n\t\t// Scope decides the host: singleton on owner, scoped on this, transient nowhere.\n\t\tconst host = this.selectHost(provider.scope, owner);\n\n\t\tif (host) {\n\t\t\tconst cached = host.store.get(token);\n\n\t\t\tif (cached) {\n\t\t\t\treturn cached.value as T;\n\t\t\t}\n\t\t}\n\n\t\t// Allocate cycle detection only now, threading it through the build.\n\t\tconst ctx = context ?? new ResolutionContext();\n\t\tctx.enter(token);\n\n\t\ttry {\n\t\t\t// Resolve deps from the host: scoped sees this container, singleton sees owner.\n\t\t\tconst deps = (host ?? this).resolveDeps(\n\t\t\t\tprovider.deps,\n\t\t\t\tctx,\n\t\t\t\tprovider.scope,\n\t\t\t\ttoken.name,\n\t\t\t);\n\t\t\tconst value = provider.useFactory(deps) as T;\n\n\t\t\tif (host) {\n\t\t\t\thost.store.set(token, {\n\t\t\t\t\tvalue,\n\t\t\t\t\t...(provider.onDispose ? { onDispose: provider.onDispose } : {}),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn value;\n\t\t} finally {\n\t\t\tctx.exit(token);\n\t\t}\n\t}\n\n\tprivate selectHost(\n\t\tscope: Scope | undefined,\n\t\towner: ResolverClass,\n\t): ResolverClass | undefined {\n\t\tif (scope === Scopes.Transient) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (scope === Scopes.Scoped) {\n\t\t\treturn this;\n\t\t}\n\n\t\treturn owner;\n\t}\n\n\tprivate resolveDeps<TDeps extends DepsMap>(\n\t\tdeps: TDeps | undefined,\n\t\tcontext: ResolutionContext,\n\t\tconsumerScope: Scope | undefined,\n\t\tconsumerName: string,\n\t): ResolveDeps<TDeps> {\n\t\tif (!deps) {\n\t\t\treturn {} as ResolveDeps<TDeps>;\n\t\t}\n\n\t\tconst resolvedDeps: Partial<ResolveDeps<TDeps>> = {};\n\n\t\tfor (const key of Object.keys(deps) as Array<keyof TDeps>) {\n\t\t\tconst dependency = deps[key];\n\n\t\t\tif (dependency === undefined) {\n\t\t\t\tthrow new InvalidDependencyError(String(key));\n\t\t\t}\n\n\t\t\tresolvedDeps[key] = (\n\t\t\t\tisOptionalDependency(dependency)\n\t\t\t\t\t? this.lookupOptional(\n\t\t\t\t\t\t\tdependency.token,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\tconsumerScope,\n\t\t\t\t\t\t\tconsumerName,\n\t\t\t\t\t\t)\n\t\t\t\t\t: this.resolveToken(dependency, context, consumerScope, consumerName)\n\t\t\t) as ResolveDeps<TDeps>[typeof key];\n\t\t}\n\n\t\treturn resolvedDeps as ResolveDeps<TDeps>;\n\t}\n\n\t// Absent provider -> undefined; a present one resolves normally (its errors surface).\n\tprivate lookupOptional<T>(\n\t\ttoken: Token<T>,\n\t\tcontext: ResolutionContext | undefined,\n\t\tconsumerScope?: Scope,\n\t\tconsumerName?: string,\n\t): T | undefined {\n\t\tlet owner: ResolverClass | undefined = this;\n\n\t\twhile (owner) {\n\t\t\tif (owner.registry.has(token)) {\n\t\t\t\treturn this.resolveToken(token, context, consumerScope, consumerName);\n\t\t\t}\n\n\t\t\towner = owner.parent;\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n\nexport const createResolver = (\n\tregistry: Registry,\n\tparent?: Resolver,\n): Resolver => new ResolverClass(registry, parent as ResolverClass | undefined);\n","import {\n\ttype Dependency,\n\tInvalidProviderError,\n\tisOptionalDependency,\n\ttype OptionalDependency,\n\ttype Provider,\n} from \"../provider\";\nimport {\n\tcreateRegistry,\n\ttype RegisterOptions,\n\ttype Registry,\n} from \"../registry\";\nimport { createResolver, type Resolver } from \"../resolver\";\nimport type { Token } from \"../token\";\nimport type { Container } from \"./types\";\n\nclass ContainerClass implements Container {\n\tprivate readonly registry: Registry;\n\tprivate readonly resolver: Resolver;\n\tprivate readonly parent: ContainerClass | undefined;\n\n\tconstructor(providers: readonly Provider[] = [], parent?: ContainerClass) {\n\t\tthis.parent = parent;\n\t\tthis.registry = createRegistry();\n\n\t\tfor (const provider of providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\n\t\tthis.resolver = createResolver(this.registry, parent?.resolver);\n\t}\n\n\tregister(provider: Provider, options?: RegisterOptions): void {\n\t\t// Don't silently drop a live disposable instance; require explicit dispose.\n\t\tif (\n\t\t\toptions?.allowOverride &&\n\t\t\tthis.resolver.hasDisposableInstance(provider.provide)\n\t\t) {\n\t\t\tthrow new InvalidProviderError(\n\t\t\t\t`Cannot override token \"${provider.provide.name}\": its instance was already created and has an onDispose hook. Dispose the container before replacing it.`,\n\t\t\t);\n\t\t}\n\n\t\tconst overridden = this.registry.register(provider, options);\n\n\t\tif (overridden) {\n\t\t\tthis.resolver.invalidate(provider.provide);\n\t\t}\n\t}\n\n\tget<T>(token: Token<T>): T;\n\tget<T>(dependency: OptionalDependency<T>): T | undefined;\n\tget<T>(dependency: Dependency<T>): T | undefined {\n\t\tif (isOptionalDependency(dependency)) {\n\t\t\treturn this.resolver.resolveOptional(dependency.token);\n\t\t}\n\n\t\treturn this.resolver.resolve(dependency);\n\t}\n\n\thas(token: Token<unknown>): boolean {\n\t\treturn this.registry.has(token) || (this.parent?.has(token) ?? false);\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.resolver.dispose();\n\t}\n}\n\nexport const createContainer = (\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers);\n\nexport const createChildContainer = (\n\tparent: Container,\n\tproviders: readonly Provider[] = [],\n): Container => new ContainerClass(providers, parent as ContainerClass);\n","import type { Token } from \"./types\";\n\nclass TokenClass<T> implements Token<T> {\n\treadonly name: string;\n\treadonly id: symbol;\n\n\tdeclare readonly __type?: T;\n\n\tconstructor(name: string) {\n\t\tthis.name = name;\n\t\tthis.id = Symbol(name);\n\t}\n}\n\nexport const createToken = <T>(name: string): Token<T> =>\n\tnew TokenClass<T>(name);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "di-craft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "A tiny, type-safe dependency injection container for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -51,8 +51,12 @@
|
|
|
51
51
|
"dependency-injection",
|
|
52
52
|
"di",
|
|
53
53
|
"ioc",
|
|
54
|
+
"inversion-of-control",
|
|
54
55
|
"typescript",
|
|
55
|
-
"
|
|
56
|
+
"type-safe",
|
|
57
|
+
"container",
|
|
58
|
+
"scoped",
|
|
59
|
+
"tree-shakable"
|
|
56
60
|
],
|
|
57
61
|
"homepage": "https://github.com/bezmen-e/di-craft",
|
|
58
62
|
"repository": {
|