di-bag 0.4.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.
Files changed (74) hide show
  1. package/AGENTS.md +41 -42
  2. package/README.md +83 -65
  3. package/dist/acquisition-context.d.ts +35 -86
  4. package/dist/acquisition-context.js +19 -37
  5. package/dist/acquisition-family.d.ts +2 -2
  6. package/dist/acquisition-family.js +3 -3
  7. package/dist/acquisition-mode.d.ts +16 -21
  8. package/dist/acquisition-mode.js +14 -18
  9. package/dist/acquisition.d.ts +13 -10
  10. package/dist/acquisition.js +68 -43
  11. package/dist/alias-types.d.ts +10 -8
  12. package/dist/aliases.d.ts +2 -2
  13. package/dist/aliases.js +26 -7
  14. package/dist/builder-method-types.d.ts +67 -0
  15. package/dist/builder-method-types.js +2 -0
  16. package/dist/composition-report.d.ts +3 -3
  17. package/dist/composition.d.ts +43 -34
  18. package/dist/composition.js +39 -28
  19. package/dist/contribution-types.d.ts +22 -16
  20. package/dist/contributions.d.ts +2 -2
  21. package/dist/contributions.js +8 -4
  22. package/dist/dependency-references.d.ts +6 -16
  23. package/dist/dependency-references.js +30 -12
  24. package/dist/di-bag.d.ts +285 -339
  25. package/dist/di-bag.js +320 -168
  26. package/dist/errors.d.ts +62 -56
  27. package/dist/errors.js +77 -68
  28. package/dist/index.d.ts +19 -16
  29. package/dist/index.js +4 -4
  30. package/dist/inspection.d.ts +21 -21
  31. package/dist/install-types.d.ts +21 -0
  32. package/dist/install-types.js +2 -0
  33. package/dist/lifetime-types.d.ts +115 -60
  34. package/dist/lifetime.d.ts +19 -34
  35. package/dist/lifetime.js +16 -23
  36. package/dist/module-types.d.ts +62 -25
  37. package/dist/module.d.ts +53 -15
  38. package/dist/module.js +149 -55
  39. package/dist/observers.d.ts +27 -27
  40. package/dist/observers.js +19 -10
  41. package/dist/options-bag.d.ts +7 -0
  42. package/dist/options-bag.js +40 -0
  43. package/dist/plugins.d.ts +17 -35
  44. package/dist/plugins.js +23 -43
  45. package/dist/provider-execution.d.ts +5 -5
  46. package/dist/provider-execution.js +24 -24
  47. package/dist/provider-facades.d.ts +126 -0
  48. package/dist/provider-facades.js +42 -0
  49. package/dist/provider-operations.d.ts +8 -8
  50. package/dist/provider-operations.js +9 -9
  51. package/dist/provider.d.ts +65 -156
  52. package/dist/provider.js +66 -91
  53. package/dist/registration.d.ts +6 -36
  54. package/dist/registration.js +9 -28
  55. package/dist/removed-api.d.ts +9 -0
  56. package/dist/removed-api.js +69 -0
  57. package/dist/replacement-types.d.ts +11 -10
  58. package/dist/runtime.d.ts +24 -12
  59. package/dist/runtime.js +149 -45
  60. package/dist/scope-selection.d.ts +3 -2
  61. package/dist/scope-selection.js +109 -47
  62. package/dist/scope-types.d.ts +38 -27
  63. package/dist/startup.d.ts +19 -22
  64. package/dist/startup.js +61 -51
  65. package/dist/token-types.d.ts +55 -27
  66. package/dist/tokens.d.ts +47 -18
  67. package/dist/tokens.js +67 -20
  68. package/dist/types.d.ts +64 -43
  69. package/docs/agent/api-card.md +193 -193
  70. package/docs/agent/errors.md +445 -591
  71. package/docs/agent/recipes.md +230 -122
  72. package/package.json +5 -6
  73. package/dist/node.d.ts +0 -4
  74. package/dist/node.js +0 -22
