di-bag 0.1.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/dist/acquisition-context.d.ts +42 -0
  4. package/dist/acquisition-context.js +19 -0
  5. package/dist/acquisition-family.d.ts +32 -0
  6. package/dist/acquisition-family.js +135 -0
  7. package/dist/acquisition-mode.d.ts +34 -0
  8. package/dist/acquisition-mode.js +35 -0
  9. package/dist/acquisition.d.ts +44 -0
  10. package/dist/acquisition.js +395 -0
  11. package/dist/alias-types.d.ts +22 -0
  12. package/dist/alias-types.js +2 -0
  13. package/dist/aliases.d.ts +4 -0
  14. package/dist/aliases.js +24 -0
  15. package/dist/composition.d.ts +41 -0
  16. package/dist/composition.js +45 -0
  17. package/dist/contribution-types.d.ts +57 -0
  18. package/dist/contribution-types.js +2 -0
  19. package/dist/contributions.d.ts +3 -0
  20. package/dist/contributions.js +11 -0
  21. package/dist/dependency-references.d.ts +52 -0
  22. package/dist/dependency-references.js +53 -0
  23. package/dist/di-bag.d.ts +247 -0
  24. package/dist/di-bag.js +211 -0
  25. package/dist/errors.d.ts +66 -0
  26. package/dist/errors.js +89 -0
  27. package/dist/index.d.ts +25 -0
  28. package/dist/index.js +10 -0
  29. package/dist/inspection.d.ts +36 -0
  30. package/dist/inspection.js +2 -0
  31. package/dist/lifetime-types.d.ts +214 -0
  32. package/dist/lifetime-types.js +2 -0
  33. package/dist/lifetime.d.ts +42 -0
  34. package/dist/lifetime.js +31 -0
  35. package/dist/module-types.d.ts +182 -0
  36. package/dist/module-types.js +2 -0
  37. package/dist/module.d.ts +45 -0
  38. package/dist/module.js +118 -0
  39. package/dist/node.d.ts +4 -0
  40. package/dist/node.js +22 -0
  41. package/dist/observers.d.ts +71 -0
  42. package/dist/observers.js +58 -0
  43. package/dist/persistent-map.d.ts +29 -0
  44. package/dist/persistent-map.js +146 -0
  45. package/dist/persistent-sequence.d.ts +9 -0
  46. package/dist/persistent-sequence.js +20 -0
  47. package/dist/plugins.d.ts +32 -0
  48. package/dist/plugins.js +82 -0
  49. package/dist/provider-execution.d.ts +59 -0
  50. package/dist/provider-execution.js +271 -0
  51. package/dist/provider-operations.d.ts +59 -0
  52. package/dist/provider-operations.js +38 -0
  53. package/dist/provider.d.ts +199 -0
  54. package/dist/provider.js +158 -0
  55. package/dist/registration.d.ts +32 -0
  56. package/dist/registration.js +45 -0
  57. package/dist/replacement-types.d.ts +30 -0
  58. package/dist/replacement-types.js +2 -0
  59. package/dist/runtime.d.ts +90 -0
  60. package/dist/runtime.js +428 -0
  61. package/dist/scope-selection.d.ts +6 -0
  62. package/dist/scope-selection.js +62 -0
  63. package/dist/scope-types.d.ts +58 -0
  64. package/dist/scope-types.js +2 -0
  65. package/dist/startup.d.ts +14 -0
  66. package/dist/startup.js +141 -0
  67. package/dist/token-types.d.ts +67 -0
  68. package/dist/token-types.js +2 -0
  69. package/dist/tokens.d.ts +47 -0
  70. package/dist/tokens.js +69 -0
  71. package/dist/types.d.ts +174 -0
  72. package/dist/types.js +2 -0
  73. package/package.json +64 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Danylo Fedorov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # DI Bag
