di-bag 0.2.0 → 0.3.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 +149 -0
- package/README.md +59 -46
- package/dist/acquisition-context.d.ts +8 -2
- package/dist/acquisition-mode.d.ts +9 -4
- package/dist/acquisition-mode.js +25 -7
- package/dist/acquisition.d.ts +2 -0
- package/dist/acquisition.js +9 -0
- package/dist/alias-types.d.ts +12 -3
- package/dist/composition-report.d.ts +3 -2
- package/dist/composition.d.ts +8 -2
- package/dist/contribution-types.d.ts +26 -18
- package/dist/dependency-references.d.ts +16 -4
- package/dist/di-bag.d.ts +320 -41
- package/dist/di-bag.js +86 -14
- package/dist/errors.d.ts +115 -8
- package/dist/errors.js +113 -12
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -1
- package/dist/inspection.d.ts +21 -5
- package/dist/lifetime-types.d.ts +281 -180
- package/dist/lifetime.d.ts +4 -1
- package/dist/module-types.d.ts +87 -30
- package/dist/module.d.ts +18 -4
- package/dist/module.js +31 -7
- package/dist/observers.d.ts +25 -6
- package/dist/plugins.d.ts +17 -4
- package/dist/provider.d.ts +41 -10
- package/dist/provider.js +1 -0
- package/dist/registration.d.ts +8 -2
- package/dist/registration.js +4 -1
- package/dist/runtime.d.ts +12 -3
- package/dist/runtime.js +25 -8
- package/dist/scope-types.d.ts +20 -5
- package/dist/startup.d.ts +19 -1
- package/dist/startup.js +72 -11
- package/dist/token-types.d.ts +26 -8
- package/dist/tokens.d.ts +10 -2
- package/dist/tokens.js +2 -0
- package/dist/types.d.ts +68 -22
- package/docs/agent/api-card.md +337 -0
- package/docs/agent/errors.md +1042 -0
- package/docs/agent/recipes.md +290 -0
- package/package.json +11 -5
package/AGENTS.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# DI Bag: notes for coding agents
|
|
2
|
+
|
|
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 bag
|
|
5
|
+
creates services on first use and releases what it owns when closed.
|
|
6
|
+
|
|
7
|
+
This file ships in `node_modules/di-bag/`. Every call, with one way per task and an example:
|
|
8
|
+
[docs/agent/api-card.md](docs/agent/api-card.md). Task recipes: [docs/agent/recipes.md](docs/agent/recipes.md).
|
|
9
|
+
Every compiler and runtime message: [docs/agent/errors.md](docs/agent/errors.md).
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
1. **Import from `di-bag`:** `import { DiBag } from 'di-bag';`. It configures
|
|
14
|
+
itself on Node, Bun, and Deno; `di-bag/node` is the same API in explicit form.
|
|
15
|
+
In browsers and workers `build()` throws
|
|
16
|
+
[`DI_BAG_CLASSIFIER_REQUIRED`](docs/agent/errors.md#di-bag-classifier-required)
|
|
17
|
+
for factories without an explicit `acquisitionMode`.
|
|
18
|
+
2. **A factory declares its dependencies in the type of its one object
|
|
19
|
+
parameter; destructure it** (`({ clock }: { clock: Clock }) => ...`) or read
|
|
20
|
+
`deps.clock` directly. The object is a Proxy that resolves each property when
|
|
21
|
+
read: spreading it, `Object.keys`, `in`, and `JSON.stringify` throw
|
|
22
|
+
[`DI_BAG_INVALID_DEPENDENCY_ACCESS`](docs/agent/errors.md#di-bag-invalid-dependency-access).
|
|
23
|
+
3. **Lifetimes.** The default is `scoped`: one instance per bag or child scope.
|
|
24
|
+
Mark a shared client `DiBag.withLifetime(factory, 'root')` only when nothing
|
|
25
|
+
it depends on is scoped; otherwise the compiler reports a
|
|
26
|
+
[root capture](docs/agent/errors.md#root-capture) naming both keys.
|
|
27
|
+
`'transient'` creates an instance on every read.
|
|
28
|
+
4. **Async is explicit.** An async factory's service is its Promise. A consumer
|
|
29
|
+
declares `{ db: Promise<Db> }` and awaits it; nothing is awaited for you.
|
|
30
|
+
5. **No thenables.** A factory that returns a non-Promise object with a `then`
|
|
31
|
+
method (query builders) is [rejected](docs/agent/errors.md#structural-thenable).
|
|
32
|
+
Return `Promise.resolve(builder)` or use `DiBag.fromFactory(create, { acquisitionMode: 'raw' })`.
|
|
33
|
+
6. **Ownership.** `DiBag.withDisposal(factory, dispose)` makes the bag own the
|
|
34
|
+
value; `close()` runs disposers, dependents first. Close every scope and fork
|
|
35
|
+
you create. A parent closes its live scopes, never forks.
|
|
36
|
+
7. **Replace dependencies in tests with `fork(keys, overrides)`**; each
|
|
37
|
+
override must satisfy the original contract.
|
|
38
|
+
8. **Modules.** Register a feature's factories, then `buildModule(['exported'])`.
|
|
39
|
+
What its factories need and the module does not register becomes a
|
|
40
|
+
requirement: the host that calls `installModule(module)` must register it.
|
|
41
|
+
Pass `buildModule(keys, { label: 'billing' })` so messages name private
|
|
42
|
+
services `billing/store`.
|
|
43
|
+
9. **Read a rejection at its name.** A graph error is an assignability error
|
|
44
|
+
whose type is `Unsatisfied<"message", details>`, reported where the builder
|
|
45
|
+
expression starts. `builder.verifyGraph() satisfies void;` reports the same
|
|
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
|
+
text. The section for a code is `docs/agent/errors.md#<code>`, lower-cased
|
|
49
|
+
with `_` replaced by `-`.
|
|
50
|
+
|
|
51
|
+
## Module layout
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
src/features/invoicing/
|
|
55
|
+
contract.ts # exported service types and the requirements the host must supply
|
|
56
|
+
module.ts # buildModule([...]) over the private factories
|
|
57
|
+
store.ts # private services; free to use names other modules also use
|
|
58
|
+
check.ts # type-checks this module alone; never imported, not built
|
|
59
|
+
tsconfig.json # extends the root tsconfig and includes only this directory
|
|
60
|
+
invoicing.test.ts
|
|
61
|
+
src/app.ts # installs every module, one installModule call per line
|
|
62
|
+
src/app.check.ts # verifyGraph() on the application builder: the merge check
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Inside a file, keep the same order: contract types, private factories, the
|
|
66
|
+
sealed module, then the host. Exclude `check.ts` files and `src/app.check.ts`
|
|
67
|
+
from an emitting build; they have no runtime purpose.
|
|
68
|
+
|
|
69
|
+
A complete module:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// src/features/greeting/contract.ts
|
|
73
|
+
export type Greeter = { greet(name: string): string };
|
|
74
|
+
export type GreetingConfig = { greeting: string };
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// src/features/greeting/module.ts
|
|
79
|
+
import { DiBag } from 'di-bag';
|
|
80
|
+
import type { Greeter, GreetingConfig } from './contract.js';
|
|
81
|
+
|
|
82
|
+
export const greetingModule = DiBag.createBuilder()
|
|
83
|
+
.register({
|
|
84
|
+
greeter: ({ config }: { config: GreetingConfig }): Greeter => ({
|
|
85
|
+
greet: name => `${config.greeting}, ${name}!`,
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
.buildModule(['greeter']);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`check.ts` is one statement: install the module, register a typed fixture for
|
|
92
|
+
each requirement, and verify.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// src/features/greeting/check.ts
|
|
96
|
+
import { DiBag } from 'di-bag';
|
|
97
|
+
import type { GreetingConfig } from './contract.js';
|
|
98
|
+
import { greetingModule } from './module.js';
|
|
99
|
+
|
|
100
|
+
DiBag.createBuilder()
|
|
101
|
+
.installModule(greetingModule)
|
|
102
|
+
.register({ config: (): GreetingConfig => ({ greeting: 'Hello' }) })
|
|
103
|
+
.verifyGraph() satisfies void;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Check one module
|
|
107
|
+
|
|
108
|
+
`src/features/<name>/tsconfig.json`:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"extends": "../../../tsconfig.json",
|
|
113
|
+
"include": ["."]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Type-check the module and its `check.ts` without the rest of the application:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
npx tsc --noEmit -p src/features/<name>/tsconfig.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A missing requirement fails with its key:
|
|
124
|
+
`required service registrations are missing: config`.
|
|
125
|
+
|
|
126
|
+
## Fast check
|
|
127
|
+
|
|
128
|
+
Define `check:fast` in the consumer's `package.json` as the per-module
|
|
129
|
+
type-check plus that module's tests, and run it after every edit:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
"check:fast": "tsc --noEmit -p src/features/$MODULE/tsconfig.json && tsx --test src/features/$MODULE/*.test.ts"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
MODULE=greeting npm run check:fast
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Replace `tsx --test` with the project's test runner. Run the full type-check and
|
|
140
|
+
test suite before merging: [review a merge](docs/agent/recipes.md#review-merge).
|
|
141
|
+
|
|
142
|
+
## Recipes
|
|
143
|
+
|
|
144
|
+
- [Add a request-scoped service with cleanup](docs/agent/recipes.md#add-scoped-service)
|
|
145
|
+
- [Write a fixture test with `fork`](docs/agent/recipes.md#fixture-test)
|
|
146
|
+
- [Split a feature into a module with private services](docs/agent/recipes.md#split-module)
|
|
147
|
+
- [Debug a missing-dependency rejection](docs/agent/recipes.md#debug-missing-dependency)
|
|
148
|
+
- [Add and consume an async client](docs/agent/recipes.md#async-client)
|
|
149
|
+
- [Review a merge](docs/agent/recipes.md#review-merge)
|
package/README.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# DI Bag
|
|
2
2
|
|
|
3
|
-
TypeScript dependency composition and resource ownership for
|
|
3
|
+
TypeScript dependency composition and resource ownership for modular codebases,
|
|
4
|
+
including the ones coding agents build one feature at a time.
|
|
4
5
|
|
|
5
|
-
[Documentation](https://dany-fedorov.github.io/di-bag/) · [Quickstart](#quickstart) · [
|
|
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)
|
|
6
7
|
|
|
7
8
|
## Why DI Bag?
|
|
8
9
|
|
|
9
10
|
Compose ordinary TypeScript factories into reusable features. DI Bag checks
|
|
10
11
|
declared dependencies, keeps module internals private, and manages resource
|
|
11
|
-
creation and cleanup. Build
|
|
12
|
-
|
|
12
|
+
creation and cleanup. Build each feature against an explicit contract, test it
|
|
13
|
+
with replaced dependencies, and let the compiler check the composition when
|
|
14
|
+
independently developed features come together.
|
|
13
15
|
|
|
14
|
-
- **[
|
|
15
|
-
|
|
16
|
+
- **[Modules a single owner can build in isolation](docs/guides/examples-modularity.md).**
|
|
17
|
+
A person or a coding agent implements one feature against its contract
|
|
18
|
+
without exposing its internals or reading another feature's source.
|
|
16
19
|
- **[Compile-time wiring checks](docs/guides/examples-type-checking.md).**
|
|
17
20
|
Catch missing dependencies and incompatible replacements before starting the app.
|
|
18
21
|
- **[Metadata inspection without service startup](docs/guides/examples-extensibility.md).**
|
|
@@ -42,11 +45,11 @@ A **factory** creates a service. A **bag** holds those factories and gives each
|
|
|
42
45
|
one access to the services it needs. Services are created when needed, and
|
|
43
46
|
resources are cleaned up when you provide a disposer and close their bag.
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
Import from `di-bag`. Here, `greeter` needs `config`. Its parameter
|
|
46
49
|
type describes that dependency, and its return value is the service it provides:
|
|
47
50
|
|
|
48
51
|
```ts
|
|
49
|
-
import { DiBag } from 'di-bag
|
|
52
|
+
import { DiBag } from 'di-bag';
|
|
50
53
|
|
|
51
54
|
const app = DiBag.createBuilder()
|
|
52
55
|
.register({
|
|
@@ -100,7 +103,7 @@ An async factory provides a promise. Declare that promise in any dependent
|
|
|
100
103
|
factory and await it where you need the value:
|
|
101
104
|
|
|
102
105
|
```ts
|
|
103
|
-
import { DiBag } from 'di-bag
|
|
106
|
+
import { DiBag } from 'di-bag';
|
|
104
107
|
|
|
105
108
|
const app = DiBag.createBuilder()
|
|
106
109
|
.register({
|
|
@@ -122,7 +125,7 @@ factories keep returning ordinary values. See
|
|
|
122
125
|
Wrap a factory with `withDisposal` to tell the bag how to release its result:
|
|
123
126
|
|
|
124
127
|
```ts
|
|
125
|
-
import { DiBag } from 'di-bag
|
|
128
|
+
import { DiBag } from 'di-bag';
|
|
126
129
|
|
|
127
130
|
const resources = DiBag.createBuilder()
|
|
128
131
|
.register({
|
|
@@ -161,32 +164,37 @@ The [tutorial](docs/guides/tutorial.md) also covers modules with private service
|
|
|
161
164
|
typed tokens, class and function adapters, optional and lazy dependencies,
|
|
162
165
|
collections, startup, metadata, observers, and plugin validation.
|
|
163
166
|
|
|
164
|
-
##
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
167
|
+
## Modules as units of work
|
|
168
|
+
|
|
169
|
+
A DI Bag **module** is the unit of work that one person or one coding agent can
|
|
170
|
+
own: a directory with a small exported contract, private services, and its own
|
|
171
|
+
tests. The composition is checked when the modules meet, so several modules can
|
|
172
|
+
be developed in parallel and merged with confidence.
|
|
173
|
+
|
|
174
|
+
- **A boundary an owner can hold.** `buildModule(keys)` seals a feature and
|
|
175
|
+
exports only the named services. Private services and their types stay
|
|
176
|
+
inside, and two modules can use the same private names without collision.
|
|
177
|
+
- **Verification without the whole application.** A module type-checks
|
|
178
|
+
against the contracts it declares. `fork()` replaces its external
|
|
179
|
+
dependencies with typed fixtures for deterministic tests, so a module's tests
|
|
180
|
+
need neither the other modules nor live clients.
|
|
181
|
+
- **Checks at merge time.** Installing every module into one builder is where
|
|
182
|
+
independently developed work meets. A missing requirement, an incompatible
|
|
183
|
+
replacement, or a contract that no longer matches its consumers fails at
|
|
184
|
+
`build()` or `verifyGraph()`. The `di-bag-graph` tool exports the declared
|
|
185
|
+
edges and cycles for review.
|
|
186
|
+
|
|
187
|
+
For discovery, the directory layout is the map: one directory per module, the
|
|
188
|
+
contract first. The [modularity guide](docs/guides/examples-modularity.md)
|
|
189
|
+
describes the recommended layout and shows separately owned features, isolated
|
|
190
|
+
tests, and contributed tools in three runnable programs.
|
|
191
|
+
|
|
192
|
+
DI Bag's dependency graph describes how services are supplied, not what runs
|
|
193
|
+
next. LLM harnesses and agent graphs are one application of the module pattern:
|
|
194
|
+
model clients, tools, and context sources become modules, and the harness or
|
|
195
|
+
graph framework owns routing, retries, persistence, and execution. The
|
|
196
|
+
[agent harness and graph guide](docs/guides/agent-harnesses-and-graphs.md) is a
|
|
197
|
+
complete example.
|
|
190
198
|
|
|
191
199
|
## How it compares
|
|
192
200
|
|
|
@@ -213,12 +221,13 @@ The package has **zero runtime dependencies** and two entry points:
|
|
|
213
221
|
|
|
214
222
|
| Import | Purpose |
|
|
215
223
|
| --- | --- |
|
|
216
|
-
| `di-bag
|
|
217
|
-
| `di-bag` |
|
|
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. |
|
|
218
226
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
On hosts without `process.getBuiltinModule` (browsers, workers), `build()`
|
|
228
|
+
rejects automatic acquisition stages unless you configure a trusted classifier
|
|
229
|
+
or give each stage an explicit acquisition mode. See
|
|
230
|
+
[portable mode](docs/guides/tutorial.md#portable-mode) for both options.
|
|
222
231
|
|
|
223
232
|
## Tradeoffs and limits
|
|
224
233
|
|
|
@@ -226,11 +235,13 @@ for both setup options.
|
|
|
226
235
|
boundaries, manage an agent's context window, or replace behavioral tests.
|
|
227
236
|
- **Async dependencies are explicit.** A factory returning `Promise<T>` exposes
|
|
228
237
|
that promise. Consumers declare and await it themselves.
|
|
229
|
-
- **Cleanup waits for your work.** Cancellation is cooperative; a
|
|
230
|
-
disposer that never settles
|
|
238
|
+
- **Cleanup waits for your work by default.** Cancellation is cooperative; a
|
|
239
|
+
factory or disposer that never settles keeps `close()` pending. Pass
|
|
240
|
+
`close({ timeoutMs, signal })` to stop waiting: the rejection names the
|
|
241
|
+
disposers still running and cleanup continues in the background.
|
|
231
242
|
- **Type safety follows the declared graph.** Casts, unchecked JavaScript, and
|
|
232
243
|
unknown plugins need appropriate runtime checks. Dependency cycles are detected
|
|
233
|
-
at runtime.
|
|
244
|
+
at runtime, or before running by [`di-bag-graph`](tools/graph/README.md).
|
|
234
245
|
- **Graph types have a compiler cost.** Very long fluent expressions can exceed
|
|
235
246
|
compiler limits. Classic TypeScript still fails the recorded 1,000-call named
|
|
236
247
|
registration and replacement cases; use bulk registration or smaller groups.
|
|
@@ -246,8 +257,10 @@ for both setup options.
|
|
|
246
257
|
| [Complete tutorial](docs/guides/tutorial.md) | Learn every public API through examples, from first composition to advanced ownership. |
|
|
247
258
|
| [API reference](docs/guides/api-reference.md) | Exact generated signatures, overloads, type parameters, and API inventories. |
|
|
248
259
|
| [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request scopes, startup, and shutdown. |
|
|
249
|
-
| [
|
|
250
|
-
| [
|
|
260
|
+
| [Radical modularity](docs/guides/examples-modularity.md) | The recommended module layout, separately owned features, isolated tests, and contributed tools. |
|
|
261
|
+
| [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
|
+
| [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
|
+
| [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. |
|
|
251
264
|
| [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, scopes, and provider metadata. |
|
|
252
265
|
| [Integration guide](docs/guides/enterprise-integration.md) | Tested recipes for request ownership, substitutions, and dynamic features. |
|
|
253
266
|
| [Comparison with alternatives](docs/guides/comparison.md) | When DI Bag or another approach may be a better fit, with primary sources. |
|
|
@@ -2,13 +2,19 @@ import type { Provider } from './provider';
|
|
|
2
2
|
import type { Factory } from './registration';
|
|
3
3
|
import type { Acquired, AcquisitionMode, AutoOutput, NativeOutput, ModeOptions } from './acquisition-mode';
|
|
4
4
|
import type { TokenDependencyContract } from './token-types';
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Cooperative cancellation information supplied to a context-aware acquisition.
|
|
7
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#start-selected-services-and-cancel-cooperatively
|
|
8
|
+
*/
|
|
6
9
|
export interface AcquisitionContext {
|
|
7
10
|
/** Aborted when the acquisition's owning scope begins closing. */
|
|
8
11
|
readonly signal: AbortSignal;
|
|
9
12
|
}
|
|
10
13
|
type ContextFactory = (this: void, deps: never, context: AcquisitionContext) => unknown;
|
|
11
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The named-dependency factory contract retained by an acquisition-context callback.
|
|
16
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#start-selected-services-and-cancel-cooperatively
|
|
17
|
+
*/
|
|
12
18
|
export type ContextualFactory<F extends ContextFactory> = (this: void, deps: Parameters<F> extends [] ? {} : Parameters<F>[0]) => ReturnType<F>;
|
|
13
19
|
type FactoryOptions<M extends AcquisitionMode> = 'auto' extends M ? [options?: {
|
|
14
20
|
readonly context?: never;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { LifecycleObservers } from './observers';
|
|
2
|
-
import type { StructuralThenable, Unsatisfied } from './types';
|
|
2
|
+
import type { SeeErrors, StructuralThenable, Unsatisfied } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* How an acquisition stage treats its returned value: configured classification,
|
|
5
5
|
* the exact raw value, or an observed native Promise fulfillment.
|
|
6
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#portable-mode
|
|
6
7
|
*/
|
|
7
8
|
export type AcquisitionMode = 'auto' | 'raw' | 'nativePromise';
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Portable facade configuration for `auto` acquisition stages.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#portable-mode
|
|
12
|
+
*/
|
|
9
13
|
export interface RuntimeOptions {
|
|
10
14
|
/** Return true only for native Promises the host can observe without thenable assimilation. */
|
|
11
15
|
readonly isNativePromise: (this: void, value: unknown) => boolean;
|
|
@@ -29,8 +33,9 @@ export type StageOptions<M extends AcquisitionMode> = 'auto' extends M ? [option
|
|
|
29
33
|
}];
|
|
30
34
|
export type NativeOutput<O, M extends AcquisitionMode> = 'nativePromise' extends M ? [O] extends [Promise<unknown>] ? unknown : Unsatisfied<'nativePromise acquisition requires a Promise output', {}> : unknown;
|
|
31
35
|
/** Reject a structural thenable output when the stage would classify it automatically. */
|
|
32
|
-
export type AutoOutput<O, M extends AcquisitionMode> = 'auto' extends M ? true extends StructuralThenable<O> ? Unsatisfied
|
|
36
|
+
export type AutoOutput<O, M extends AcquisitionMode> = 'auto' extends M ? true extends StructuralThenable<O> ? Unsatisfied<`factory output is a structural thenable; return a native Promise or select acquisitionMode raw or nativePromise${SeeErrors<'structural-thenable'>}`, {}> : unknown : unknown;
|
|
33
37
|
export declare function acquisitionMode(options: {
|
|
34
38
|
readonly acquisitionMode?: AcquisitionMode;
|
|
35
39
|
} | undefined, fallback?: AcquisitionMode): AcquisitionMode;
|
|
36
|
-
|
|
40
|
+
/** Resolve the classifier when a graph first needs one; a configured classifier always wins. */
|
|
41
|
+
export declare function requireClassifier(context: RuntimeContext): RuntimeContext;
|
package/dist/acquisition-mode.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.unconfigured = void 0;
|
|
4
4
|
exports.runtimeContext = runtimeContext;
|
|
5
5
|
exports.acquisitionMode = acquisitionMode;
|
|
6
|
-
exports.
|
|
6
|
+
exports.requireClassifier = requireClassifier;
|
|
7
7
|
const errors_1 = require("./errors");
|
|
8
8
|
exports.unconfigured = Object.freeze({});
|
|
9
9
|
function runtimeContext(options, previous = exports.unconfigured) {
|
|
@@ -25,11 +25,29 @@ function acquisitionMode(options, fallback = 'auto') {
|
|
|
25
25
|
throw (0, errors_1.libraryError)('DI_BAG_INVALID_ACQUISITION_MODE', 'invalid acquisitionMode: use auto, raw, or nativePromise', { option: 'acquisitionMode' });
|
|
26
26
|
return mode;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Read the host classifier through `process.getBuiltinModule` (Node, Bun, Deno). A call, not an
|
|
30
|
+
* import, keeps `node:` specifiers out of the root entry's module graph for bundlers and browsers.
|
|
31
|
+
*/
|
|
32
|
+
function hostClassifier() {
|
|
33
|
+
const host = globalThis.process;
|
|
34
|
+
if (typeof host !== 'object' || host === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
const load = host.getBuiltinModule;
|
|
37
|
+
if (typeof load !== 'function')
|
|
38
|
+
return undefined;
|
|
39
|
+
const types = Reflect.apply(load, host, ['node:util/types']);
|
|
40
|
+
if (typeof types !== 'object' || types === null)
|
|
41
|
+
return undefined;
|
|
42
|
+
const { isPromise } = types;
|
|
43
|
+
return typeof isPromise === 'function' ? isPromise : undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Resolve the classifier when a graph first needs one; a configured classifier always wins. */
|
|
46
|
+
function requireClassifier(context) {
|
|
29
47
|
if (context.isNativePromise)
|
|
30
|
-
return;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
return context;
|
|
49
|
+
const isNativePromise = hostClassifier();
|
|
50
|
+
if (isNativePromise)
|
|
51
|
+
return Object.freeze({ ...context, isNativePromise });
|
|
52
|
+
throw (0, errors_1.libraryError)('DI_BAG_CLASSIFIER_REQUIRED', 'this host has no process.getBuiltinModule; configure DiBag.withConfiguration({ runtime: { isNativePromise } }) or give each automatic registration an explicit acquisitionMode', { option: 'runtime.isNativePromise' });
|
|
35
53
|
}
|
package/dist/acquisition.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare class ScopeAcquisitions {
|
|
|
39
39
|
private assertAliasPath;
|
|
40
40
|
assertOpen(): void;
|
|
41
41
|
close(beforeDispose?: Promise<void>, cause?: unknown): Promise<void>;
|
|
42
|
+
/** Labels of this scope's running disposers and of acquisitions close is still draining. */
|
|
43
|
+
collectProgress(pending: string[], acquiring: string[]): void;
|
|
42
44
|
private getContext;
|
|
43
45
|
private resolveBinding;
|
|
44
46
|
private eventFields;
|
package/dist/acquisition.js
CHANGED
|
@@ -138,6 +138,15 @@ class ScopeAcquisitions {
|
|
|
138
138
|
});
|
|
139
139
|
return this.closing;
|
|
140
140
|
}
|
|
141
|
+
/** Labels of this scope's running disposers and of acquisitions close is still draining. */
|
|
142
|
+
collectProgress(pending, acquiring) {
|
|
143
|
+
for (const attempt of this.attempts.values()) {
|
|
144
|
+
if (attempt.state === 'disposing' || this.retired.has(attempt.id))
|
|
145
|
+
pending.push(attempt.label);
|
|
146
|
+
else if (attempt.state === 'creating' || attempt.state === 'pending')
|
|
147
|
+
acquiring.push(attempt.label);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
141
150
|
getContext() {
|
|
142
151
|
if (!this.acquisitionContext) {
|
|
143
152
|
this.controller = new AbortController();
|
package/dist/alias-types.d.ts
CHANGED
|
@@ -6,14 +6,23 @@ import type { Singleton, Unsatisfied } from './types';
|
|
|
6
6
|
export type AliasSelection = string | TokenBase;
|
|
7
7
|
export type AliasAdmission<T> = Singleton<T> extends true ? unknown : ValidToken<T> extends true ? unknown : Unsatisfied<'alias requires one singleton name or genuine token', {}>;
|
|
8
8
|
export type AliasTarget<R extends Registrations, T> = T extends string ? T extends keyof R ? unknown : Unsatisfied<'alias requires an existing named target', {}> : [WrongToken<T, R>] extends [never] ? unknown : Unsatisfied<'token dependency has an incompatible or opaque contract', {}>;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the service type exposed by a possible alias target.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
12
|
+
*/
|
|
10
13
|
export type AliasOutput<R extends Registrations, T> = T extends string ? T extends keyof R ? ProviderOutput<R[T]> : never : TokenService<T>;
|
|
11
14
|
export type AliasDestination<R extends Registrations, D, T> = D extends TokenBase ? [AliasOutput<R, T>] extends [TokenService<D>] ? unknown : Unsatisfied<'alias output is not assignable to destination service', {}> : unknown;
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A provider contract that forwards a destination to a canonical target acquisition.
|
|
17
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
18
|
+
*/
|
|
13
19
|
export type AliasRegistration<R extends Registrations, D, T> = Provider<(this: void, deps: T extends string ? Record<T, AliasOutput<R, T>> : Record<never, never>) => AliasOutput<R, T>, Readonly<object>, readonly unknown[], TokenDependencyContract<T extends TokenBase ? readonly [T] : readonly [], D extends TokenBase ? D : never> & {
|
|
14
20
|
readonly alias: SelectionKey<T>;
|
|
15
21
|
}, unknown>;
|
|
16
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The single registration-map entry introduced by an alias operation.
|
|
24
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
25
|
+
*/
|
|
17
26
|
export type AliasEntries<R extends Registrations, D, T> = Record<SelectionKey<D>, AliasRegistration<R, D, T>>;
|
|
18
27
|
/** Reflected generic methods cannot introduce a checked singleton destination. */
|
|
19
28
|
export type AliasEntry<R extends Registrations, D, T> = unknown extends AliasAdmission<D> & AliasAdmission<T> ? {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Builder } from './di-bag';
|
|
2
2
|
import type { CheckedLifetimes } from './lifetime-types';
|
|
3
3
|
import type { CheckedConstraints, CompleteConstraints, NeedConstraint } from './module-types';
|
|
4
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Entry, RegistrationsFromEntries } from './types';
|
|
4
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, ConsumerReport, Entry, RegistrationsFromEntries } from './types';
|
|
5
5
|
type ReportOf<Check> = unknown extends Check ? never : Check;
|
|
6
|
-
type Reports<E extends Entry, C extends NeedConstraint> = ReportOf<CheckDependencyCompatibility<RegistrationsFromEntries<E
|
|
6
|
+
type Reports<E extends Entry, C extends NeedConstraint> = ReportOf<ConsumerReport<CheckDependencyCompatibility<RegistrationsFromEntries<E>>>> | ReportOf<CheckDependencyCompleteness<RegistrationsFromEntries<E>>> | ReportOf<ConsumerReport<CheckedConstraints<C, RegistrationsFromEntries<E>>>> | ReportOf<CompleteConstraints<C, RegistrationsFromEntries<E>>> | ReportOf<CheckedLifetimes<RegistrationsFromEntries<E>, C>>;
|
|
7
7
|
/**
|
|
8
8
|
* The compile-time verdict for a builder: `void` when `build()` would be accepted,
|
|
9
9
|
* otherwise the same failure `build()` reports, including its details.
|
|
10
10
|
* Read it through `builder.verifyGraph() satisfies void;` or as `CompositionReport<typeof builder>`.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#read-compile-time-rejections
|
|
11
12
|
*/
|
|
12
13
|
export type CompositionReport<B> = B extends Builder<infer E, infer C> ? [Reports<E, C>] extends [never] ? void : Reports<E, C> : never;
|
|
13
14
|
export {};
|
package/dist/composition.d.ts
CHANGED
|
@@ -4,12 +4,18 @@ import type { DependencyReference } from './dependency-references';
|
|
|
4
4
|
import type { TokenArguments, ReferenceGraph, DependencyTupleAdmission } from './token-types';
|
|
5
5
|
import type { Unsatisfied } from './types';
|
|
6
6
|
type OutputFactory<O> = () => O;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Compile-time admission that checks supplied token values against a callable's parameter tuple.
|
|
9
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#adapt-classes-and-positional-functions
|
|
10
|
+
*/
|
|
8
11
|
export type CompositionArguments<A extends readonly unknown[], P extends readonly unknown[]> = [A] extends [P] ? unknown : Unsatisfied<'composition arguments must match the declared parameter tuple', {
|
|
9
12
|
supplied: A;
|
|
10
13
|
parameters: P;
|
|
11
14
|
}>;
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A receiver-free positional callback matching the values supplied by a dependency tuple.
|
|
17
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#adapt-classes-and-positional-functions
|
|
18
|
+
*/
|
|
13
19
|
export type CompositionFunction<T extends readonly DependencyReference[], O = unknown> = TokenArguments<T> extends [...infer A] ? (this: void, ...args: A) => O : never;
|
|
14
20
|
/**
|
|
15
21
|
* Adapt a positional function without awaiting its arguments or return value.
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import type { Registration, Registrations } from './registration';
|
|
2
2
|
import type { TokenBase, TokenKey, TokenService } from './tokens';
|
|
3
3
|
import type { ValidToken, TokenTupleAdmission, BindingOutput } from './token-types';
|
|
4
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Unsatisfied, Entry, RegistrationsFromEntries } from './types';
|
|
4
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, SeeErrors, Unsatisfied, Entry, RegistrationsFromEntries } from './types';
|
|
5
5
|
import type { ProviderCollectionTokens } from './provider';
|
|
6
6
|
import type { Module } from './module';
|
|
7
7
|
import type { RegistrationConstraints, PublicProvider, NeedConstraint, CheckedConstraints } from './module-types';
|
|
8
|
-
import type { LexicalContext, ModuleScope, Enclosed, RenamedContext } from './lifetime-types';
|
|
9
8
|
declare const contributionSite: unique symbol;
|
|
10
|
-
/**
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Each union member retains one independently checked provider and its group.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
12
|
+
*/
|
|
13
|
+
export type Contribution<T extends TokenBase = TokenBase, V extends Registration = Registration> = {
|
|
12
14
|
readonly kind: 'contribution';
|
|
13
15
|
readonly token: T;
|
|
14
16
|
readonly registration: V;
|
|
15
|
-
readonly context: L;
|
|
16
17
|
};
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The erased contribution contract retained by checked builders and modules.
|
|
20
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
21
|
+
*/
|
|
22
|
+
export type ContributionConstraint = Contribution<TokenBase, Registration>;
|
|
19
23
|
type Groups<C> = Extract<C, ContributionConstraint>;
|
|
20
24
|
type Same<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
|
|
21
25
|
type WrongMember<T, G> = G extends ContributionConstraint ? TokenKey<T> extends TokenKey<G['token']> ? Same<T, G['token']> extends true ? never : TokenKey<T> : never : never;
|
|
@@ -33,25 +37,29 @@ type AllNeeds<C, A extends Registrations> = ProviderCollectionTokens<A[keyof A]>
|
|
|
33
37
|
token: infer T;
|
|
34
38
|
} ? T : never);
|
|
35
39
|
type GroupErrors<C, A extends Registrations> = WrongGroup<Groups<C>['token'] | AllNeeds<C, A>, C>;
|
|
36
|
-
export type CheckedContributions<C, A extends Registrations> = [Groups<C>] extends [never] ? unknown : [GroupErrors<C, A>] extends [never] ? [WrongProvider<C, A>] extends [never] ? unknown : Unsatisfied
|
|
40
|
+
export type CheckedContributions<C, A extends Registrations> = [Groups<C>] extends [never] ? unknown : [GroupErrors<C, A>] extends [never] ? [WrongProvider<C, A>] extends [never] ? unknown : Unsatisfied<`contribution service is incompatible with its consumer dependency contract${SeeErrors<'unsatisfied-consumer'>}`, {
|
|
37
41
|
readonly failures: ContributionFailures<WrongProvider<C, A>, A>;
|
|
38
42
|
}> : Unsatisfied<'collection token has an incompatible or opaque contract', {}>;
|
|
39
|
-
export type CompleteContributions<C, A extends Registrations> = [MissingProvider<C, A>] extends [never] ? unknown : Unsatisfied
|
|
43
|
+
export type CompleteContributions<C, A extends Registrations> = [MissingProvider<C, A>] extends [never] ? unknown : Unsatisfied<`required service registrations are missing${SeeErrors<'missing-service'>}`, {
|
|
40
44
|
readonly contributions: MissingProvider<C, A>;
|
|
41
45
|
}>;
|
|
42
46
|
/**
|
|
43
|
-
* Retain a contribution's provider
|
|
44
|
-
*
|
|
45
|
-
*
|
|
47
|
+
* Retain a contribution's projected provider and its checked needs when its builder seals.
|
|
48
|
+
* Lifetime reach is retained separately as compact obligations. A contribution retained
|
|
49
|
+
* from an inner installation is already projected and has no needs left to re-scope.
|
|
50
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
51
|
+
*/
|
|
52
|
+
export type ModuleContributionConstraints<C, R extends Registrations, P extends keyof R> = C extends ContributionConstraint ? Contribution<C['token'], PublicProvider<C['registration']>> | RegistrationConstraints<C['registration'], R, P> : never;
|
|
53
|
+
/**
|
|
54
|
+
* Project a module's typed-token collections as readonly service arrays.
|
|
55
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
46
56
|
*/
|
|
47
|
-
export type ModuleContributionConstraints<C, R extends Registrations, P extends keyof R> = C extends ContributionConstraint ? C['context'] extends LexicalContext ? Contribution<C['token'], C['registration'], Enclosed<C['context'], ModuleScope<R, P>>> : Contribution<C['token'], PublicProvider<C['registration']>, ModuleScope<R, P> & {
|
|
48
|
-
readonly registration: C['registration'];
|
|
49
|
-
}> | RegistrationConstraints<C['registration'], R, P> : never;
|
|
50
|
-
export type RenamedContribution<C extends ContributionConstraint, Old extends string, New extends string> = C['context'] extends LexicalContext ? Contribution<C['token'], C['registration'], RenamedContext<C['context'], Old, New>> : C;
|
|
51
|
-
/** Project a module's typed-token collections as readonly service arrays. */
|
|
52
57
|
export type ModuleContributions<M> = M extends Module<infer _P, infer _R, infer C, infer _D> ? Readonly<{
|
|
53
58
|
[T in Groups<C>['token'] as TokenKey<T>]: ReadonlyArray<TokenService<T>>;
|
|
54
59
|
}> : never;
|
|
55
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* The checked generic `contribute` callable exposed by a builder.
|
|
62
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
63
|
+
*/
|
|
56
64
|
export type BuilderContribute<E extends Entry, C extends NeedConstraint> = <T extends TokenBase, V extends Registration>(token: T & TokenTupleAdmission<readonly [T]>, registration: V & Registration & BindingOutput<NoInfer<T>, NoInfer<V>> & CheckedConstraints<C | Contribution<NoInfer<T>, NoInfer<V>>, RegistrationsFromEntries<E>>, ...invalid: [T] extends [never] ? [never] : [V] extends [never] ? [never] : []) => import('./di-bag').Builder<E, C | Contribution<T, V>>;
|
|
57
65
|
export {};
|