di-bag 0.3.0 → 0.5.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/AGENTS.md +41 -41
- package/README.md +94 -72
- package/dist/acquisition-context.d.ts +53 -35
- package/dist/acquisition-context.js +20 -10
- package/dist/acquisition-family.d.ts +2 -2
- package/dist/acquisition-family.js +3 -3
- package/dist/acquisition-mode.d.ts +20 -17
- package/dist/acquisition-mode.js +24 -20
- package/dist/acquisition.d.ts +20 -8
- package/dist/acquisition.js +95 -43
- package/dist/alias-types.d.ts +10 -8
- package/dist/aliases.d.ts +2 -2
- package/dist/aliases.js +26 -7
- package/dist/builder-method-types.d.ts +67 -0
- package/dist/builder-method-types.js +2 -0
- package/dist/composition-report.d.ts +3 -3
- package/dist/composition.d.ts +43 -34
- package/dist/composition.js +39 -28
- package/dist/contribution-types.d.ts +22 -16
- package/dist/contributions.d.ts +2 -2
- package/dist/contributions.js +8 -4
- package/dist/dependency-references.d.ts +6 -16
- package/dist/dependency-references.js +30 -12
- package/dist/di-bag.d.ts +288 -319
- package/dist/di-bag.js +320 -168
- package/dist/errors.d.ts +62 -56
- package/dist/errors.js +77 -68
- package/dist/index.d.ts +19 -16
- package/dist/index.js +4 -4
- package/dist/inspection.d.ts +21 -21
- package/dist/install-types.d.ts +21 -0
- package/dist/install-types.js +2 -0
- package/dist/lifetime-types.d.ts +115 -60
- package/dist/lifetime.d.ts +19 -34
- package/dist/lifetime.js +16 -23
- package/dist/module-types.d.ts +62 -25
- package/dist/module.d.ts +53 -15
- package/dist/module.js +149 -55
- package/dist/observers.d.ts +27 -27
- package/dist/observers.js +19 -10
- package/dist/options-bag.d.ts +7 -0
- package/dist/options-bag.js +40 -0
- package/dist/plugins.d.ts +17 -35
- package/dist/plugins.js +23 -43
- package/dist/provider-execution.d.ts +38 -2
- package/dist/provider-execution.js +159 -31
- package/dist/provider-facades.d.ts +126 -0
- package/dist/provider-facades.js +42 -0
- package/dist/provider-operations.d.ts +8 -8
- package/dist/provider-operations.js +9 -9
- package/dist/provider.d.ts +65 -156
- package/dist/provider.js +66 -91
- package/dist/registration.d.ts +6 -36
- package/dist/registration.js +9 -28
- package/dist/removed-api.d.ts +9 -0
- package/dist/removed-api.js +69 -0
- package/dist/replacement-types.d.ts +11 -10
- package/dist/runtime.d.ts +25 -12
- package/dist/runtime.js +162 -47
- package/dist/scope-selection.d.ts +3 -2
- package/dist/scope-selection.js +109 -47
- package/dist/scope-types.d.ts +38 -27
- package/dist/startup.d.ts +19 -22
- package/dist/startup.js +69 -50
- package/dist/token-types.d.ts +55 -27
- package/dist/tokens.d.ts +47 -18
- package/dist/tokens.js +67 -20
- package/dist/types.d.ts +65 -44
- package/docs/agent/api-card.md +195 -178
- package/docs/agent/errors.md +470 -520
- package/docs/agent/recipes.md +297 -76
- package/package.json +12 -6
- package/dist/node.d.ts +0 -4
- package/dist/node.js +0 -22
package/AGENTS.md
CHANGED
|
@@ -1,65 +1,63 @@
|
|
|
1
1
|
# DI Bag: notes for coding agents
|
|
2
2
|
|
|
3
3
|
DI Bag composes TypeScript factories into a dependency graph that the compiler
|
|
4
|
-
checks. Modules keep a feature's services private behind exported keys; a
|
|
4
|
+
checks. Modules keep a feature's services private behind exported keys; a container
|
|
5
5
|
creates services on first use and releases what it owns when closed.
|
|
6
6
|
|
|
7
|
-
This file ships in `node_modules/di-bag/`. Every call, with one way per task and an example:
|
|
8
|
-
[docs/agent/
|
|
9
|
-
Every compiler and runtime message: [docs/agent/errors.md](docs/agent/errors.md).
|
|
7
|
+
This file ships in `node_modules/di-bag/`. Every call, with one way per task and an example: [docs/agent/api-card.md](docs/agent/api-card.md).
|
|
8
|
+
Task recipes: [docs/agent/recipes.md](docs/agent/recipes.md). Every compiler and runtime message: [docs/agent/errors.md](docs/agent/errors.md).
|
|
10
9
|
|
|
11
10
|
## Rules
|
|
12
11
|
|
|
13
12
|
1. **Import from `di-bag`:** `import { DiBag } from 'di-bag';`. It configures
|
|
14
|
-
itself on Node, Bun, and Deno;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for
|
|
13
|
+
itself on Node, Bun, and Deno; use the same root import on every runtime.
|
|
14
|
+
For browsers and workers register synchronous factories with
|
|
15
|
+
`DiBag.createProvider(factory, { factoryReturnKind: 'sync-value' })`; use
|
|
16
|
+
`'native-promise'` for a factory that returns a native Promise
|
|
17
|
+
([portable recipe](docs/agent/recipes.md#portable-graph)); an auto-detect factory there fails
|
|
18
|
+
`buildContainer()` with [`DI_BAG_CLASSIFIER_REQUIRED`](docs/agent/errors.md#di-bag-classifier-required), which names it.
|
|
18
19
|
2. **A factory declares its dependencies in the type of its one object
|
|
19
20
|
parameter; destructure it** (`({ clock }: { clock: Clock }) => ...`) or read
|
|
20
|
-
`
|
|
21
|
+
`dependencies.clock` directly. The object is a Proxy that resolves each property when
|
|
21
22
|
read: spreading it, `Object.keys`, `in`, and `JSON.stringify` throw
|
|
22
23
|
[`DI_BAG_INVALID_DEPENDENCY_ACCESS`](docs/agent/errors.md#di-bag-invalid-dependency-access).
|
|
23
|
-
3. **Lifetimes.** The default is `scoped
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`'transient'` creates an instance on every read.
|
|
24
|
+
3. **Lifetimes.** The default is `'scoped:one-per-container'`. Mark a shared client
|
|
25
|
+
`'singleton:one-per-container-tree'` only when nothing it depends on is scoped.
|
|
26
|
+
A child may replace only scoped or transient services; use an independent container
|
|
27
|
+
to replace a singleton.
|
|
28
28
|
4. **Async is explicit.** An async factory's service is its Promise. A consumer
|
|
29
29
|
declares `{ db: Promise<Db> }` and awaits it; nothing is awaited for you.
|
|
30
30
|
5. **No thenables.** A factory that returns a non-Promise object with a `then`
|
|
31
31
|
method (query builders) is [rejected](docs/agent/errors.md#structural-thenable).
|
|
32
|
-
Return `Promise.resolve(builder)` or use `DiBag.
|
|
33
|
-
6. **Ownership.** `DiBag.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
8. **Modules.**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
services `billing/store`.
|
|
32
|
+
Return `Promise.resolve(builder)` or use `DiBag.createProvider(create, { factoryReturnKind: 'uninspected' })`.
|
|
33
|
+
6. **Ownership.** Use `DiBag.providerWithDisposal({ provider, disposeService })`.
|
|
34
|
+
`close()` runs disposers, dependents first. Close every child and
|
|
35
|
+
independent container; a parent closes its live children, never independent containers. Inside a factory,
|
|
36
|
+
[`factoryContext.pushDisposer`](docs/agent/recipes.md#partial-acquisition) owns what it acquires on the way; if that is also the returned value, act only when `disposerContext.reason !== 'service-disposed'`.
|
|
37
|
+
7. **Replace dependencies in tests with `createIndependentContainer(keys, providers)`**; each provider must satisfy the original contract.
|
|
38
|
+
8. **Modules.** Add factories with `withServices`, then
|
|
39
|
+
`buildModule({ exportedServiceKeys: ['exported'], moduleLabel: 'billing' })`.
|
|
40
|
+
Unregistered needs become requirements: the host supplies them after
|
|
41
|
+
`withInstalledModules([module])`. Rename colliding string requirements with module.withRenamedRequirement({ currentRequirementKey, newRequirementKey }); tokens keep their global identity.
|
|
43
42
|
9. **Read a rejection at its name.** A graph error is an assignability error
|
|
44
43
|
whose type is `Unsatisfied<"message", details>`, reported where the builder
|
|
45
|
-
expression starts. `builder.
|
|
46
|
-
message on its own line; `"noErrorTruncation": true` prints the details.
|
|
47
|
-
Runtime errors carry `code` and `details`: branch on `code`, never on message
|
|
48
|
-
|
|
49
|
-
with `_` replaced by `-`.
|
|
44
|
+
expression starts. `builder.verifyGraphAtCompileTime() satisfies void;`
|
|
45
|
+
reports the same message on its own line; `"noErrorTruncation": true` prints the details.
|
|
46
|
+
Runtime errors carry `code` and `details`: branch on `code`, never on message text.
|
|
47
|
+
The section for a code is `docs/agent/errors.md#<code>`, lower-cased with `_` replaced by `-`.
|
|
50
48
|
|
|
51
49
|
## Module layout
|
|
52
50
|
|
|
53
51
|
```text
|
|
54
52
|
src/features/invoicing/
|
|
55
53
|
contract.ts # exported service types and the requirements the host must supply
|
|
56
|
-
module.ts # buildModule([...]) over the private factories
|
|
54
|
+
module.ts # buildModule({ exportedServiceKeys: [...] }) over the private factories
|
|
57
55
|
store.ts # private services; free to use names other modules also use
|
|
58
56
|
check.ts # type-checks this module alone; never imported, not built
|
|
59
57
|
tsconfig.json # extends the root tsconfig and includes only this directory
|
|
60
58
|
invoicing.test.ts
|
|
61
|
-
src/app.ts # installs
|
|
62
|
-
src/app.check.ts #
|
|
59
|
+
src/app.ts # installs modules in one withInstalledModules([...]) list
|
|
60
|
+
src/app.check.ts # verifyGraphAtCompileTime() on the application builder: the merge check
|
|
63
61
|
```
|
|
64
62
|
|
|
65
63
|
Inside a file, keep the same order: contract types, private factories, the
|
|
@@ -80,15 +78,15 @@ import { DiBag } from 'di-bag';
|
|
|
80
78
|
import type { Greeter, GreetingConfig } from './contract.js';
|
|
81
79
|
|
|
82
80
|
export const greetingModule = DiBag.createBuilder()
|
|
83
|
-
.
|
|
81
|
+
.withServices({
|
|
84
82
|
greeter: ({ config }: { config: GreetingConfig }): Greeter => ({
|
|
85
83
|
greet: name => `${config.greeting}, ${name}!`,
|
|
86
84
|
}),
|
|
87
85
|
})
|
|
88
|
-
.buildModule(['greeter']);
|
|
86
|
+
.buildModule({ exportedServiceKeys: ['greeter'] });
|
|
89
87
|
```
|
|
90
88
|
|
|
91
|
-
`check.ts` is one statement: install the module,
|
|
89
|
+
`check.ts` is one statement: install the module, add a typed fixture for
|
|
92
90
|
each requirement, and verify.
|
|
93
91
|
|
|
94
92
|
```ts
|
|
@@ -98,9 +96,11 @@ import type { GreetingConfig } from './contract.js';
|
|
|
98
96
|
import { greetingModule } from './module.js';
|
|
99
97
|
|
|
100
98
|
DiBag.createBuilder()
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
.withInstalledModules([
|
|
100
|
+
greetingModule,
|
|
101
|
+
])
|
|
102
|
+
.withServices({ config: (): GreetingConfig => ({ greeting: 'Hello' }) })
|
|
103
|
+
.verifyGraphAtCompileTime() satisfies void;
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
## Check one module
|
|
@@ -121,7 +121,7 @@ npx tsc --noEmit -p src/features/<name>/tsconfig.json
|
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
A missing requirement fails with its key:
|
|
124
|
-
`required
|
|
124
|
+
`required services are missing: config`.
|
|
125
125
|
|
|
126
126
|
## Fast check
|
|
127
127
|
|
|
@@ -142,7 +142,7 @@ test suite before merging: [review a merge](docs/agent/recipes.md#review-merge).
|
|
|
142
142
|
## Recipes
|
|
143
143
|
|
|
144
144
|
- [Add a request-scoped service with cleanup](docs/agent/recipes.md#add-scoped-service)
|
|
145
|
-
- [Write a fixture test with
|
|
145
|
+
- [Write a fixture test with an independent container](docs/agent/recipes.md#fixture-test)
|
|
146
146
|
- [Split a feature into a module with private services](docs/agent/recipes.md#split-module)
|
|
147
147
|
- [Debug a missing-dependency rejection](docs/agent/recipes.md#debug-missing-dependency)
|
|
148
148
|
- [Add and consume an async client](docs/agent/recipes.md#async-client)
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DI Bag
|
|
2
2
|
|
|
3
|
-
TypeScript dependency composition and resource ownership for modular codebases
|
|
4
|
-
|
|
3
|
+
TypeScript dependency composition and resource ownership for modular codebases.
|
|
4
|
+
Built for coding agents that ship one feature at a time.
|
|
5
5
|
|
|
6
6
|
[Documentation](https://dany-fedorov.github.io/di-bag/) · [Quickstart](#quickstart) · [Modules as units of work](#modules-as-units-of-work) · [Comparison](#how-it-compares) · [Tutorial](docs/guides/tutorial.md) · [API reference](docs/guides/api-reference.md)
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ including the ones coding agents build one feature at a time.
|
|
|
9
9
|
|
|
10
10
|
Compose ordinary TypeScript factories into reusable features. DI Bag checks
|
|
11
11
|
declared dependencies, keeps module internals private, and manages resource
|
|
12
|
-
creation and
|
|
12
|
+
creation and disposal. Build each feature against an explicit contract, test it
|
|
13
13
|
with replaced dependencies, and let the compiler check the composition when
|
|
14
14
|
independently developed features come together.
|
|
15
15
|
|
|
@@ -18,10 +18,10 @@ independently developed features come together.
|
|
|
18
18
|
without exposing its internals or reading another feature's source.
|
|
19
19
|
- **[Compile-time wiring checks](docs/guides/examples-type-checking.md).**
|
|
20
20
|
Catch missing dependencies and incompatible replacements before starting the app.
|
|
21
|
-
- **[Metadata inspection without
|
|
21
|
+
- **[Metadata inspection without creating services](docs/guides/examples-extensibility.md).**
|
|
22
22
|
Build capability catalogs without running factories or opening clients.
|
|
23
23
|
- **[Inject anything with a simple factory function](docs/guides/examples-plain-services.md).**
|
|
24
|
-
Supply functions, objects, clients, or promises
|
|
24
|
+
Supply functions, objects, clients, or promises without decorators or base classes.
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
@@ -31,19 +31,20 @@ Install [di-bag from npm](https://www.npmjs.com/package/di-bag):
|
|
|
31
31
|
npm install di-bag
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The API is pre-1.0 and includes breaking changes
|
|
35
|
-
|
|
34
|
+
The API is pre-1.0 and includes breaking changes. Run the 0.4-to-0.5 codemod
|
|
35
|
+
before upgrading, then review the changelog and the
|
|
36
|
+
[0.5 migration guide](docs/guides/migrating-to-0.5.md).
|
|
36
37
|
|
|
37
38
|
The minimum supported TypeScript version is **6.0.3**; enable `strict` in your
|
|
38
39
|
`tsconfig.json`. The repository checks classic TypeScript 6.0.3 and native 7.0.2.
|
|
39
|
-
For browsers and
|
|
40
|
+
For browsers and workers, see [runtime support](#runtime-support).
|
|
40
41
|
|
|
41
42
|
## Quickstart
|
|
42
43
|
|
|
43
44
|
A **service** can be a configuration object, a database client, or a function.
|
|
44
|
-
A **factory** creates a service. A **
|
|
45
|
-
|
|
46
|
-
resources
|
|
45
|
+
A **factory** creates a service. A **container** resolves its factories' declared
|
|
46
|
+
dependencies. Services are created when needed. The container disposes owned
|
|
47
|
+
resources when you close it.
|
|
47
48
|
|
|
48
49
|
Import from `di-bag`. Here, `greeter` needs `config`. Its parameter
|
|
49
50
|
type describes that dependency, and its return value is the service it provides:
|
|
@@ -52,7 +53,7 @@ type describes that dependency, and its return value is the service it provides:
|
|
|
52
53
|
import { DiBag } from 'di-bag';
|
|
53
54
|
|
|
54
55
|
const app = DiBag.createBuilder()
|
|
55
|
-
.
|
|
56
|
+
.withServices({
|
|
56
57
|
config: () => ({ greeting: 'Hello' }),
|
|
57
58
|
greeter: ({ config }: { config: { greeting: string } }) => ({
|
|
58
59
|
greet(name: string) {
|
|
@@ -60,28 +61,37 @@ const app = DiBag.createBuilder()
|
|
|
60
61
|
},
|
|
61
62
|
}),
|
|
62
63
|
})
|
|
63
|
-
.
|
|
64
|
+
.buildContainer();
|
|
64
65
|
|
|
65
66
|
const greeter = app.resolve('greeter');
|
|
66
67
|
console.log(greeter.greet('Ada')); // Hello, Ada!
|
|
68
|
+
await app.close();
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
declared graph and creates
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
`withServices({...})` adds factories to an immutable builder. `buildContainer()`
|
|
72
|
+
checks the declared graph and creates a container. `resolve('greeter')` creates
|
|
73
|
+
the greeter and its config on first use. The default scoped lifetime returns the
|
|
74
|
+
same greeter on later resolutions in this container. Provider order does not matter.
|
|
73
75
|
|
|
74
76
|
TypeScript knows that `greeter` has a `greet(name: string): string` method.
|
|
75
|
-
Removing the `config` factory makes
|
|
77
|
+
Removing the `config` factory makes `buildContainer()` a compile-time error. Changing
|
|
76
78
|
`greeting` to a number also fails the type check because the greeter needs a string.
|
|
77
79
|
|
|
78
80
|
## Swap a dependency for a test
|
|
79
81
|
|
|
80
|
-
Use `
|
|
81
|
-
|
|
82
|
+
Use `createIndependentContainer()` for a test with a replacement dependency.
|
|
83
|
+
This example uses the graph from the [quickstart](#quickstart):
|
|
82
84
|
|
|
83
85
|
```ts
|
|
84
|
-
|
|
86
|
+
import { DiBag } from 'di-bag';
|
|
87
|
+
|
|
88
|
+
const app = DiBag.createBuilder().withServices({
|
|
89
|
+
config: () => ({ greeting: 'Hello' }),
|
|
90
|
+
greeter: ({ config }: { config: { greeting: string } }) =>
|
|
91
|
+
({ greet: (name: string) => `${config.greeting}, ${name}!` }),
|
|
92
|
+
}).buildContainer();
|
|
93
|
+
|
|
94
|
+
const testApp = app.createIndependentContainer(['config'], {
|
|
85
95
|
config: () => ({ greeting: 'Hi' }),
|
|
86
96
|
});
|
|
87
97
|
|
|
@@ -90,12 +100,13 @@ try {
|
|
|
90
100
|
console.log(app.resolve('greeter').greet('Ada')); // Hello, Ada!
|
|
91
101
|
} finally {
|
|
92
102
|
await testApp.close();
|
|
103
|
+
await app.close();
|
|
93
104
|
}
|
|
94
105
|
```
|
|
95
106
|
|
|
96
|
-
The replacement must satisfy the original service contract. Each
|
|
97
|
-
|
|
98
|
-
can still return shared objects captured outside the
|
|
107
|
+
The replacement must satisfy the original service contract. Each independent
|
|
108
|
+
container owns its own acquisitions and disposal. Close it separately. Factories
|
|
109
|
+
can still return shared objects captured outside the container.
|
|
99
110
|
|
|
100
111
|
## Work with async services
|
|
101
112
|
|
|
@@ -106,35 +117,36 @@ factory and await it where you need the value:
|
|
|
106
117
|
import { DiBag } from 'di-bag';
|
|
107
118
|
|
|
108
119
|
const app = DiBag.createBuilder()
|
|
109
|
-
.
|
|
120
|
+
.withServices({
|
|
110
121
|
greeting: async () => 'Hello',
|
|
111
122
|
message: async ({ greeting }: { greeting: Promise<string> }) =>
|
|
112
123
|
`${await greeting}, Ada!`,
|
|
113
124
|
})
|
|
114
|
-
.
|
|
125
|
+
.buildContainer();
|
|
115
126
|
|
|
116
127
|
console.log(await app.resolve('message')); // Hello, Ada!
|
|
128
|
+
await app.close();
|
|
117
129
|
```
|
|
118
130
|
|
|
119
|
-
|
|
131
|
+
Within one container, repeated resolutions share the same pending promise. Synchronous
|
|
120
132
|
factories keep returning ordinary values. See
|
|
121
133
|
[async behavior](docs/guides/tutorial.md#async-edges-are-explicit) for details.
|
|
122
134
|
|
|
123
135
|
## Give resources a clear owner
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
Use `DiBag.providerWithDisposal` to tell the container how to release a factory's result:
|
|
126
138
|
|
|
127
139
|
```ts
|
|
128
140
|
import { DiBag } from 'di-bag';
|
|
129
141
|
|
|
130
142
|
const resources = DiBag.createBuilder()
|
|
131
|
-
.
|
|
132
|
-
cache: DiBag.
|
|
133
|
-
() => new Map<string, string>(),
|
|
134
|
-
|
|
135
|
-
),
|
|
143
|
+
.withServices({
|
|
144
|
+
cache: DiBag.providerWithDisposal({
|
|
145
|
+
provider: () => new Map<string, string>(),
|
|
146
|
+
disposeService: cache => cache.clear(),
|
|
147
|
+
}),
|
|
136
148
|
})
|
|
137
|
-
.
|
|
149
|
+
.buildContainer();
|
|
138
150
|
|
|
139
151
|
try {
|
|
140
152
|
resources.resolve('cache').set('answer', '42');
|
|
@@ -143,18 +155,21 @@ try {
|
|
|
143
155
|
}
|
|
144
156
|
```
|
|
145
157
|
|
|
146
|
-
The same pattern works for connections, clients, and subscriptions.
|
|
158
|
+
The same pattern works for connections, clients, and subscriptions. Disposal can
|
|
147
159
|
be asynchronous. Dependents close before their dependencies, and resources that
|
|
148
|
-
were never created need no
|
|
149
|
-
|
|
160
|
+
were never created need no disposal. Ordinary factories return borrowed values.
|
|
161
|
+
Having a `close()` method alone does not transfer ownership to the container.
|
|
150
162
|
|
|
151
|
-
##
|
|
163
|
+
## Child and independent containers {#child-and-independent-containers}
|
|
152
164
|
|
|
153
165
|
| Operation | What it creates | Who closes it? |
|
|
154
166
|
| --- | --- | --- |
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
167
|
+
| `container.createChildContainer()` | A tracked child with its own scoped services | Close it when its work ends. The parent closes live children. |
|
|
168
|
+
| `container.createChildContainer(keys, providers)` | A tracked child with selected scoped or transient services replaced | Close it when its work ends. The parent closes live children. |
|
|
169
|
+
| `container.createIndependentContainer()` | A separate container with fresh instances | The caller closes it separately. |
|
|
170
|
+
| `container.createIndependentContainer(keys, providers)` | A separate container with selected dependencies replaced | The caller closes it separately. |
|
|
171
|
+
|
|
172
|
+
An unmarked provider is scoped. Choose singleton explicitly when child containers should share it.
|
|
158
173
|
|
|
159
174
|
For request handling, checked test replacements, and loading dynamic features,
|
|
160
175
|
see the [server guide](docs/guides/server-integration.md) and
|
|
@@ -162,7 +177,7 @@ see the [server guide](docs/guides/server-integration.md) and
|
|
|
162
177
|
|
|
163
178
|
The [tutorial](docs/guides/tutorial.md) also covers modules with private services,
|
|
164
179
|
typed tokens, class and function adapters, optional and lazy dependencies,
|
|
165
|
-
collections,
|
|
180
|
+
collections, service readiness, metadata, observers, and plugin validation.
|
|
166
181
|
|
|
167
182
|
## Modules as units of work
|
|
168
183
|
|
|
@@ -171,19 +186,24 @@ own: a directory with a small exported contract, private services, and its own
|
|
|
171
186
|
tests. The composition is checked when the modules meet, so several modules can
|
|
172
187
|
be developed in parallel and merged with confidence.
|
|
173
188
|
|
|
174
|
-
- **A boundary an owner can hold.** `buildModule(
|
|
189
|
+
- **A boundary an owner can hold.** `buildModule({ exportedServiceKeys })` seals a feature and
|
|
175
190
|
exports only the named services. Private services and their types stay
|
|
176
191
|
inside, and two modules can use the same private names without collision.
|
|
177
|
-
- **Verification without the whole application.** A module
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
- **Verification without the whole application.** A module declares what its host
|
|
193
|
+
must supply. A small check file installs the module with typed fixtures and calls
|
|
194
|
+
`verifyGraphAtCompileTime()`. Tests can use `createIndependentContainer(keys, providers)`
|
|
195
|
+
for checked replacements without live clients.
|
|
181
196
|
- **Checks at merge time.** Installing every module into one builder is where
|
|
182
197
|
independently developed work meets. A missing requirement, an incompatible
|
|
183
198
|
replacement, or a contract that no longer matches its consumers fails at
|
|
184
|
-
`
|
|
199
|
+
`buildContainer()` or `verifyGraphAtCompileTime()`. The `di-bag-graph` tool exports the declared
|
|
185
200
|
edges and cycles for review.
|
|
186
201
|
|
|
202
|
+
Install modules with `withInstalledModules([...])`. If two modules require the
|
|
203
|
+
same name for different contracts, rename each requirement before installation
|
|
204
|
+
with `withRenamedRequirement({ currentRequirementKey, newRequirementKey })`. Use
|
|
205
|
+
`withRenamedExport({ currentExportKey, newExportKey })` for colliding exports.
|
|
206
|
+
|
|
187
207
|
For discovery, the directory layout is the map: one directory per module, the
|
|
188
208
|
contract first. The [modularity guide](docs/guides/examples-modularity.md)
|
|
189
209
|
describes the recommended layout and shows separately owned features, isolated
|
|
@@ -204,30 +224,28 @@ async factories, and TypeScript support are also available in other libraries.
|
|
|
204
224
|
|
|
205
225
|
| Alternative | Reasons to choose it | DI Bag's different emphasis |
|
|
206
226
|
| --- | --- | --- |
|
|
207
|
-
| Manual dependency injection | Direct function calls may be all a small application needs. TypeScript checks their arguments. | Adds lazy caching, graph-wide composition checks,
|
|
227
|
+
| Manual dependency injection | Direct function calls may be all a small application needs. TypeScript checks their arguments. | Adds lazy caching, graph-wide composition checks, child containers, and coordinated disposal. |
|
|
208
228
|
| Awilix | Function and class registration, inferred cradle types, lifetime options, and runtime strict checks. | Checks declared factory requirements against the registrations at compile time. |
|
|
209
229
|
| InversifyJS / TSyringe | Token and class-oriented containers; Inversify also offers decorator-free factory bindings and awaited async resolution. | Starts with object-parameter factories and immutable builders; checks accumulated graph contracts. |
|
|
210
|
-
| Typed Inject | A close alternative with compile-time dependency checks, explicit dependency tuples, child injectors, and disposal. | Adds object-parameter dependencies, forward references, private module exports, and selected
|
|
211
|
-
| Effect Context / Layer | Typed requirements, scoped resources, and composition within Effect's broader async and error model. | Keeps ordinary `T` and `Promise<T>` service values and explicit
|
|
230
|
+
| Typed Inject | A close alternative with compile-time dependency checks, explicit dependency tuples, child injectors, and disposal. | Adds object-parameter dependencies, forward references, private module exports, and selected service readiness with rollback. |
|
|
231
|
+
| Effect Context / Layer | Typed requirements, scoped resources, and composition within Effect's broader async and error model. | Keeps ordinary `T` and `Promise<T>` service values and explicit container ownership. |
|
|
212
232
|
| NestJS / Angular DI | Their native containers connect directly to framework components, testing tools, and lifecycles. | Provides standalone composition; applications supply the framework integration. |
|
|
213
233
|
|
|
214
234
|
See the [comparison guide](docs/guides/comparison.md) for primary sources,
|
|
215
|
-
differences in async and
|
|
235
|
+
differences in async and disposal behavior, and the limits of these comparisons.
|
|
216
236
|
There is no verified performance ranking against these libraries.
|
|
217
237
|
|
|
218
238
|
## Runtime support
|
|
219
239
|
|
|
220
|
-
The package has **zero runtime dependencies** and
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
| --- | --- |
|
|
224
|
-
| `di-bag` | The entry to use. Configures native Promise detection itself on Node, Bun, and Deno through `process.getBuiltinModule`; has no `node:` imports, so it also bundles for browsers. |
|
|
225
|
-
| `di-bag/node` | The same API with detection configured explicitly at import, for Node and Bun. |
|
|
240
|
+
The package has **zero runtime dependencies** and one entry point: `di-bag`.
|
|
241
|
+
The root import configures native Promise detection on Node, Bun, and Deno.
|
|
242
|
+
It also bundles for browsers and workers because it has no `node:` imports.
|
|
226
243
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
In browsers and workers, `buildContainer()` names any factory that needs an
|
|
245
|
+
explicit return kind. Register synchronous factories with
|
|
246
|
+
`DiBag.createProvider(factory, { factoryReturnKind: 'sync-value' })` and native
|
|
247
|
+
Promise factories with `'native-promise'`. See
|
|
248
|
+
[portable mode](docs/guides/tutorial.md#portable-mode).
|
|
231
249
|
|
|
232
250
|
## Tradeoffs and limits
|
|
233
251
|
|
|
@@ -235,20 +253,23 @@ or give each stage an explicit acquisition mode. See
|
|
|
235
253
|
boundaries, manage an agent's context window, or replace behavioral tests.
|
|
236
254
|
- **Async dependencies are explicit.** A factory returning `Promise<T>` exposes
|
|
237
255
|
that promise. Consumers declare and await it themselves.
|
|
238
|
-
- **
|
|
256
|
+
- **Disposal waits for your work by default.** Cancellation is cooperative; a
|
|
239
257
|
factory or disposer that never settles keeps `close()` pending. Pass
|
|
240
|
-
`close({
|
|
241
|
-
disposers still running and
|
|
258
|
+
`close({ waitTimeoutMs, abortSignal })` to stop waiting. The rejection names the
|
|
259
|
+
disposers still running, and disposal continues in the background.
|
|
242
260
|
- **Type safety follows the declared graph.** Casts, unchecked JavaScript, and
|
|
243
261
|
unknown plugins need appropriate runtime checks. Dependency cycles are detected
|
|
244
262
|
at runtime, or before running by [`di-bag-graph`](tools/graph/README.md).
|
|
245
|
-
- **Graph types have a compiler cost.**
|
|
246
|
-
compiler
|
|
247
|
-
|
|
248
|
-
|
|
263
|
+
- **Graph types have a compiler cost.** One fluent expression is bounded by the
|
|
264
|
+
compiler's recursion budget: classic TypeScript 6.0.3 accepts about 1,000
|
|
265
|
+
chained calls and overflows beyond that (about 950 for a bulk map followed by
|
|
266
|
+
individual replacements); native 7.0.2 has no such ceiling. Keep an
|
|
267
|
+
expression to 500 calls or fewer and use bulk registration, groups, or named
|
|
268
|
+
modules beyond that. See the [compiler evidence](docs/benchmarks/typescript.md).
|
|
249
269
|
- **Framework integration belongs to the application.** DI Bag provides the
|
|
250
270
|
composition and ownership primitives; the host connects request, job, or UI
|
|
251
|
-
lifecycles.
|
|
271
|
+
lifecycles. The [server guide](docs/guides/server-integration.md) and the
|
|
272
|
+
[React guide](docs/guides/react-integration.md) are tested recipes for both.
|
|
252
273
|
|
|
253
274
|
## Explore further
|
|
254
275
|
|
|
@@ -256,12 +277,13 @@ or give each stage an explicit acquisition mode. See
|
|
|
256
277
|
| --- | --- |
|
|
257
278
|
| [Complete tutorial](docs/guides/tutorial.md) | Learn every public API through examples, from first composition to advanced ownership. |
|
|
258
279
|
| [API reference](docs/guides/api-reference.md) | Exact generated signatures, overloads, type parameters, and API inventories. |
|
|
259
|
-
| [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request
|
|
280
|
+
| [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request containers, service readiness, and shutdown. |
|
|
281
|
+
| [React guide](docs/guides/react-integration.md) | Browser applications: one app runtime at bootstrap, project runtimes owned from effects, Strict Mode, cancellation, bounded teardown, and `useSyncExternalStore`. |
|
|
260
282
|
| [Radical modularity](docs/guides/examples-modularity.md) | The recommended module layout, separately owned features, isolated tests, and contributed tools. |
|
|
261
283
|
| [Agent docs](AGENTS.md) | Rules, module layout, and check commands for coding agents, with [recipes](docs/agent/recipes.md) and [errors](docs/agent/errors.md). Shipped in the package. |
|
|
262
284
|
| [Agent harnesses and graphs](docs/guides/agent-harnesses-and-graphs.md) | One worked application: model and tool modules, metadata inspection, and node tests with typed fixtures. |
|
|
263
285
|
| [Static dependency graph](docs/agent/recipes.md#review-merge) | Export every builder chain, declared edge, and cycle to JSON with `di-bag-graph` for merge review and CI. |
|
|
264
|
-
| [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers,
|
|
286
|
+
| [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, child containers, and provider metadata. |
|
|
265
287
|
| [Integration guide](docs/guides/enterprise-integration.md) | Tested recipes for request ownership, substitutions, and dynamic features. |
|
|
266
288
|
| [Comparison with alternatives](docs/guides/comparison.md) | When DI Bag or another approach may be a better fit, with primary sources. |
|
|
267
289
|
| [Development and verification](docs/guides/development.md) | Full checks, portable runtime testing, compiler scale, and performance evidence. |
|
|
@@ -1,48 +1,66 @@
|
|
|
1
1
|
import type { Provider } from './provider';
|
|
2
2
|
import type { Factory } from './registration';
|
|
3
|
-
import type { Acquired,
|
|
3
|
+
import type { Acquired, FactoryReturnKind, NativeOutput, AutoOutput, SyncOutput } from './acquisition-mode';
|
|
4
4
|
import type { TokenDependencyContract } from './token-types';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Why a pushed disposer is running: the factory never returned, or it did and the
|
|
7
|
+
* service disposer — the `withDisposal` on the value this factory returned — has
|
|
8
|
+
* just run. Ownership a consumer attaches to a transformed value does not count.
|
|
9
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#release-partial-acquisition
|
|
8
10
|
*/
|
|
9
|
-
export interface
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
export interface DisposerContext {
|
|
12
|
+
/**
|
|
13
|
+
* `'factory-failed'`: the factory threw, rejected, or was cancelled; no service exists.
|
|
14
|
+
* `'no-service-disposer'`: the factory returned and no `withDisposal` owns that value.
|
|
15
|
+
* `'service-disposed'`: the service disposer ran without throwing.
|
|
16
|
+
* `'service-disposal-failed'`: the service disposer threw; pushed disposers still run.
|
|
17
|
+
*/
|
|
18
|
+
readonly reason: 'factory-failed' | 'no-service-disposer' | 'service-disposed' | 'service-disposal-failed';
|
|
12
19
|
}
|
|
13
|
-
type ContextFactory = (this: void, deps: never, context: AcquisitionContext) => unknown;
|
|
14
20
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#
|
|
21
|
+
* Cooperative cancellation and acquisition-local ownership supplied to a context-aware factory.
|
|
22
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#make-selected-services-ready
|
|
17
23
|
*/
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
readonly
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export interface FactoryContext {
|
|
25
|
+
/** Aborted when the acquisition's owning container begins closing. */
|
|
26
|
+
readonly abortSignal: AbortSignal;
|
|
27
|
+
/**
|
|
28
|
+
* Own a resource acquired during this factory call; pushed disposers run once in reverse order.
|
|
29
|
+
* Pass the release callback itself, such as `() => socket.close()`, rather than calling it here.
|
|
30
|
+
* @param disposer - Releases the acquired resource when the factory fails or the container closes.
|
|
31
|
+
*/
|
|
32
|
+
pushDisposer(this: void, disposer: (this: void, disposerContext: DisposerContext) => void | Promise<void>): void;
|
|
33
|
+
}
|
|
26
34
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @
|
|
30
|
-
* @param options - Context selection and result policy; acquisitionMode defaults to auto.
|
|
31
|
-
* @returns A lazy provider preserving exact output and named dependencies; adds no ownership.
|
|
32
|
-
* @typeParam F - The complete callback signature, retaining dependency and output inference.
|
|
33
|
-
* @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
|
|
35
|
+
* The named-dependency factory contract retained by an acquisition-context callback.
|
|
36
|
+
* @typeParam F - The contextual callback whose named dependencies and return type are retained.
|
|
37
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#make-selected-services-ready
|
|
34
38
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
export type ContextualFactory<F extends (this: void, dependencies: never, factoryContext: never) => unknown> = (this: void, dependencies: Parameters<F> extends [] ? {} : Parameters<F>[0]) => ReturnType<F>;
|
|
40
|
+
type ReturnKindAdmission<Output, ReturnKind extends FactoryReturnKind> = NativeOutput<Output, NoInfer<ReturnKind>> & AutoOutput<Output, NoInfer<ReturnKind>> & SyncOutput<Output, NoInfer<ReturnKind>>;
|
|
41
|
+
type CheckedReturnKindOptions<Output, ReturnKind extends FactoryReturnKind> = 'auto-detect' extends ReturnKind ? unknown extends NativeOutput<Output, NoInfer<ReturnKind>> & SyncOutput<Output, NoInfer<ReturnKind>> ? {
|
|
42
|
+
readonly factoryReturnKind?: ReturnKind & ReturnKindAdmission<Output, ReturnKind>;
|
|
43
|
+
} : {
|
|
44
|
+
readonly factoryReturnKind: ReturnKind & ReturnKindAdmission<Output, ReturnKind>;
|
|
45
|
+
} : {
|
|
46
|
+
readonly factoryReturnKind: ReturnKind & ReturnKindAdmission<Output, ReturnKind>;
|
|
47
|
+
};
|
|
38
48
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
42
|
-
* @param options - Optional result acquisitionMode, defaulting to auto.
|
|
43
|
-
* @returns A lazy provider retaining exact output and dependency types without adding ownership.
|
|
44
|
-
* @typeParam F - The exact factory signature and exposed result.
|
|
45
|
-
* @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
|
|
49
|
+
* Create a provider from a named-dependency factory that also receives FactoryContext.
|
|
50
|
+
* @typeParam F - The exact callback signature and output.
|
|
51
|
+
* @typeParam ReturnKind - How its output is acquired.
|
|
46
52
|
*/
|
|
47
|
-
export declare function
|
|
53
|
+
export declare function createProvider<F extends (this: void, dependencies: never, factoryContext: FactoryContext) => any, ReturnKind extends FactoryReturnKind = 'auto-detect'>(factory: F & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<ReturnKind>>, options: {
|
|
54
|
+
readonly factoryReceivesContext: true;
|
|
55
|
+
} & CheckedReturnKindOptions<ReturnType<NoInfer<F>>, ReturnKind>): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, ReturnKind>>;
|
|
56
|
+
export declare function createProvider<F extends FactoryType, ReturnKind extends FactoryReturnKind = 'auto-detect'>(factory: F & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<ReturnKind>>, ...options: {} extends CheckedReturnKindOptions<ReturnType<NoInfer<F>>, ReturnKind> ? [
|
|
57
|
+
options?: {
|
|
58
|
+
readonly factoryReceivesContext?: never;
|
|
59
|
+
} & CheckedReturnKindOptions<ReturnType<NoInfer<F>>, ReturnKind>
|
|
60
|
+
] : [
|
|
61
|
+
options: {
|
|
62
|
+
readonly factoryReceivesContext?: never;
|
|
63
|
+
} & CheckedReturnKindOptions<ReturnType<NoInfer<F>>, ReturnKind>
|
|
64
|
+
]): Provider<F, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, ReturnKind>>;
|
|
65
|
+
type FactoryType = Factory;
|
|
48
66
|
export {};
|