@fnioc/di 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -27
- package/dist/index.d.ts +652 -8
- package/dist/index.js +521 -23
- package/package.json +6 -9
- package/dist/builder.d.ts +0 -81
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -85
- package/dist/builder.js.map +0 -1
- package/dist/errors.d.ts +0 -73
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -139
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/scope.d.ts +0 -180
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -466
- package/dist/scope.js.map +0 -1
- package/dist/types.d.ts +0 -57
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -4
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,16 +6,18 @@ No decorators. No `reflect-metadata`. No runtime type introspection. Feed it str
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## `DiBuilder<
|
|
9
|
+
## `DiBuilder<Root, Children>`
|
|
10
10
|
|
|
11
|
-
The entry point. `
|
|
11
|
+
The entry point. `Root` is the root scope's name (the app-lifetime scope singletons bind to, default `"singleton"`); `Children` is the union of declarable child-scope names. Scopes are `Root | Children`. Transient (no cache, fresh instance on every resolve) is the default — there is no `"transient"` scope; the absence of `.as()` is what makes a registration transient.
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { DiBuilder } from "@fnioc/di";
|
|
15
15
|
|
|
16
|
-
const services = new DiBuilder<"singleton"
|
|
16
|
+
const services = new DiBuilder<"singleton", "request">();
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Registration is append-only: each token holds a **list** of registrations in registration order, and resolution picks the most-recent (last) one. A later `add` for the same token therefore overrides an earlier one without deleting it.
|
|
20
|
+
|
|
19
21
|
### `.add<Interface>(Concrete).as<"scope">()`
|
|
20
22
|
|
|
21
23
|
Register a concrete implementation against an interface token. The transformer rewrites `add<IFoo>(Foo)` to `add("pkg:IFoo", Foo)` at build time. Hand-fed consumers pass the token string directly.
|
|
@@ -24,7 +26,7 @@ Register a concrete implementation against an interface token. The transformer r
|
|
|
24
26
|
// With transformer (author form):
|
|
25
27
|
services.add<ILogger>(ConsoleLogger).as<"singleton">();
|
|
26
28
|
services.add<IUserRepo>(SqlUserRepo).as<"request">();
|
|
27
|
-
services.add<IRequestId>(UuidRequestId
|
|
29
|
+
services.add<IRequestId>(UuidRequestId); // no .as() → transient
|
|
28
30
|
|
|
29
31
|
// Without transformer (lowered form, or plugin-less):
|
|
30
32
|
services.add("pkg:ILogger", ConsoleLogger).as("singleton");
|
|
@@ -36,39 +38,43 @@ The type constraint on `Concrete` is `new (...args: any[]) => Interface` — pla
|
|
|
36
38
|
|
|
37
39
|
`.as<S>()` checks at compile time that `S` is a declared scope name. Passing an undeclared string is a type error.
|
|
38
40
|
|
|
39
|
-
### `useFactory` and `useValue`
|
|
41
|
+
### `add(token, { useFactory })` and `add(token, { useValue })`
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
The same `add` surface also takes factory and value specs — registration paths that bypass the dep-metadata system entirely. Recommended for test doubles, third-party instances, and plugin-less consumers. Both return the builder for chaining.
|
|
42
44
|
|
|
43
45
|
```typescript
|
|
44
|
-
// Factory: receives the scope
|
|
45
|
-
|
|
46
|
+
// Factory: receives the scope, returns the instance. An optional `scope`
|
|
47
|
+
// caches the result at the matching ancestor (singleton-style).
|
|
48
|
+
services.add("pkg:IDb", {
|
|
46
49
|
useFactory: (c) => new PostgresDb(c.resolve<IConfig>("pkg:IConfig")),
|
|
50
|
+
scope: "singleton",
|
|
47
51
|
});
|
|
48
52
|
|
|
49
|
-
// Value: a pre-constructed instance (
|
|
50
|
-
|
|
53
|
+
// Value: a pre-constructed instance (re-used as-is, no lifetime)
|
|
54
|
+
services.add("pkg:ICache", {
|
|
51
55
|
useValue: new NullCache(),
|
|
52
56
|
});
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
`useFactory` with
|
|
59
|
+
A `useFactory` with `scope: "singleton"` runs once and caches the result; without a `scope` it runs on every resolve (transient). `useValue` is always the same reference.
|
|
60
|
+
|
|
61
|
+
The same two specs are available scope-locally via `scope.add(token, spec)`, so a single scope (e.g. a test scope) can swap an implementation without rebuilding the builder.
|
|
56
62
|
|
|
57
63
|
---
|
|
58
64
|
|
|
59
65
|
## Scope model
|
|
60
66
|
|
|
61
|
-
Scopes form a parent-linked chain. The root scope is a real, app-lifetime object — never auto-created by the container.
|
|
67
|
+
Scopes form a parent-linked chain. The root scope is a real, app-lifetime object — minted by `build()`, never auto-created by the container. Child scopes nest from a scope via `createScope`.
|
|
62
68
|
|
|
63
69
|
```typescript
|
|
64
|
-
const root = services.
|
|
65
|
-
const req = root.createScope("request");
|
|
70
|
+
const root = services.build(); // app lifetime — named by Root
|
|
71
|
+
const req = root.createScope("request"); // per HTTP request
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
**Resolution walks the parent chain** for two purposes:
|
|
69
75
|
|
|
70
|
-
1. **Registration lookup** — walks up until the token is found. A child scope can shadow a parent registration.
|
|
71
|
-
2. **Instance ownership** — the lifetime
|
|
76
|
+
1. **Registration lookup** — walks up until the token is found, taking the most-recent registration at the nearest scope. A child scope can shadow a parent registration.
|
|
77
|
+
2. **Instance ownership** — the lifetime scope names which ancestor scope caches the instance. Walks up to the nearest matching ancestor and caches there.
|
|
72
78
|
|
|
73
79
|
**Lifetime rules:**
|
|
74
80
|
|
|
@@ -77,20 +83,20 @@ const req = root.createScope("request"); // per HTTP request
|
|
|
77
83
|
| No `.as()` (transient) | Fresh instance on every resolve. Never cached. |
|
|
78
84
|
| `.as("singleton")` | Owned and cached by the nearest `"singleton"` ancestor in the chain. |
|
|
79
85
|
| `.as("request")` | Owned and cached by the nearest `"request"` ancestor in the chain. |
|
|
80
|
-
|
|
|
86
|
+
| Scope with no matching ancestor | **Throws.** The missing-ancestor error is intentional — see captive-dependency protection below. |
|
|
81
87
|
|
|
82
88
|
### Captive-dependency protection
|
|
83
89
|
|
|
84
90
|
The critical correctness rule: deps are resolved **relative to the scope that will own the instance**, not the scope that triggered the resolve.
|
|
85
91
|
|
|
86
92
|
```typescript
|
|
87
|
-
const services = new DiBuilder<"singleton"
|
|
93
|
+
const services = new DiBuilder<"singleton", "request">();
|
|
88
94
|
services.add<ICache>(RedisCache).as<"singleton">();
|
|
89
95
|
services.add<IUserContext>(HttpUserContext).as<"request">();
|
|
90
96
|
services.add<IUserService>(UserService).as<"singleton">();
|
|
91
97
|
// UserService constructor: (cache: ICache, ctx: IUserContext)
|
|
92
98
|
|
|
93
|
-
const root = services.
|
|
99
|
+
const root = services.build();
|
|
94
100
|
const req = root.createScope("request");
|
|
95
101
|
|
|
96
102
|
req.resolve<IUserService>("pkg:IUserService");
|
|
@@ -164,11 +170,12 @@ The container never awaits. Async is expressed as `Promise<T>` values through th
|
|
|
164
170
|
|
|
165
171
|
```typescript
|
|
166
172
|
// Register an async factory
|
|
167
|
-
|
|
173
|
+
services.add("pkg:IDb", {
|
|
168
174
|
useFactory: async (c) => {
|
|
169
175
|
const pool = c.resolve<IConnectionPool>("pkg:IConnectionPool");
|
|
170
176
|
return new PostgresDb(await pool.connect());
|
|
171
177
|
},
|
|
178
|
+
scope: "singleton",
|
|
172
179
|
});
|
|
173
180
|
|
|
174
181
|
// Consume it — declare the dep as Promise<IDb>
|
|
@@ -258,21 +265,23 @@ Note: `FactoryTargetError` is thrown when the factory callable is constructed (a
|
|
|
258
265
|
|
|
259
266
|
## API reference
|
|
260
267
|
|
|
261
|
-
### `DiBuilder<
|
|
268
|
+
### `DiBuilder<Root, Children>`
|
|
262
269
|
|
|
263
270
|
| Member | Signature | Description |
|
|
264
271
|
|---|---|---|
|
|
265
|
-
| `add<I>(Concrete)` | `(ctor: new (...) => I) =>
|
|
266
|
-
| `.as<S>()
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
272
|
+
| `add<I>(Concrete)` | `(ctor: new (...) => I) => AddBuilder` | Register a concrete class against interface `I`. |
|
|
273
|
+
| `.as<S>()` | `(scope: S) → void` | Set the lifetime scope. No call → transient. |
|
|
274
|
+
| `add(token, ctor)` | `(token: string, ctor) => AddBuilder` | Class registration (lowered form). |
|
|
275
|
+
| `add(token, spec)` | `(token: string, { useFactory, scope? } \| { useValue }) => this` | Factory / value registration. No dep metadata required. |
|
|
276
|
+
| `build()` | `() => Scope<Root \| Children>` | Mint the root scope (named `Root`). No argument. |
|
|
269
277
|
|
|
270
278
|
### `Scope<Scopes>`
|
|
271
279
|
|
|
272
280
|
| Member | Signature | Description |
|
|
273
281
|
|---|---|---|
|
|
274
|
-
| `createScope(
|
|
275
|
-
| `
|
|
282
|
+
| `createScope(name)` | `(name: Scopes) => Scope<Scopes>` | Create a nested child scope. |
|
|
283
|
+
| `add(token, spec)` | `(token, { useFactory, scope? } \| { useValue }) => this` | Scope-local override registration. |
|
|
284
|
+
| `resolve<T>(token)` | `(token: string) => T` | Resolve an instance. Throws on captive-dep violation, missing scope ancestor, or cycle. |
|
|
276
285
|
| `dispose()` | `() => void` | Sync close. Throws if any owned instance has async-only disposal. |
|
|
277
286
|
| `disposeAsync()` | `() => Promise<void>` | Async close. |
|
|
278
287
|
| `[Symbol.dispose]()` | — | Native `using` support. |
|