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.
Files changed (43) hide show
  1. package/AGENTS.md +149 -0
  2. package/README.md +59 -46
  3. package/dist/acquisition-context.d.ts +8 -2
  4. package/dist/acquisition-mode.d.ts +9 -4
  5. package/dist/acquisition-mode.js +25 -7
  6. package/dist/acquisition.d.ts +2 -0
  7. package/dist/acquisition.js +9 -0
  8. package/dist/alias-types.d.ts +12 -3
  9. package/dist/composition-report.d.ts +3 -2
  10. package/dist/composition.d.ts +8 -2
  11. package/dist/contribution-types.d.ts +26 -18
  12. package/dist/dependency-references.d.ts +16 -4
  13. package/dist/di-bag.d.ts +320 -41
  14. package/dist/di-bag.js +86 -14
  15. package/dist/errors.d.ts +115 -8
  16. package/dist/errors.js +113 -12
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +2 -1
  19. package/dist/inspection.d.ts +21 -5
  20. package/dist/lifetime-types.d.ts +281 -180
  21. package/dist/lifetime.d.ts +4 -1
  22. package/dist/module-types.d.ts +87 -30
  23. package/dist/module.d.ts +18 -4
  24. package/dist/module.js +31 -7
  25. package/dist/observers.d.ts +25 -6
  26. package/dist/plugins.d.ts +17 -4
  27. package/dist/provider.d.ts +41 -10
  28. package/dist/provider.js +1 -0
  29. package/dist/registration.d.ts +8 -2
  30. package/dist/registration.js +4 -1
  31. package/dist/runtime.d.ts +12 -3
  32. package/dist/runtime.js +25 -8
  33. package/dist/scope-types.d.ts +20 -5
  34. package/dist/startup.d.ts +19 -1
  35. package/dist/startup.js +72 -11
  36. package/dist/token-types.d.ts +26 -8
  37. package/dist/tokens.d.ts +10 -2
  38. package/dist/tokens.js +2 -0
  39. package/dist/types.d.ts +68 -22
  40. package/docs/agent/api-card.md +337 -0
  41. package/docs/agent/errors.md +1042 -0
  42. package/docs/agent/recipes.md +290 -0
  43. 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 agentic development, LLM harnesses, and agent graphs.
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) · [Agent harnesses](docs/guides/agent-harnesses-and-graphs.md) · [Comparison](#how-it-compares) · [Tutorial](docs/guides/tutorial.md) · [API reference](docs/guides/api-reference.md)
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 services, tools, or graph nodes against explicit
12
- contracts, then replace their dependencies for tests.
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
- - **[Reusable modules with private bindings](docs/guides/examples-modularity.md).**
15
- Compose independently developed features without exposing their internals.
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
- Use `di-bag/node` in Node or Bun. Here, `greeter` needs `config`. Its parameter
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/node';
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/node';
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/node';
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
- ## LLM harnesses and agent graphs
165
-
166
- Compose an **LLM harness** from model clients, tools, context sources, and
167
- ordinary functions that serve as **agent graph** nodes.
168
-
169
- - **Explicit feature boundaries:** give each node or tool a small contract
170
- and private implementation. Keep feature contracts and focused tests together;
171
- select tools and context for the LLM in the harness.
172
- - **Contract checks and fixture tests:** check declared wiring before a model call,
173
- then fork the composition with typed model and tool fixtures for deterministic
174
- behavioral tests. Keep live-model evals for quality and task success.
175
- - **Inspectable capability descriptions:** describe public nodes and tools next
176
- to their factories. Inspect that metadata without creating services, and use
177
- it in application-defined catalogs, diagnostics, or dispatch policies.
178
-
179
- DI Bag's dependency graph describes how services are supplied. The agent graph
180
- describes execution: which node runs next and what state it receives. Your
181
- harness or graph framework owns routing, retries, persistence, and execution;
182
- DI Bag supplies checked composition and resource ownership.
183
-
184
- The [agent harness and graph guide](docs/guides/agent-harnesses-and-graphs.md)
185
- combines private feature modules, an LLM-backed node, metadata inspection, and
186
- fork-based fixture tests in one runnable example. `inspectGraph()` lists every
187
- binding and the edges observed at runtime; declared edges come from the static
188
- graph tool. Observers track acquisition, not ordinary node calls. Use your graph
189
- framework for workflow checkpoints.
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/node` | Ready-to-use factory composition in Node and Bun, with native Promise detection. |
217
- | `di-bag` | Portable core for other hosts, including Deno and bundled browsers. Use explicit acquisition modes or configure a trusted native Promise predicate. |
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
- The portable entry rejects automatic acquisition stages unless you configure a
220
- trusted classifier. See [portable mode](docs/guides/tutorial.md#portable-mode)
221
- for both setup options.
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 factory or
230
- disposer that never settles can keep `close()` pending.
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
- | [Agent harnesses and graphs](docs/guides/agent-harnesses-and-graphs.md) | Compose model and tool dependencies, inspect metadata, and test nodes with typed fixtures. |
250
- | [Static dependency graph](docs/guides/agent-harnesses-and-graphs.md#export-the-declared-dependency-graph) | Export every builder chain, declared edge, and cycle to JSON with `di-bag-graph`. |
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
- /** Cooperative cancellation information supplied to a context-aware acquisition. */
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
- /** The named-dependency factory contract retained by an acquisition-context callback. */
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
- /** Portable facade configuration for `auto` acquisition stages. */
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<'factory output is a structural thenable; return a native Promise or select acquisitionMode raw or nativePromise', {}> : unknown : unknown;
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
- export declare function requireClassificationCapability(modes: Iterable<AcquisitionMode>, context: RuntimeContext): void;
40
+ /** Resolve the classifier when a graph first needs one; a configured classifier always wins. */
41
+ export declare function requireClassifier(context: RuntimeContext): RuntimeContext;
@@ -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.requireClassificationCapability = requireClassificationCapability;
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
- function requireClassificationCapability(modes, context) {
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
- for (const mode of modes)
32
- if (mode === 'auto') {
33
- throw (0, errors_1.libraryError)('DI_BAG_CLASSIFIER_REQUIRED', 'Automatic acquisition classification requires DiBag.withConfiguration({ runtime }), di-bag/node, or explicit acquisitionMode options', { option: 'runtime.isNativePromise' });
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
  }
@@ -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;
@@ -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();
@@ -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
- /** Resolve the service type exposed by a possible alias target. */
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
- /** A provider contract that forwards a destination to a canonical target acquisition. */
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
- /** The single registration-map entry introduced by an alias operation. */
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>>> | ReportOf<CheckDependencyCompleteness<RegistrationsFromEntries<E>>> | ReportOf<CheckedConstraints<C, RegistrationsFromEntries<E>>> | ReportOf<CompleteConstraints<C, RegistrationsFromEntries<E>>> | ReportOf<CheckedLifetimes<RegistrationsFromEntries<E>, C>>;
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 {};
@@ -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
- /** Compile-time admission that checks supplied token values against a callable's parameter tuple. */
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
- /** A receiver-free positional callback matching the values supplied by a dependency tuple. */
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
- /** Each union member retains one independently checked provider and its group. */
11
- export type Contribution<T extends TokenBase = TokenBase, V extends Registration = Registration, L = undefined> = {
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
- /** The erased contribution contract retained by checked builders and modules. */
18
- export type ContributionConstraint = Contribution<TokenBase, Registration, unknown>;
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<'contribution service is incompatible with its consumer dependency contract', {
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<'required service registrations are missing', {
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 checks and lexical private-service context when
44
- * its builder seals. A contribution retained from an inner installation is already
45
- * projected; sealing only encloses its scope in this module's scope.
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
- /** The checked generic `contribute` callable exposed by a builder. */
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 {};