di-craft 0.0.10 → 0.0.12
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 +245 -18
- package/dist/index.cjs +2 -166
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -17
- package/dist/index.d.mts +47 -17
- package/dist/index.mjs +2 -157
- package/dist/index.mjs.map +1 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
A tiny, type-safe dependency injection container for TypeScript.
|
|
4
4
|
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Quick start](#quick-start)
|
|
8
|
+
- [Philosophy](#philosophy)
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Install](#install)
|
|
11
|
+
- [Core concepts](#core-concepts)
|
|
12
|
+
- [Tokens](#tokens)
|
|
13
|
+
- [Providers](#providers)
|
|
14
|
+
- [Optional dependencies](#optional-dependencies)
|
|
15
|
+
- [Container](#container)
|
|
16
|
+
- [Scopes](#scopes)
|
|
17
|
+
- [Disposal](#disposal)
|
|
18
|
+
- [Child containers](#child-containers)
|
|
19
|
+
- [Cycle detection](#cycle-detection)
|
|
20
|
+
- [Async dependencies](#async-dependencies)
|
|
21
|
+
- [Dependency injection vs service location](#dependency-injection-vs-service-location)
|
|
22
|
+
- [Error handling](#error-handling)
|
|
23
|
+
- [API reference](#api-reference)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
Declare typed tokens, describe how each one is built with a provider, then create
|
|
29
|
+
a container and resolve from it. Dependencies are wired explicitly through the
|
|
30
|
+
`deps` map and resolved for you.
|
|
31
|
+
|
|
5
32
|
```ts
|
|
6
33
|
import {
|
|
7
34
|
createContainer,
|
|
@@ -34,23 +61,18 @@ const users = container.get(USERS); // UserService, fully typed
|
|
|
34
61
|
|
|
35
62
|
## Philosophy
|
|
36
63
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- No `reflect-metadata`
|
|
41
|
-
- No framework dependencies
|
|
42
|
-
|
|
43
|
-
Just **tokens**, **providers**, a **container**, **scopes**, and **cycle detection**.
|
|
64
|
+
Dependency injection without the magic — no decorators, no `reflect-metadata`, no
|
|
65
|
+
framework coupling. You work with just **tokens**, **providers**, a **container**,
|
|
66
|
+
**scopes**, and **cycle detection**.
|
|
44
67
|
|
|
45
68
|
## Features
|
|
46
69
|
|
|
47
70
|
- Zero runtime dependencies
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- Singleton and transient scopes
|
|
71
|
+
- Type-safe tokens and factories
|
|
72
|
+
- Optional dependencies via `optional()`
|
|
73
|
+
- Singleton, transient, and scoped lifetimes
|
|
74
|
+
- Hierarchical child containers
|
|
75
|
+
- Deterministic disposal with `onDispose` hooks
|
|
54
76
|
- Circular dependency detection
|
|
55
77
|
- Tree-shakable, tiny bundle size
|
|
56
78
|
- Ships both ESM and CommonJS builds
|
|
@@ -102,17 +124,70 @@ provideFactory(HTTP, {
|
|
|
102
124
|
|
|
103
125
|
The keys in `deps` become the keys of the object passed to `useFactory`, each resolved to its token's type.
|
|
104
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
|
+
|
|
105
158
|
### Container
|
|
106
159
|
|
|
160
|
+
The container holds your providers and resolves values on demand. Create one
|
|
161
|
+
from a list of providers (all optional), then add more, check, resolve, and
|
|
162
|
+
dispose:
|
|
163
|
+
|
|
164
|
+
- `register(provider, options?)` — add a provider at any time.
|
|
165
|
+
- `has(token)` — whether a provider for the token is registered.
|
|
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.
|
|
167
|
+
- `dispose()` — run `onDispose` hooks and release resolved instances.
|
|
168
|
+
|
|
107
169
|
```ts
|
|
108
170
|
const container = createContainer(providers); // providers are optional
|
|
109
171
|
|
|
110
172
|
container.register(provideValue(PORT, 3000)); // register more at any time
|
|
111
173
|
container.has(PORT); // true
|
|
112
174
|
container.get(PORT); // 3000
|
|
175
|
+
await container.dispose(); // release resolved singletons
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Registering the same token twice throws `DuplicateProviderError`. To replace an
|
|
179
|
+
existing provider on purpose (handy for tests, mocks, and environment-specific
|
|
180
|
+
overrides), pass `{ allowOverride: true }`:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
container.register(provideValue(API, fakeApi), { allowOverride: true });
|
|
113
184
|
```
|
|
114
185
|
|
|
115
|
-
|
|
186
|
+
Overriding a token whose value was already resolved as a singleton drops the
|
|
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.
|
|
116
191
|
|
|
117
192
|
### Scopes
|
|
118
193
|
|
|
@@ -120,17 +195,91 @@ Registering the same token twice throws `DuplicateProviderError`.
|
|
|
120
195
|
| ---------------------- | ---------------------------------------------------------------- |
|
|
121
196
|
| `singleton` (default) | The factory runs once; the same instance is returned every time. |
|
|
122
197
|
| `transient` | The factory runs on every `get`, producing a fresh instance. |
|
|
198
|
+
| `scoped` | One instance per container. In a child container each child gets its own instance, while the provider can still be declared once on the parent. |
|
|
199
|
+
|
|
200
|
+
Use the `Scopes` helper for autocompletion, or pass the plain string — both work:
|
|
123
201
|
|
|
124
202
|
```ts
|
|
203
|
+
import { Scopes, provideFactory } from "di-craft";
|
|
204
|
+
|
|
125
205
|
provideFactory(ID, {
|
|
126
|
-
scope: "transient"
|
|
206
|
+
scope: Scopes.Transient, // or scope: "transient"
|
|
127
207
|
useFactory: () => crypto.randomUUID(),
|
|
128
208
|
});
|
|
129
209
|
|
|
130
210
|
container.get(ID) !== container.get(ID); // true
|
|
131
211
|
```
|
|
132
212
|
|
|
133
|
-
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.
|
|
219
|
+
|
|
220
|
+
### Disposal
|
|
221
|
+
|
|
222
|
+
Factory providers can declare an `onDispose` hook to release resources (database
|
|
223
|
+
pools, sockets, timers, subscriptions). Calling `container.dispose()` runs the
|
|
224
|
+
hooks for every resolved singleton and clears the cache:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const DB = createToken<Pool>("db");
|
|
228
|
+
|
|
229
|
+
const container = createContainer([
|
|
230
|
+
provideFactory(DB, {
|
|
231
|
+
useFactory: () => createPool(url),
|
|
232
|
+
onDispose: (pool) => pool.end(), // may be sync or async
|
|
233
|
+
}),
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
container.get(DB);
|
|
237
|
+
|
|
238
|
+
await container.dispose(); // awaits async hooks, then clears instances
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Details:
|
|
242
|
+
|
|
243
|
+
- Hooks run in reverse creation order (dependents before their dependencies).
|
|
244
|
+
- `dispose()` returns a promise and awaits async hooks.
|
|
245
|
+
- It is idempotent — calling it again is a no-op.
|
|
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.
|
|
248
|
+
|
|
249
|
+
### Child containers
|
|
250
|
+
|
|
251
|
+
`createChildContainer(parent, providers?)` creates a child that inherits
|
|
252
|
+
everything from its parent but can add or override providers locally. This is the
|
|
253
|
+
typical pattern for per-request isolation on a server: shared services live on
|
|
254
|
+
the root, request-specific values live on a short-lived child.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
const root = createContainer([
|
|
258
|
+
provideFactory(LOGGER, { useFactory: () => console }), // singleton, shared
|
|
259
|
+
provideFactory(HANDLER, {
|
|
260
|
+
scope: Scopes.Scoped, // one instance per child
|
|
261
|
+
deps: { request: REQUEST },
|
|
262
|
+
useFactory: ({ request }) => createHandler(request),
|
|
263
|
+
}),
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
function handle(request: Request) {
|
|
267
|
+
const child = createChildContainer(root, [provideValue(REQUEST, request)]);
|
|
268
|
+
|
|
269
|
+
child.get(LOGGER); // same logger as the root and every other child
|
|
270
|
+
child.get(HANDLER); // a fresh handler, unique to this child
|
|
271
|
+
|
|
272
|
+
return child.dispose(); // release only this child's instances
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
How resolution works across the chain:
|
|
277
|
+
|
|
278
|
+
- A token is looked up in the child first, then walks up to the parent.
|
|
279
|
+
- `singleton` is cached on the container that **owns** the provider, so it is shared by the whole subtree.
|
|
280
|
+
- `scoped` is cached on the **requesting** child, so each child gets its own instance — even when the provider is declared once on the parent.
|
|
281
|
+
- A `scoped` provider resolves its dependencies from the requesting child, so it can depend on values registered only in that child (like `REQUEST`).
|
|
282
|
+
- `dispose()` only releases the container it is called on; it does not cascade to parents or children.
|
|
134
283
|
|
|
135
284
|
### Cycle detection
|
|
136
285
|
|
|
@@ -141,6 +290,80 @@ If providers form a dependency cycle, resolution throws `CircularDependencyError
|
|
|
141
290
|
container.get(A); // throws: Circular dependency detected: A -> B -> A
|
|
142
291
|
```
|
|
143
292
|
|
|
293
|
+
### Async dependencies
|
|
294
|
+
|
|
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:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const db = await connectDatabase(config);
|
|
304
|
+
container.register(provideValue(DB, db));
|
|
305
|
+
```
|
|
306
|
+
|
|
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:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const POOL = createToken<Promise<Pool>>("pool");
|
|
313
|
+
|
|
314
|
+
container.register(provideFactory(POOL, { useFactory: () => createPool() }));
|
|
315
|
+
|
|
316
|
+
const pool = await container.get(POOL);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
A factory that depends on `POOL` receives the promise and awaits it itself:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
provideFactory(USERS, {
|
|
323
|
+
deps: { pool: POOL },
|
|
324
|
+
useFactory: async ({ pool }) => new UsersRepo(await pool),
|
|
325
|
+
});
|
|
326
|
+
// USERS is now Token<Promise<UsersRepo>> — consumers await it too.
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Dependency injection vs service location
|
|
330
|
+
|
|
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.
|
|
334
|
+
|
|
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),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const users = container.get(USERS); // resolved at the root, then injected down
|
|
349
|
+
```
|
|
350
|
+
|
|
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
|
+
```
|
|
361
|
+
|
|
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.
|
|
366
|
+
|
|
144
367
|
## Error handling
|
|
145
368
|
|
|
146
369
|
All errors extend the shared `DiError` base class, so you can catch any container error with a single check:
|
|
@@ -167,6 +390,7 @@ try {
|
|
|
167
390
|
| `DuplicateProviderError` | A token is registered more than once. |
|
|
168
391
|
| `CircularDependencyError` | Providers form a dependency cycle. |
|
|
169
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. |
|
|
170
394
|
|
|
171
395
|
## API reference
|
|
172
396
|
|
|
@@ -175,11 +399,14 @@ try {
|
|
|
175
399
|
| `createToken<T>(name)` | Create a unique, typed token. |
|
|
176
400
|
| `provideValue(token, value)` | Provider that returns an existing value. |
|
|
177
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). |
|
|
178
403
|
| `createContainer(providers?)` | Create a container, optionally seeded with providers. |
|
|
404
|
+
| `createChildContainer(parent, providers?)` | Create a child container that inherits from `parent`. |
|
|
405
|
+
| `Scopes` | Object of scope values (`Scopes.Singleton`, `Scopes.Transient`, `Scopes.Scoped`). |
|
|
179
406
|
|
|
180
|
-
Exported types: `Container`, `Token`, `Provider`, `ValueProvider`, `FactoryProvider`, `Scope`.
|
|
407
|
+
Exported types: `Container`, `Token`, `Provider`, `ValueProvider`, `FactoryProvider`, `Dependency`, `OptionalDependency`, `Scope`, `DisposeHook`, `RegisterOptions`.
|
|
181
408
|
|
|
182
|
-
Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`, `CircularDependencyError`, `InvalidDependencyError`.
|
|
409
|
+
Exported errors: `DiError`, `MissingProviderError`, `DuplicateProviderError`, `CircularDependencyError`, `InvalidDependencyError`, `InvalidProviderError`.
|
|
183
410
|
|
|
184
411
|
## License
|
|
185
412
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,166 +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) {
|
|
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
|
|
147
|
-
//#region src/token/token.ts
|
|
148
|
-
var TokenClass = class {
|
|
149
|
-
name;
|
|
150
|
-
id;
|
|
151
|
-
constructor(name) {
|
|
152
|
-
this.name = name;
|
|
153
|
-
this.id = Symbol(name);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const createToken = (name) => new TokenClass(name);
|
|
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;
|
|
164
|
-
exports.createToken = createToken;
|
|
165
|
-
exports.provideFactory = provideFactory;
|
|
166
|
-
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,21 @@
|
|
|
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
|
|
11
|
+
//#region src/scope/scope.d.ts
|
|
12
|
+
declare const Scopes: {
|
|
13
|
+
readonly Singleton: "singleton";
|
|
14
|
+
readonly Transient: "transient";
|
|
15
|
+
readonly Scoped: "scoped";
|
|
16
|
+
};
|
|
17
|
+
type Scope = (typeof Scopes)[keyof typeof Scopes];
|
|
18
|
+
//#endregion
|
|
1
19
|
//#region src/token/types.d.ts
|
|
2
20
|
type Token<T> = {
|
|
3
21
|
readonly id: symbol;
|
|
@@ -9,10 +27,16 @@ type Token<T> = {
|
|
|
9
27
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
10
28
|
//#endregion
|
|
11
29
|
//#region src/provider/types.d.ts
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
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]> };
|
|
15
38
|
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
39
|
+
type DisposeHook<T> = (instance: T) => void | Promise<void>;
|
|
16
40
|
type ValueProvider<T> = {
|
|
17
41
|
readonly provide: Token<T>;
|
|
18
42
|
readonly useValue: T;
|
|
@@ -22,10 +46,10 @@ type FactoryProvider<T, TDeps extends DepsMap = Record<never, never>> = {
|
|
|
22
46
|
readonly deps?: TDeps;
|
|
23
47
|
readonly scope?: Scope;
|
|
24
48
|
readonly useFactory: Factory<T, TDeps>;
|
|
49
|
+
readonly onDispose?: DisposeHook<T>;
|
|
25
50
|
};
|
|
26
|
-
type AnyFactoryProvider = FactoryProvider<
|
|
51
|
+
type AnyFactoryProvider = FactoryProvider<any, any>;
|
|
27
52
|
type Provider = ValueProvider<unknown> | AnyFactoryProvider;
|
|
28
|
-
type Scope = "singleton" | "transient";
|
|
29
53
|
//#endregion
|
|
30
54
|
//#region src/provider/provider.d.ts
|
|
31
55
|
declare const provideValue: <T>(token: Token<T>, useValue: T) => ValueProvider<T>;
|
|
@@ -33,27 +57,32 @@ declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(
|
|
|
33
57
|
readonly deps?: TDeps;
|
|
34
58
|
readonly scope?: Scope;
|
|
35
59
|
readonly useFactory: Factory<T, TDeps>;
|
|
60
|
+
readonly onDispose?: DisposeHook<T>;
|
|
36
61
|
}) => FactoryProvider<T, TDeps>;
|
|
62
|
+
declare const optional: <T>(token: Token<T>) => OptionalDependency<T>;
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/registry/errors.d.ts
|
|
65
|
+
declare class DuplicateProviderError extends DiError {
|
|
66
|
+
constructor(tokenName: string);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/registry/types.d.ts
|
|
70
|
+
type RegisterOptions = {
|
|
71
|
+
readonly allowOverride?: boolean;
|
|
72
|
+
};
|
|
37
73
|
//#endregion
|
|
38
74
|
//#region src/container/types.d.ts
|
|
39
75
|
type Container = {
|
|
40
|
-
register(provider: Provider): void;
|
|
76
|
+
register(provider: Provider, options?: RegisterOptions): void;
|
|
41
77
|
get<T>(token: Token<T>): T;
|
|
78
|
+
get<T>(dependency: OptionalDependency<T>): T | undefined;
|
|
42
79
|
has(token: Token<unknown>): boolean;
|
|
80
|
+
dispose(): Promise<void>;
|
|
43
81
|
};
|
|
44
82
|
//#endregion
|
|
45
83
|
//#region src/container/container.d.ts
|
|
46
84
|
declare const createContainer: (providers?: readonly Provider[]) => Container;
|
|
47
|
-
|
|
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
|
-
}
|
|
85
|
+
declare const createChildContainer: (parent: Container, providers?: readonly Provider[]) => Container;
|
|
57
86
|
//#endregion
|
|
58
87
|
//#region src/resolver/errors.d.ts
|
|
59
88
|
declare class MissingProviderError extends DiError {
|
|
@@ -66,4 +95,5 @@ declare class CircularDependencyError extends DiError {
|
|
|
66
95
|
constructor(tokenNames: string[]);
|
|
67
96
|
}
|
|
68
97
|
//#endregion
|
|
69
|
-
export { CircularDependencyError, type Container, DiError, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type Scope, type Token, type ValueProvider, 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,21 @@
|
|
|
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
|
|
11
|
+
//#region src/scope/scope.d.ts
|
|
12
|
+
declare const Scopes: {
|
|
13
|
+
readonly Singleton: "singleton";
|
|
14
|
+
readonly Transient: "transient";
|
|
15
|
+
readonly Scoped: "scoped";
|
|
16
|
+
};
|
|
17
|
+
type Scope = (typeof Scopes)[keyof typeof Scopes];
|
|
18
|
+
//#endregion
|
|
1
19
|
//#region src/token/types.d.ts
|
|
2
20
|
type Token<T> = {
|
|
3
21
|
readonly id: symbol;
|
|
@@ -9,10 +27,16 @@ type Token<T> = {
|
|
|
9
27
|
declare const createToken: <T>(name: string) => Token<T>;
|
|
10
28
|
//#endregion
|
|
11
29
|
//#region src/provider/types.d.ts
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
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]> };
|
|
15
38
|
type Factory<T, TDeps extends DepsMap> = (deps: ResolveDeps<TDeps>) => T;
|
|
39
|
+
type DisposeHook<T> = (instance: T) => void | Promise<void>;
|
|
16
40
|
type ValueProvider<T> = {
|
|
17
41
|
readonly provide: Token<T>;
|
|
18
42
|
readonly useValue: T;
|
|
@@ -22,10 +46,10 @@ type FactoryProvider<T, TDeps extends DepsMap = Record<never, never>> = {
|
|
|
22
46
|
readonly deps?: TDeps;
|
|
23
47
|
readonly scope?: Scope;
|
|
24
48
|
readonly useFactory: Factory<T, TDeps>;
|
|
49
|
+
readonly onDispose?: DisposeHook<T>;
|
|
25
50
|
};
|
|
26
|
-
type AnyFactoryProvider = FactoryProvider<
|
|
51
|
+
type AnyFactoryProvider = FactoryProvider<any, any>;
|
|
27
52
|
type Provider = ValueProvider<unknown> | AnyFactoryProvider;
|
|
28
|
-
type Scope = "singleton" | "transient";
|
|
29
53
|
//#endregion
|
|
30
54
|
//#region src/provider/provider.d.ts
|
|
31
55
|
declare const provideValue: <T>(token: Token<T>, useValue: T) => ValueProvider<T>;
|
|
@@ -33,27 +57,32 @@ declare const provideFactory: <T, TDeps extends DepsMap = Record<never, never>>(
|
|
|
33
57
|
readonly deps?: TDeps;
|
|
34
58
|
readonly scope?: Scope;
|
|
35
59
|
readonly useFactory: Factory<T, TDeps>;
|
|
60
|
+
readonly onDispose?: DisposeHook<T>;
|
|
36
61
|
}) => FactoryProvider<T, TDeps>;
|
|
62
|
+
declare const optional: <T>(token: Token<T>) => OptionalDependency<T>;
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/registry/errors.d.ts
|
|
65
|
+
declare class DuplicateProviderError extends DiError {
|
|
66
|
+
constructor(tokenName: string);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/registry/types.d.ts
|
|
70
|
+
type RegisterOptions = {
|
|
71
|
+
readonly allowOverride?: boolean;
|
|
72
|
+
};
|
|
37
73
|
//#endregion
|
|
38
74
|
//#region src/container/types.d.ts
|
|
39
75
|
type Container = {
|
|
40
|
-
register(provider: Provider): void;
|
|
76
|
+
register(provider: Provider, options?: RegisterOptions): void;
|
|
41
77
|
get<T>(token: Token<T>): T;
|
|
78
|
+
get<T>(dependency: OptionalDependency<T>): T | undefined;
|
|
42
79
|
has(token: Token<unknown>): boolean;
|
|
80
|
+
dispose(): Promise<void>;
|
|
43
81
|
};
|
|
44
82
|
//#endregion
|
|
45
83
|
//#region src/container/container.d.ts
|
|
46
84
|
declare const createContainer: (providers?: readonly Provider[]) => Container;
|
|
47
|
-
|
|
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
|
-
}
|
|
85
|
+
declare const createChildContainer: (parent: Container, providers?: readonly Provider[]) => Container;
|
|
57
86
|
//#endregion
|
|
58
87
|
//#region src/resolver/errors.d.ts
|
|
59
88
|
declare class MissingProviderError extends DiError {
|
|
@@ -66,4 +95,5 @@ declare class CircularDependencyError extends DiError {
|
|
|
66
95
|
constructor(tokenNames: string[]);
|
|
67
96
|
}
|
|
68
97
|
//#endregion
|
|
69
|
-
export { CircularDependencyError, type Container, DiError, DuplicateProviderError, type FactoryProvider, InvalidDependencyError, MissingProviderError, type Provider, type Scope, type Token, type ValueProvider, 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,157 +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) {
|
|
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
|
|
146
|
-
//#region src/token/token.ts
|
|
147
|
-
var TokenClass = class {
|
|
148
|
-
name;
|
|
149
|
-
id;
|
|
150
|
-
constructor(name) {
|
|
151
|
-
this.name = name;
|
|
152
|
-
this.id = Symbol(name);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
const createToken = (name) => new TokenClass(name);
|
|
156
|
-
//#endregion
|
|
157
|
-
export { CircularDependencyError, DiError, DuplicateProviderError, InvalidDependencyError, MissingProviderError, 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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "di-craft",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "A tiny
|
|
3
|
+
"version": "0.0.12",
|
|
4
|
+
"description": "A tiny, type-safe dependency injection container for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Egor Bezmen",
|
|
@@ -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": {
|