package/AGENTS.md CHANGED
@@ -1,66 +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 bag
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/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).
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; `di-bag/node` is the same API in explicit form.
15
- For browsers and workers register with `DiBag.fromSyncFactory` / `fromAsyncFactory`
16
- ([portable recipe](docs/agent/recipes.md#portable-graph)); a plain factory there fails
17
- `build()` with [`DI_BAG_CLASSIFIER_REQUIRED`](docs/agent/errors.md#di-bag-classifier-required), which names it.
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
- `deps.clock` directly. The object is a Proxy that resolves each property when
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`: 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.
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.fromFactory(create, { acquisitionMode: 'raw' })`.
33
- 6. **Ownership.** `DiBag.withDisposal(factory, dispose)` makes the bag own the
34
- returned value; `close()` runs disposers, dependents first. Close every scope and
35
- fork you create; a parent closes its live scopes, never forks. Inside a factory,
36
- [`factoryCtx.pushDisposer`](docs/agent/recipes.md#partial-acquisition) owns what it acquires on the way; if that is also the returned value, act only when `disposerCtx.reason !== 'service-disposed'`.
37
- 7. **Replace dependencies in tests with `fork(keys, overrides)`**; each
38
- override must satisfy the original contract.
39
- 8. **Modules.** Register a feature's factories, then `buildModule(['exported'])`.
40
- What its factories need and the module does not register becomes a
41
- requirement: the host that calls `installModule(module)` must register it.
42
- Pass `buildModule(keys, { label: 'billing' })` so messages name private
43
- 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.
44
42
  9. **Read a rejection at its name.** A graph error is an assignability error
45
43
  whose type is `Unsatisfied<"message", details>`, reported where the builder
46
- expression starts. `builder.verifyGraph() satisfies void;` reports the same
47
- message on its own line; `"noErrorTruncation": true` prints the details.
48
- Runtime errors carry `code` and `details`: branch on `code`, never on message
49
- text. The section for a code is `docs/agent/errors.md#<code>`, lower-cased
50
- 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 `-`.
51
48
 
52
49
  ## Module layout
53
50
 
54
51
  ```text
55
52
  src/features/invoicing/
56
53
  contract.ts # exported service types and the requirements the host must supply
57
- module.ts # buildModule([...]) over the private factories
54
+ module.ts # buildModule({ exportedServiceKeys: [...] }) over the private factories
58
55
  store.ts # private services; free to use names other modules also use
59
56
  check.ts # type-checks this module alone; never imported, not built
60
57
  tsconfig.json # extends the root tsconfig and includes only this directory
61
58
  invoicing.test.ts
62
- src/app.ts # installs every module, one installModule call per line
63
- src/app.check.ts # verifyGraph() on the application builder: the merge check
59
+ src/app.ts # installs modules in one withInstalledModules([...]) list
60
+ src/app.check.ts # verifyGraphAtCompileTime() on the application builder: the merge check
64
61
  ```
65
62
 
66
63
  Inside a file, keep the same order: contract types, private factories, the
@@ -81,15 +78,15 @@ import { DiBag } from 'di-bag';
81
78
  import type { Greeter, GreetingConfig } from './contract.js';
82
79
 
83
80
  export const greetingModule = DiBag.createBuilder()
84
- .register({
81
+ .withServices({
85
82
  greeter: ({ config }: { config: GreetingConfig }): Greeter => ({
86
83
  greet: name => `${config.greeting}, ${name}!`,
87
84
  }),
88
85
  })
89
- .buildModule(['greeter']);
86
+ .buildModule({ exportedServiceKeys: ['greeter'] });
90
87
  ```
91
88
 
92
- `check.ts` is one statement: install the module, register a typed fixture for
89
+ `check.ts` is one statement: install the module, add a typed fixture for
93
90
  each requirement, and verify.
94
91
 
95
92
  ```ts
@@ -99,9 +96,11 @@ import type { GreetingConfig } from './contract.js';
99
96
  import { greetingModule } from './module.js';
100
97
 
101
98
  DiBag.createBuilder()
102
- .installModule(greetingModule)
103
- .register({ config: (): GreetingConfig => ({ greeting: 'Hello' }) })
104
- .verifyGraph() satisfies void;
99
+ .withInstalledModules([
100
+ greetingModule,
101
+ ])
102
+ .withServices({ config: (): GreetingConfig => ({ greeting: 'Hello' }) })
103
+ .verifyGraphAtCompileTime() satisfies void;
105
104
  ```
106
105
 
107
106
  ## Check one module
@@ -122,7 +121,7 @@ npx tsc --noEmit -p src/features/<name>/tsconfig.json
122
121
  ```
123
122
 
124
123
  A missing requirement fails with its key:
125
- `required service registrations are missing: config`.
124
+ `required services are missing: config`.
126
125
 
127
126
  ## Fast check
128
127
 
@@ -143,7 +142,7 @@ test suite before merging: [review a merge](docs/agent/recipes.md#review-merge).
143
142
  ## Recipes
144
143
 
145
144
  - [Add a request-scoped service with cleanup](docs/agent/recipes.md#add-scoped-service)
146
- - [Write a fixture test with `fork`](docs/agent/recipes.md#fixture-test)
145
+ - [Write a fixture test with an independent container](docs/agent/recipes.md#fixture-test)
147
146
  - [Split a feature into a module with private services](docs/agent/recipes.md#split-module)
148
147
  - [Debug a missing-dependency rejection](docs/agent/recipes.md#debug-missing-dependency)
149
148
  - [Add and consume an async client](docs/agent/recipes.md#async-client)
package/README.md CHANGED
@@ -9,7 +9,7 @@ Built for coding agents that ship 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 cleanup. Build each feature against an explicit contract, test it
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 service startup](docs/guides/examples-extensibility.md).**
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—no decorators or base classes.
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, so review the changelog and
35
- migration guides when updating.
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 Deno, see [runtime support](#runtime-support).
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 **bag** holds those factories and gives each
45
- one access to the services it needs. Services are created when needed, and
46
- resources are cleaned up when you provide a disposer and close their bag.
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
- .register({
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
- .build();
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
- `.register()` adds factories to an immutable builder, `.build()` checks the
70
- declared graph and creates the bag, and
71
- `resolve('greeter')` creates the greeter and the config it needs. Resolving
72
- `greeter` again returns the same instance. Registration order does not matter.
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 `.build()` a compile-time error. Changing
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 `fork()` to create a separate bag with a replacement dependency.
81
- Continuing the [quickstart](#quickstart):
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
- const testApp = app.fork(['config'], {
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 fork has
97
- independent acquisition and cleanup ownership; close it separately. Factories
98
- can still return shared objects captured outside the fork.
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
- .register({
120
+ .withServices({
110
121
  greeting: async () => 'Hello',
111
122
  message: async ({ greeting }: { greeting: Promise<string> }) =>
112
123
  `${await greeting}, Ada!`,
113
124
  })
114
- .build();
125
+ .buildContainer();
115
126
 
116
127
  console.log(await app.resolve('message')); // Hello, Ada!
128
+ await app.close();
117
129
  ```
118
130
 
119
- By default, repeated resolutions share the same in-flight promise. Synchronous
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
- Wrap a factory with `withDisposal` to tell the bag how to release its result:
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
- .register({
132
- cache: DiBag.withDisposal(
133
- () => new Map<string, string>(),
134
- (cache) => cache.clear(),
135
- ),
143
+ .withServices({
144
+ cache: DiBag.providerWithDisposal({
145
+ provider: () => new Map<string, string>(),
146
+ disposeService: cache => cache.clear(),
147
+ }),
136
148
  })
137
- .build();
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. Cleanup can
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 cleanup. Ordinary factories return borrowed values;
149
- having a `close()` method alone does not transfer ownership to the bag.
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
- ## Scopes and forks
163
+ ## Child and independent containers {#child-and-independent-containers}
152
164
 
153
165
  | Operation | What it creates | Who closes it? |
154
166
  | --- | --- | --- |
155
- | `bag.createScope()` | A tracked child with fresh scoped services; root services are shared | Close it when its work ends. The parent also closes live children. |
156
- | `bag.fork()` | An independent bag with the same registrations and fresh instances | The caller closes it separately. |
157
- | `bag.fork(keys, overrides)` | An independent bag with selected dependencies replaced | The caller closes it separately. |
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, startup, metadata, observers, and plugin validation.
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(keys)` seals a feature and
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 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.
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
- `build()` or `verifyGraph()`. The `di-bag-graph` tool exports the declared
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, scopes, and coordinated cleanup. |
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 startup with rollback. |
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 bag lifecycles. |
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 cleanup behavior, and the limits of these comparisons.
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 two entry points:
221
-
222
- | Import | Purpose |
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
- On hosts without `process.getBuiltinModule` (browsers, workers), `build()`
228
- rejects automatic acquisition stages and names them. Register with
229
- `DiBag.fromSyncFactory` / `DiBag.fromAsyncFactory` there, or configure a trusted
230
- classifier. See [portable mode](docs/guides/tutorial.md#portable-mode).
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,10 +253,10 @@ classifier. See [portable mode](docs/guides/tutorial.md#portable-mode).
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
- - **Cleanup waits for your work by default.** Cancellation is cooperative; a
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({ timeoutMs, signal })` to stop waiting: the rejection names the
241
- disposers still running and cleanup continues in the background.
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).
@@ -259,13 +277,13 @@ classifier. See [portable mode](docs/guides/tutorial.md#portable-mode).
259
277
  | --- | --- |
260
278
  | [Complete tutorial](docs/guides/tutorial.md) | Learn every public API through examples, from first composition to advanced ownership. |
261
279
  | [API reference](docs/guides/api-reference.md) | Exact generated signatures, overloads, type parameters, and API inventories. |
262
- | [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request scopes, startup, and shutdown. |
280
+ | [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request containers, service readiness, and shutdown. |
263
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`. |
264
282
  | [Radical modularity](docs/guides/examples-modularity.md) | The recommended module layout, separately owned features, isolated tests, and contributed tools. |
265
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. |
266
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. |
267
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. |
268
- | [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, scopes, and provider metadata. |
286
+ | [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, child containers, and provider metadata. |
269
287
  | [Integration guide](docs/guides/enterprise-integration.md) | Tested recipes for request ownership, substitutions, and dynamic features. |
270
288
  | [Comparison with alternatives](docs/guides/comparison.md) | When DI Bag or another approach may be a better fit, with primary sources. |
271
289
  | [Development and verification](docs/guides/development.md) | Full checks, portable runtime testing, compiler scale, and performance evidence. |
@@ -1,6 +1,6 @@
1
1
  import type { Provider } from './provider';
2
2
  import type { Factory } from './registration';
3
- import type { Acquired, AcquisitionMode, AsyncOutput, AutoOutput, NativeOutput, ModeOptions, SyncOutput } from './acquisition-mode';
3
+ import type { Acquired, FactoryReturnKind, NativeOutput, AutoOutput, SyncOutput } from './acquisition-mode';
4
4
  import type { TokenDependencyContract } from './token-types';
5
5
  /**
6
6
  * Why a pushed disposer is running: the factory never returned, or it did and the
@@ -19,99 +19,48 @@ export interface DisposerContext {
19
19
  }
20
20
  /**
21
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#start-selected-services-and-cancel-cooperatively
22
+ * @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#make-selected-services-ready
23
23
  */
24
- export interface AcquisitionContext {
25
- /** Aborted when the acquisition's owning scope begins closing. */
26
- readonly signal: AbortSignal;
24
+ export interface FactoryContext {
25
+ /** Aborted when the acquisition's owning container begins closing. */
26
+ readonly abortSignal: AbortSignal;
27
27
  /**
28
- * Own a resource this factory has already acquired. Pushed disposers run exactly once, last
29
- * pushed first: at once if the factory fails, otherwise at `close()` after every disposer of the
30
- * service, with `disposerCtx.reason` saying which. `withDisposal` owns the returned value; push
31
- * what is acquired on the way, and test `reason` before releasing the returned value itself.
32
- * @param disposer - Releases the resource acquired immediately before this call.
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.
33
31
  */
34
- pushDisposer(this: void, disposer: (this: void, disposerCtx: DisposerContext) => void | Promise<void>): void;
32
+ pushDisposer(this: void, disposer: (this: void, disposerContext: DisposerContext) => void | Promise<void>): void;
35
33
  }
36
- type ContextFactory = (this: void, deps: never, factoryCtx: AcquisitionContext) => unknown;
37
34
  /**
38
35
  * The named-dependency factory contract retained by an acquisition-context callback.
39
- * @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#start-selected-services-and-cancel-cooperatively
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
40
38
  */
41
- export type ContextualFactory<F extends ContextFactory> = (this: void, deps: Parameters<F> extends [] ? {} : Parameters<F>[0]) => ReturnType<F>;
42
- type FactoryOptions<M extends AcquisitionMode> = 'auto' extends M ? [options?: {
43
- readonly context?: never;
44
- readonly acquisitionMode?: M;
45
- }] : [options: {
46
- readonly context?: never;
47
- readonly acquisitionMode: M;
48
- }];
49
- /**
50
- * Describe a named-dependency factory receiving its acquisition owner's cancellation signal.
51
- * Context allocation is opt-in through `context: 'acquisition'`, independent of callback arity.
52
- * @param callback - A receiver-free factory taking dependencies and acquisition context.
53
- * @param options - Context selection and result policy; acquisitionMode defaults to auto.
54
- * @returns A lazy provider preserving exact output and named dependencies; adds no ownership.
55
- * @typeParam F - The complete callback signature, retaining dependency and output inference.
56
- * @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
57
- */
58
- export declare function fromFactory<F extends (this: void, deps: never, factoryCtx: AcquisitionContext) => ('nativePromise' extends M ? Promise<unknown> : unknown), M extends AcquisitionMode = 'auto'>(callback: F & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, options: {
59
- readonly context: 'acquisition';
60
- } & ModeOptions<M>): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, M>>;
61
- /**
62
- * Describe a named-dependency factory with explicit or automatic result acquisition.
63
- * Raw mode preserves the exact acquired value; nativePromise observes Promise fulfillment.
64
- * @param callback - A receiver-free factory taking its named dependency object.
65
- * @param options - Optional result acquisitionMode, defaulting to auto.
66
- * @returns A lazy provider retaining exact output and dependency types without adding ownership.
67
- * @typeParam F - The exact factory signature and exposed result.
68
- * @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
69
- */
70
- export declare function fromFactory<F extends Factory, M extends AcquisitionMode = 'auto'>(callback: F & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>> & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...options: FactoryOptions<M>): Provider<F, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, M>>;
71
- type PortableFactoryOptions = {
72
- readonly context?: never;
73
- readonly acquisitionMode?: never;
74
- };
75
- type ContextualPortableFactoryOptions = {
76
- readonly context: 'acquisition';
77
- readonly acquisitionMode?: never;
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>;
78
47
  };
79
48
  /**
80
- * Describe a synchronous named-dependency factory that runs on every host: a `raw` stage whose
81
- * exact return value is the service, so `then` is never read and no Promise classifier is needed.
82
- * @param callback - A receiver-free factory taking its named dependency object and the acquisition context.
83
- * @param options - `context: 'acquisition'`; the acquisition mode is fixed and `acquisitionMode` is rejected.
84
- * @returns A lazy provider preserving exact output and named dependencies; adds no ownership.
85
- * @typeParam F - The complete callback signature, retaining dependency and output inference.
86
- */
87
- export declare function fromSyncFactory<F extends ContextFactory>(callback: F & SyncOutput<ReturnType<NoInfer<F>>>, options: ContextualPortableFactoryOptions): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, ReturnType<F>>;
88
- /**
89
- * Describe a synchronous named-dependency factory that runs on every host: a `raw` stage whose
90
- * exact return value is the service. A Promise or thenable output is rejected at compile time;
91
- * use `fromAsyncFactory`, or `fromFactory` with `acquisitionMode: 'raw'` when the Promise object is the service.
92
- * @param callback - A receiver-free factory taking its named dependency object.
93
- * @param options - Optional; `acquisitionMode` is rejected because the helper fixes it.
94
- * @returns A lazy provider retaining exact output and dependency types without adding ownership.
95
- * @typeParam F - The exact factory signature and exposed result.
96
- */
97
- export declare function fromSyncFactory<F extends Factory>(callback: F & SyncOutput<ReturnType<NoInfer<F>>>, options?: PortableFactoryOptions): Provider<F, Readonly<{}>, readonly [], TokenDependencyContract, ReturnType<F>>;
98
- /**
99
- * Describe an asynchronous named-dependency factory that runs on every host: a `nativePromise`
100
- * stage whose service is the returned Promise and whose owners receive the fulfilled value.
101
- * @param callback - A receiver-free async factory taking its named dependency object and the acquisition context.
102
- * @param options - `context: 'acquisition'`; the acquisition mode is fixed and `acquisitionMode` is rejected.
103
- * @returns A lazy provider exposing the factory's own Promise; adds no ownership.
104
- * @typeParam F - The complete callback signature, retaining dependency and output inference.
105
- */
106
- export declare function fromAsyncFactory<F extends (this: void, deps: never, factoryCtx: AcquisitionContext) => Promise<unknown>>(callback: F, options: ContextualPortableFactoryOptions): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, Awaited<ReturnType<F>>>;
107
- /**
108
- * Describe an asynchronous named-dependency factory that runs on every host: a `nativePromise`
109
- * stage whose service is the returned Promise and whose owners receive the fulfilled value.
110
- * A non-Promise output is rejected at compile time; a thenable that is not a native Promise fails the acquisition with a `TypeError`.
111
- * @param callback - A receiver-free factory returning a native Promise.
112
- * @param options - Optional; `acquisitionMode` is rejected because the helper fixes it.
113
- * @returns A lazy provider exposing the factory's own Promise; `withDisposal` receives its fulfilled value.
114
- * @typeParam F - The exact factory signature and exposed Promise.
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.
115
52
  */
116
- export declare function fromAsyncFactory<F extends Factory>(callback: F & AsyncOutput<ReturnType<NoInfer<F>>>, options?: PortableFactoryOptions): Provider<F, Readonly<{}>, readonly [], TokenDependencyContract, Awaited<ReturnType<F>>>;
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;
117
66
  export {};