2
+
3
+ Dependency injection for agentic development.
4
+
5
+ [Documentation](https://dany-fedorov.github.io/di-bag/) · [Quickstart](#quickstart) · [Comparison](#how-it-compares) · [Tutorial](docs/guides/tutorial.md) · [API reference](docs/guides/api-reference.md)
6
+
7
+ ## Why DI Bag?
8
+
9
+ DI Bag is designed to make modular applications easier for coding agents to
10
+ work on. Modularity is a practical context-engineering technique: clear
11
+ feature boundaries can reduce the code and dependencies an agent needs to
12
+ consider for a task. DI Bag is a TypeScript dependency injection library built
13
+ around that idea—helping you define those boundaries, replace dependencies in
14
+ tests, and check how the pieces fit together.
15
+
16
+ - **[Radical modularity for agentic development](docs/guides/examples-modularity.md).**
17
+ Compose small features with private internals and explicit contracts. Give
18
+ humans and coding agents focused units to implement, replace, and test
19
+ independently, then check their composition together.
20
+ - **[TypeScript-first composition](docs/guides/examples-type-checking.md).**
21
+ Catch missing dependencies, incompatible service contracts, and invalid
22
+ replacements at compile time—not just incorrect arguments at the call site.
23
+ Coding agents can shorten their evaluation loop by checking wiring without
24
+ starting the app or running integration tests.
25
+ - **[A programmable DI layer for custom tooling](docs/guides/examples-extensibility.md).**
26
+ Give agents the building blocks to create the tooling your workflow needs:
27
+ custom inspectors, diagnostics, metadata-driven actions, and lifecycle tools.
28
+ Combine application-defined metadata, composable provider wrappers, and
29
+ configurable observers without changing your services.
30
+ - **[Inject anything with a simple factory function](docs/guides/examples-plain-services.md).**
31
+ DI Bag is TypeScript-first, but injecting services is as simple as writing an
32
+ ordinary JavaScript function: receive dependencies and return a value.
33
+ Functions, class instances, configuration, clients, or promises—no decorators,
34
+ reflection metadata, or special base classes required.
35
+
36
+ Each guide above contains three complete application examples. Lazy creation,
37
+ configurable lifetimes, scopes, and dependency-ordered cleanup support these
38
+ patterns; the [tutorial](docs/guides/tutorial.md) explains how.
39
+
40
+ For a small graph, passing dependencies directly is often simpler. Other DI
41
+ libraries also offer typed composition and resource management; the
42
+ [comparison below](#how-it-compares) explains the tradeoffs.
43
+
44
+ ## Install
45
+
46
+ This checkout is a pre-1.0 release candidate, currently versioned `0.1.0`.
47
+ It includes breaking API changes, so expect to review migrations when updating.
48
+ These instructions install the current checkout without assuming an npm release
49
+ is available. Build a local package with Node 24 and npm:
50
+
51
+ ```sh
52
+ git clone https://github.com/dany-fedorov/di-bag.git
53
+ cd di-bag
54
+ npm ci
55
+ npm pack
56
+ ```
57
+
58
+ Then, from your application, install the generated archive using its local path:
59
+
60
+ ```sh
61
+ npm install /path/to/di-bag/di-bag-0.1.0.tgz
62
+ ```
63
+
64
+ The minimum supported TypeScript version is **6.0.3**; enable `strict` in your
65
+ `tsconfig.json`. The repository checks classic TypeScript 6.0.3 and native 7.0.2.
66
+ For browsers and Deno, see [runtime support](#runtime-support).
67
+
68
+ For an older checkout, follow the [single builder](docs/migrations/single-builder.md) and [API renaming](docs/migrations/api-renaming.md) migration guides.
69
+
70
+ ## Quickstart
71
+
72
+ A **service** can be a configuration object, a database client, or a function.
73
+ A **factory** creates a service. A **bag** holds those factories and gives each
74
+ one access to the services it needs. Services are created when needed, and
75
+ resources are cleaned up when you provide a disposer and close their bag.
76
+
77
+ Use `di-bag/node` in Node or Bun. Here, `greeter` needs `config`. Its parameter
78
+ type describes that dependency, and its return value is the service it provides:
79
+
80
+ ```ts
81
+ import { DiBag } from 'di-bag/node';
82
+
83
+ const app = DiBag.createBuilder()
84
+ .register({
85
+ config: () => ({ greeting: 'Hello' }),
86
+ greeter: ({ config }: { config: { greeting: string } }) => ({
87
+ greet(name: string) {
88
+ return `${config.greeting}, ${name}!`;
89
+ },
90
+ }),
91
+ })
92
+ .build();
93
+
94
+ const greeter = app.resolve('greeter');
95
+ console.log(greeter.greet('Ada')); // Hello, Ada!
96
+ ```
97
+
98
+ `.register()` adds factories to an immutable builder, `.build()` checks the
99
+ declared graph and creates the bag, and
100
+ `resolve('greeter')` creates the greeter and the config it needs. Resolving
101
+ `greeter` again returns the same instance. Registration order does not matter.
102
+
103
+ TypeScript knows that `greeter` has a `greet(name: string): string` method.
104
+ Removing the `config` factory makes `.build()` a compile-time error. Changing
105
+ `greeting` to a number also fails the type check because the greeter needs a string.
106
+
107
+ ## Swap a dependency for a test
108
+
109
+ Use `fork()` to create a separate bag with a replacement dependency.
110
+ Continuing the [quickstart](#quickstart):
111
+
112
+ ```ts
113
+ const testApp = app.fork(['config'], {
114
+ config: () => ({ greeting: 'Hi' }),
115
+ });
116
+
117
+ try {
118
+ console.log(testApp.resolve('greeter').greet('Ada')); // Hi, Ada!
119
+ console.log(app.resolve('greeter').greet('Ada')); // Hello, Ada!
120
+ } finally {
121
+ await testApp.close();
122
+ }
123
+ ```
124
+
125
+ The replacement must satisfy the original service contract. Each fork creates
126
+ its own instances and owns its own cleanup; close it separately.
127
+
128
+ ## Work with async services
129
+
130
+ An async factory provides a promise. Declare that promise in any dependent
131
+ factory and await it where you need the value:
132
+
133
+ ```ts
134
+ import { DiBag } from 'di-bag/node';
135
+
136
+ const app = DiBag.createBuilder()
137
+ .register({
138
+ greeting: async () => 'Hello',
139
+ message: async ({ greeting }: { greeting: Promise<string> }) =>
140
+ `${await greeting}, Ada!`,
141
+ })
142
+ .build();
143
+
144
+ console.log(await app.resolve('message')); // Hello, Ada!
145
+ ```
146
+
147
+ By default, repeated resolutions share the same in-flight promise. Synchronous
148
+ factories keep returning ordinary values. See
149
+ [async behavior](docs/guides/tutorial.md#async-edges-are-explicit) for details.
150
+
151
+ ## Give resources a clear owner
152
+
153
+ Wrap a factory with `withDisposal` to tell the bag how to release its result:
154
+
155
+ ```ts
156
+ import { DiBag } from 'di-bag/node';
157
+
158
+ const resources = DiBag.createBuilder()
159
+ .register({
160
+ cache: DiBag.withDisposal(
161
+ () => new Map<string, string>(),
162
+ (cache) => cache.clear(),
163
+ ),
164
+ })
165
+ .build();
166
+
167
+ try {
168
+ resources.resolve('cache').set('answer', '42');
169
+ } finally {
170
+ await resources.close();
171
+ }
172
+ ```
173
+
174
+ The same pattern works for connections, clients, and subscriptions. Cleanup can
175
+ be asynchronous. Dependents close before their dependencies, and resources that
176
+ were never created need no cleanup. Ordinary factories return borrowed values;
177
+ having a `close()` method alone does not transfer ownership to the bag.
178
+
179
+ ## Scopes and forks
180
+
181
+ | Operation | What it creates | Who closes it? |
182
+ | --- | --- | --- |
183
+ | `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. |
184
+ | `bag.fork()` | An independent bag with the same registrations and fresh instances | The caller closes it separately. |
185
+ | `bag.fork(keys, overrides)` | An independent bag with selected dependencies replaced | The caller closes it separately. |
186
+
187
+ For request handling, checked test replacements, and loading dynamic features,
188
+ see the [server guide](docs/guides/server-integration.md) and
189
+ [integration recipes](docs/guides/enterprise-integration.md).
190
+
191
+ The [tutorial](docs/guides/tutorial.md) also covers modules with private services,
192
+ typed tokens, class and function adapters, optional and lazy dependencies,
193
+ collections, startup, metadata, observers, and plugin validation.
194
+
195
+ ## How it compares
196
+
197
+ DI Bag's appeal is the combination of object-parameter factories, checks across
198
+ the declared graph, and explicit resource ownership. Decorator-free composition,
199
+ async factories, and TypeScript support are also available in other libraries.
200
+
201
+ | Alternative | Reasons to choose it | DI Bag's different emphasis |
202
+ | --- | --- | --- |
203
+ | 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. |
204
+ | Awilix | Function and class registration, inferred cradle types, lifetime options, and runtime strict checks. | Checks declared factory requirements against the registrations at compile time. |
205
+ | 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. |
206
+ | 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. |
207
+ | 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. |
208
+ | NestJS / Angular DI | Their native containers connect directly to framework components, testing tools, and lifecycles. | Provides standalone composition; applications supply the framework integration. |
209
+
210
+ See the [comparison guide](docs/guides/comparison.md) for primary sources,
211
+ differences in async and cleanup behavior, and the limits of these comparisons.
212
+ There is no verified performance ranking against these libraries.
213
+
214
+ ## Runtime support
215
+
216
+ The package has **zero runtime dependencies** and two entry points:
217
+
218
+ | Import | Purpose |
219
+ | --- | --- |
220
+ | `di-bag/node` | Ready-to-use factory composition in Node and Bun, with native Promise detection. |
221
+ | `di-bag` | Portable core for other hosts, including Deno and bundled browsers. Use explicit acquisition modes or configure a trusted native Promise predicate. |
222
+
223
+ The portable entry rejects automatic acquisition stages unless you configure a
224
+ trusted classifier. See [portable mode](docs/guides/tutorial.md#portable-mode)
225
+ for both setup options.
226
+
227
+ ## Tradeoffs and limits
228
+
229
+ - **Agent context is still your responsibility.** DI Bag does not choose module
230
+ boundaries, manage an agent's context window, or replace behavioral tests.
231
+ - **Async dependencies are explicit.** A factory returning `Promise<T>` exposes
232
+ that promise. Consumers declare and await it themselves.
233
+ - **Cleanup waits for your work.** Cancellation is cooperative; a factory or
234
+ disposer that never settles can keep `close()` pending.
235
+ - **Type safety follows the declared graph.** Casts, unchecked JavaScript, and
236
+ unknown plugins need appropriate runtime checks. Dependency cycles are detected
237
+ at runtime.
238
+ - **Graph types have a compiler cost.** Very long fluent expressions can exceed
239
+ compiler limits. Classic TypeScript still fails the recorded 1,000-call named
240
+ registration and replacement cases; use bulk registration or smaller groups.
241
+ See the [compiler evidence](docs/benchmarks/typescript.md) for tested forms and limits.
242
+ - **Framework integration belongs to the application.** DI Bag provides the
243
+ composition and ownership primitives; the host connects request, job, or UI
244
+ lifecycles.
245
+
246
+ ## Explore further
247
+
248
+ | Resource | What you'll find |
249
+ | --- | --- |
250
+ | [Complete tutorial](docs/guides/tutorial.md) | Learn every public API through examples, from first composition to advanced ownership. |
251
+ | [API reference](docs/guides/api-reference.md) | Exact generated signatures, overloads, type parameters, and API inventories. |
252
+ | [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request scopes, startup, and shutdown. |
253
+ | [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, scopes, and provider metadata. |
254
+ | [Integration guide](docs/guides/enterprise-integration.md) | Tested recipes for request ownership, substitutions, and dynamic features. |
255
+ | [Comparison with alternatives](docs/guides/comparison.md) | When DI Bag or another approach may be a better fit, with primary sources. |
256
+ | [Migration guides](docs/migrations/single-builder.md) | Before/after examples for the single builder and the [earlier API renaming](docs/migrations/api-renaming.md). |
257
+ | [Development and verification](docs/guides/development.md) | Full checks, portable runtime testing, compiler scale, and performance evidence. |
258
+ | [Documentation map](docs/README.md) | Current guides, migration history, and archived research and design notes. |
259
+
260
+ ## Working on DI Bag
261
+
262
+ [![CI](https://github.com/dany-fedorov/di-bag/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/dany-fedorov/di-bag/actions/workflows/ci.yml)
263
+ [![Runtime dependencies: 0](https://img.shields.io/badge/runtime_dependencies-0-2563eb)](package.json)
264
+ [![License: MIT](https://img.shields.io/badge/license-MIT-16a34a)](LICENSE)
265
+
266
+ After `npm ci`, run the main checks with Node and Bun installed:
267
+
268
+ ```sh
269
+ npm run platform:pin
270
+ npm run check
271
+ npm run check:native
272
+ ```
273
+
274
+ Run an example with `bun run examples/composition.ts`. The
275
+ [development guide](docs/guides/development.md) covers the remaining compiler,
276
+ platform, and packaging checks and their tool requirements.
277
+
278
+ ## License
279
+
280
+ [MIT](LICENSE) © Dany Fedorov
@@ -0,0 +1,42 @@
1
+ import type { Provider } from './provider';
2
+ import type { Factory } from './registration';
3
+ import type { Acquired, AcquisitionMode, NativeOutput, ModeOptions } from './acquisition-mode';
4
+ import type { TokenDependencyContract } from './token-types';
5
+ /** Cooperative cancellation information supplied to a context-aware acquisition. */
6
+ export interface AcquisitionContext {
7
+ /** Aborted when the acquisition's owning scope begins closing. */
8
+ readonly signal: AbortSignal;
9
+ }
10
+ type ContextFactory = (this: void, deps: never, context: AcquisitionContext) => unknown;
11
+ /** The named-dependency factory contract retained by an acquisition-context callback. */
12
+ export type ContextualFactory<F extends ContextFactory> = (this: void, deps: Parameters<F> extends [] ? {} : Parameters<F>[0]) => ReturnType<F>;
13
+ type FactoryOptions<M extends AcquisitionMode> = 'auto' extends M ? [options?: {
14
+ readonly context?: never;
15
+ readonly acquisitionMode?: M;
16
+ }] : [options: {
17
+ readonly context?: never;
18
+ readonly acquisitionMode: M;
19
+ }];
20
+ /**
21
+ * Describe a named-dependency factory receiving its acquisition owner's cancellation signal.
22
+ * Context allocation is opt-in through `context: 'acquisition'`, independent of callback arity.
23
+ * @param callback - A receiver-free factory taking dependencies and acquisition context.
24
+ * @param options - Context selection and result policy; acquisitionMode defaults to auto.
25
+ * @returns A lazy provider preserving exact output and named dependencies; adds no ownership.
26
+ * @typeParam F - The complete callback signature, retaining dependency and output inference.
27
+ * @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
28
+ */
29
+ export declare function fromFactory<F extends (this: void, deps: never, context: AcquisitionContext) => ('nativePromise' extends M ? Promise<unknown> : unknown), M extends AcquisitionMode = 'auto'>(callback: F, options: {
30
+ readonly context: 'acquisition';
31
+ } & ModeOptions<M>): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, M>>;
32
+ /**
33
+ * Describe a named-dependency factory with explicit or automatic result acquisition.
34
+ * Raw mode preserves the exact acquired value; nativePromise observes Promise fulfillment.
35
+ * @param callback - A receiver-free factory taking its named dependency object.
36
+ * @param options - Optional result acquisitionMode, defaulting to auto.
37
+ * @returns A lazy provider retaining exact output and dependency types without adding ownership.
38
+ * @typeParam F - The exact factory signature and exposed result.
39
+ * @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
40
+ */
41
+ export declare function fromFactory<F extends Factory, M extends AcquisitionMode = 'auto'>(callback: F & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...options: FactoryOptions<M>): Provider<F, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, M>>;
42
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fromFactory = fromFactory;
4
+ const errors_1 = require("./errors");
5
+ const provider_1 = require("./provider");
6
+ const provider_operations_1 = require("./provider-operations");
7
+ const acquisition_mode_1 = require("./acquisition-mode");
8
+ function fromFactory(callback, options) {
9
+ if (typeof callback !== 'function')
10
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_FACTORY', 'fromFactory requires a function', { operation: 'fromFactory' });
11
+ const mode = (0, acquisition_mode_1.acquisitionMode)(options);
12
+ if (options?.context !== undefined && options.context !== 'acquisition')
13
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_FACTORY', 'fromFactory context must be acquisition', { operation: 'fromFactory' });
14
+ const contextual = options?.context === 'acquisition';
15
+ const handle = (0, provider_1.createProvider)();
16
+ const create = contextual ? ((deps, context) => callback(deps, context)) : callback;
17
+ (0, provider_operations_1.retainDescription)(handle, (0, provider_operations_1.sourceDescription)(create, undefined, [], mode, contextual));
18
+ return handle;
19
+ }
@@ -0,0 +1,32 @@
1
+ import type { BindingId } from './runtime';
2
+ export type AcquisitionId = symbol;
3
+ export interface AcquisitionHistory {
4
+ readonly id: AcquisitionId;
5
+ readonly previous: AcquisitionHistory | undefined;
6
+ }
7
+ export interface AttemptIdentity {
8
+ readonly id: AcquisitionId;
9
+ readonly bindingId: BindingId;
10
+ readonly ownerId: symbol;
11
+ readonly label: string;
12
+ readonly dependencies: Set<AcquisitionId>;
13
+ readonly ancestry: AcquisitionHistory | undefined;
14
+ state: 'creating' | 'pending' | 'ready' | 'failed' | 'disposing' | 'disposed';
15
+ }
16
+ /** Family-wide identity and traversal only; finalizers stay with their owner. */
17
+ export declare class AcquisitionFamily {
18
+ private readonly attempts;
19
+ private readonly incoming;
20
+ private readonly constructing;
21
+ private readonly active;
22
+ add(attempt: AttemptIdentity): void;
23
+ deactivate(attempt: AttemptIdentity): void;
24
+ release(attempt: AttemptIdentity): void;
25
+ enter(attempt: AttemptIdentity): void;
26
+ leave(): void;
27
+ ancestry(bindingId: BindingId, ownerId: symbol, label: string, from?: AttemptIdentity): AcquisitionHistory | undefined;
28
+ dependencyPath(from: AttemptIdentity, dependency: string): readonly string[];
29
+ retireIncoming(attempt: AttemptIdentity): void;
30
+ recordEdge(from: AttemptIdentity, to: AttemptIdentity): void;
31
+ private path;
32
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AcquisitionFamily = void 0;
4
+ const errors_1 = require("./errors");
5
+ /** Family-wide identity and traversal only; finalizers stay with their owner. */
6
+ class AcquisitionFamily {
7
+ attempts = new Map();
8
+ incoming = new Map();
9
+ constructing = [];
10
+ // Only constructing/pending attempts can repeat an active binding+owner.
11
+ active = new Map();
12
+ add(attempt) {
13
+ this.attempts.set(attempt.id, attempt);
14
+ if (attempt.state !== 'creating' && attempt.state !== 'pending')
15
+ return;
16
+ let owners = this.active.get(attempt.bindingId);
17
+ if (!owners)
18
+ this.active.set(attempt.bindingId, owners = new Map());
19
+ let ids = owners.get(attempt.ownerId);
20
+ if (!ids)
21
+ owners.set(attempt.ownerId, ids = new Set());
22
+ ids.add(attempt.id);
23
+ }
24
+ deactivate(attempt) {
25
+ const owners = this.active.get(attempt.bindingId);
26
+ const ids = owners?.get(attempt.ownerId);
27
+ if (!ids)
28
+ return;
29
+ ids.delete(attempt.id);
30
+ if (!ids.size)
31
+ owners.delete(attempt.ownerId);
32
+ if (!owners.size)
33
+ this.active.delete(attempt.bindingId);
34
+ }
35
+ release(attempt) {
36
+ this.deactivate(attempt);
37
+ // Remove this consumer from reverse indexes before its outgoing edges clear.
38
+ for (const dependency of attempt.dependencies) {
39
+ const consumers = this.incoming.get(dependency);
40
+ consumers?.delete(attempt.id);
41
+ if (consumers?.size === 0)
42
+ this.incoming.delete(dependency);
43
+ }
44
+ this.incoming.delete(attempt.id);
45
+ this.attempts.delete(attempt.id);
46
+ }
47
+ enter(attempt) { this.constructing.push(attempt.id); }
48
+ leave() { this.constructing.pop(); }
49
+ ancestry(bindingId, ownerId, label, from) {
50
+ const source = from ?? this.attempts.get(this.constructing.at(-1));
51
+ const ancestry = source ? { id: source.id, previous: source.ancestry } : undefined;
52
+ // Most cold reads introduce an unrelated binding. Share history in O(1),
53
+ // materializing the original label order only for a possible active cycle.
54
+ if (!this.active.get(bindingId)?.has(ownerId))
55
+ return ancestry;
56
+ const history = [];
57
+ for (let entry = ancestry; entry; entry = entry.previous)
58
+ history.push(entry.id);
59
+ history.reverse();
60
+ // A public synchronous resolve has no proxy edge, but is still construction.
61
+ const active = [...new Set([...history, ...this.constructing])]
62
+ .map(id => this.attempts.get(id))
63
+ .filter((attempt) => !!attempt && (attempt.state === 'creating' || attempt.state === 'pending'));
64
+ const repeated = active.findIndex(attempt => attempt.bindingId === bindingId && attempt.ownerId === ownerId);
65
+ if (repeated !== -1)
66
+ throw (0, errors_1.libraryError)('DI_BAG_CYCLE', `cycle: ${[...active.slice(repeated).map(attempt => attempt.label), label].join(' -> ')}`, { path: Object.freeze([...active.slice(repeated).map(attempt => attempt.label), label]) });
67
+ return ancestry;
68
+ }
69
+ dependencyPath(from, dependency) {
70
+ // Linked histories stay shared on successful acquisition; only diagnostics
71
+ // materialize the consumer path in root-to-leaf order.
72
+ const history = [];
73
+ for (let entry = from.ancestry; entry; entry = entry.previous) {
74
+ const label = this.attempts.get(entry.id)?.label;
75
+ if (label !== undefined)
76
+ history.push(label);
77
+ }
78
+ history.reverse();
79
+ return Object.freeze([...history, from.label, dependency]);
80
+ }
81
+ retireIncoming(attempt) {
82
+ const consumers = this.incoming.get(attempt.id);
83
+ if (!consumers)
84
+ return;
85
+ for (const id of consumers)
86
+ this.attempts.get(id)?.dependencies.delete(attempt.id);
87
+ this.incoming.delete(attempt.id);
88
+ }
89
+ recordEdge(from, to) {
90
+ // A retained failed proxy may be used again, but its retired ID stays dead.
91
+ if (!this.attempts.has(from.id))
92
+ return;
93
+ // Re-reading an existing edge cannot introduce a new cycle.
94
+ if (from.dependencies.has(to.id))
95
+ return;
96
+ const path = this.path(to.id, from.id);
97
+ if (path) {
98
+ const labels = [...path, to.id].map(id => this.attempts.get(id).label);
99
+ throw (0, errors_1.libraryError)('DI_BAG_CYCLE', `cycle: ${labels.join(' -> ')}`, { path: Object.freeze(labels) });
100
+ }
101
+ from.dependencies.add(to.id);
102
+ const consumers = this.incoming.get(to.id) ?? new Set();
103
+ consumers.add(from.id);
104
+ this.incoming.set(to.id, consumers);
105
+ }
106
+ path(from, to) {
107
+ const attempt = this.attempts.get(from);
108
+ if (!attempt)
109
+ return undefined;
110
+ if (from === to)
111
+ return [from];
112
+ if (attempt.dependencies.size === 0)
113
+ return undefined;
114
+ const seen = new Set([from]);
115
+ const stack = [{ id: from, dependencies: attempt.dependencies.values() }];
116
+ while (stack.length) {
117
+ const next = stack[stack.length - 1].dependencies.next();
118
+ if (next.done) {
119
+ stack.pop();
120
+ continue;
121
+ }
122
+ const dependency = this.attempts.get(next.value);
123
+ if (!dependency)
124
+ continue;
125
+ if (next.value === to)
126
+ return [...stack.map(frame => frame.id), to];
127
+ if (seen.has(next.value))
128
+ continue;
129
+ seen.add(next.value);
130
+ stack.push({ id: next.value, dependencies: dependency.dependencies.values() });
131
+ }
132
+ return undefined;
133
+ }
134
+ }
135
+ exports.AcquisitionFamily = AcquisitionFamily;
@@ -0,0 +1,34 @@
1
+ import type { LifecycleObservers } from './observers';
2
+ import type { Unsatisfied } from './types';
3
+ /**
4
+ * How an acquisition stage treats its returned value: configured classification,
5
+ * the exact raw value, or an observed native Promise fulfillment.
6
+ */
7
+ export type AcquisitionMode = 'auto' | 'raw' | 'nativePromise';
8
+ /** Portable facade configuration for `auto` acquisition stages. */
9
+ export interface RuntimeOptions {
10
+ /** Return true only for native Promises the host can observe without thenable assimilation. */
11
+ readonly isNativePromise: (this: void, value: unknown) => boolean;
12
+ }
13
+ export interface RuntimeContext {
14
+ readonly isNativePromise?: RuntimeOptions['isNativePromise'];
15
+ readonly observers?: LifecycleObservers;
16
+ }
17
+ export declare const unconfigured: RuntimeContext;
18
+ export declare function runtimeContext(options: RuntimeOptions, previous?: RuntimeContext): RuntimeContext;
19
+ export type Acquired<O, M extends AcquisitionMode> = M extends 'raw' ? O : Awaited<O>;
20
+ export type ModeOptions<M extends AcquisitionMode, Default extends AcquisitionMode = 'auto'> = Default extends M ? {
21
+ readonly acquisitionMode?: M;
22
+ } : {
23
+ readonly acquisitionMode: M;
24
+ };
25
+ export type StageOptions<M extends AcquisitionMode> = 'auto' extends M ? [options?: {
26
+ readonly acquisitionMode: M;
27
+ }] : [options: {
28
+ readonly acquisitionMode: M;
29
+ }];
30
+ export type NativeOutput<O, M extends AcquisitionMode> = 'nativePromise' extends M ? [O] extends [Promise<unknown>] ? unknown : Unsatisfied<'nativePromise acquisition requires a Promise output', {}> : unknown;
31
+ export declare function acquisitionMode(options: {
32
+ readonly acquisitionMode?: AcquisitionMode;
33
+ } | undefined, fallback?: AcquisitionMode): AcquisitionMode;
34
+ export declare function requireClassificationCapability(modes: Iterable<AcquisitionMode>, context: RuntimeContext): void;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unconfigured = void 0;
4
+ exports.runtimeContext = runtimeContext;
5
+ exports.acquisitionMode = acquisitionMode;
6
+ exports.requireClassificationCapability = requireClassificationCapability;
7
+ const errors_1 = require("./errors");
8
+ exports.unconfigured = Object.freeze({});
9
+ function runtimeContext(options, previous = exports.unconfigured) {
10
+ if (typeof options !== 'object' || options === null)
11
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_CONFIGURATION', 'withConfiguration runtime requires isNativePromise', { operation: 'withConfiguration' });
12
+ const { isNativePromise } = options;
13
+ if (typeof isNativePromise !== 'function')
14
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_CONFIGURATION', 'withConfiguration runtime requires isNativePromise', { operation: 'withConfiguration' });
15
+ return Object.freeze({ ...previous, isNativePromise });
16
+ }
17
+ function acquisitionMode(options, fallback = 'auto') {
18
+ if (options === undefined)
19
+ return fallback;
20
+ if (typeof options !== 'object' || options === null)
21
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_ACQUISITION_MODE', 'invalid acquisition options', { option: 'acquisitionMode' });
22
+ const selected = options.acquisitionMode;
23
+ const mode = selected === undefined ? fallback : selected;
24
+ if (mode !== 'auto' && mode !== 'raw' && mode !== 'nativePromise')
25
+ throw (0, errors_1.libraryError)('DI_BAG_INVALID_ACQUISITION_MODE', 'invalid acquisitionMode: use auto, raw, or nativePromise', { option: 'acquisitionMode' });
26
+ return mode;
27
+ }
28
+ function requireClassificationCapability(modes, context) {
29
+ 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
+ }
35
+ }
@@ -0,0 +1,44 @@
1
+ import type { BindingGraph, BindingId, BindingKey } from './runtime';
2
+ import type { RegistrationSnapshot, AcquisitionSnapshot } from './inspection';
3
+ import type { RuntimeContext } from './acquisition-mode';
4
+ /** Mutable, runtime-local attempts. Binding descriptions never carry ownership. */
5
+ export declare class ScopeAcquisitions {
6
+ private readonly graph;
7
+ private readonly context;
8
+ private readonly parent?;
9
+ readonly ownerId: symbol;
10
+ private readonly cache;
11
+ private readonly attempts;
12
+ private readonly retired;
13
+ private readonly failures;
14
+ private invocationSequence;
15
+ private readonly owned;
16
+ private state;
17
+ private closing;
18
+ private controller;
19
+ private acquisitionContext;
20
+ private cancellationStarted;
21
+ private cancellationCause;
22
+ private readonly shared;
23
+ private readonly family;
24
+ constructor(graph: BindingGraph, context: RuntimeContext, parent?: ScopeAcquisitions | undefined, shared?: readonly BindingId[]);
25
+ private owner;
26
+ resolve(key: BindingKey): unknown;
27
+ resolveAll(key: symbol): readonly unknown[];
28
+ private resolveCollection;
29
+ acquire(key: BindingKey): Promise<void>;
30
+ private takeExposed;
31
+ inspect(bindingId: BindingId, path?: readonly BindingId[]): readonly AcquisitionSnapshot<readonly unknown[]>[];
32
+ isTransient(bindingId: BindingId, path?: readonly BindingId[]): boolean;
33
+ /** Relationship uses the effective owner graph; frames use canonical attempts. */
34
+ inspectDescription(bindingId: BindingId): Pick<RegistrationSnapshot<object, readonly unknown[]>, 'registrationMetadata' | 'aliasTarget'>;
35
+ private assertAliasPath;
36
+ assertOpen(): void;
37
+ close(beforeDispose?: Promise<void>, cause?: unknown): Promise<void>;
38
+ private getContext;
39
+ private resolveBinding;
40
+ private eventFields;
41
+ private observeAttempt;
42
+ private retire;
43
+ private disposeAll;
44
+ }