di-bag 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/dist/acquisition-context.d.ts +3 -3
- package/dist/acquisition-family.d.ts +5 -0
- package/dist/acquisition-family.js +20 -0
- package/dist/acquisition-mode.d.ts +3 -1
- package/dist/acquisition.d.ts +4 -0
- package/dist/acquisition.js +9 -0
- package/dist/composition-report.d.ts +13 -0
- package/dist/composition-report.js +2 -0
- package/dist/composition.d.ts +4 -4
- package/dist/di-bag.d.ts +19 -4
- package/dist/di-bag.js +8 -0
- package/dist/index.d.ts +3 -1
- package/dist/inspection.d.ts +35 -0
- package/dist/lifetime-types.d.ts +12 -3
- package/dist/module-types.d.ts +2 -2
- package/dist/runtime.d.ts +12 -1
- package/dist/runtime.js +41 -0
- package/dist/types.d.ts +36 -9
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -35,8 +35,6 @@ The minimum supported TypeScript version is **6.0.3**; enable `strict` in your
|
|
|
35
35
|
`tsconfig.json`. The repository checks classic TypeScript 6.0.3 and native 7.0.2.
|
|
36
36
|
For browsers and Deno, see [runtime support](#runtime-support).
|
|
37
37
|
|
|
38
|
-
For an older checkout, follow the [single builder](docs/migrations/single-builder.md) and [API renaming](docs/migrations/api-renaming.md) migration guides.
|
|
39
|
-
|
|
40
38
|
## Quickstart
|
|
41
39
|
|
|
42
40
|
A **service** can be a configuration object, a database client, or a function.
|
|
@@ -185,9 +183,10 @@ DI Bag supplies checked composition and resource ownership.
|
|
|
185
183
|
|
|
186
184
|
The [agent harness and graph guide](docs/guides/agent-harnesses-and-graphs.md)
|
|
187
185
|
combines private feature modules, an LLM-backed node, metadata inspection, and
|
|
188
|
-
fork-based fixture tests in one runnable example.
|
|
189
|
-
|
|
190
|
-
ordinary node calls. Use your graph
|
|
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.
|
|
191
190
|
|
|
192
191
|
## How it compares
|
|
193
192
|
|
|
@@ -248,12 +247,12 @@ for both setup options.
|
|
|
248
247
|
| [API reference](docs/guides/api-reference.md) | Exact generated signatures, overloads, type parameters, and API inventories. |
|
|
249
248
|
| [Server guide](docs/guides/server-integration.md) | Node HTTP, Express, Fastify, Bun, and Deno: shared services, request scopes, startup, and shutdown. |
|
|
250
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`. |
|
|
251
251
|
| [Runnable examples](examples) | Modules, tokens, composition, collections, plugins, observers, scopes, and provider metadata. |
|
|
252
252
|
| [Integration guide](docs/guides/enterprise-integration.md) | Tested recipes for request ownership, substitutions, and dynamic features. |
|
|
253
253
|
| [Comparison with alternatives](docs/guides/comparison.md) | When DI Bag or another approach may be a better fit, with primary sources. |
|
|
254
|
-
| [Migration guides](docs/migrations/single-builder.md) | Before/after examples for the single builder and the [earlier API renaming](docs/migrations/api-renaming.md). |
|
|
255
254
|
| [Development and verification](docs/guides/development.md) | Full checks, portable runtime testing, compiler scale, and performance evidence. |
|
|
256
|
-
| [Documentation map](docs/README.md) |
|
|
255
|
+
| [Documentation map](docs/README.md) | Every guide, the generated reference, and the contributor documents. |
|
|
257
256
|
|
|
258
257
|
## Working on DI Bag
|
|
259
258
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Provider } from './provider';
|
|
2
2
|
import type { Factory } from './registration';
|
|
3
|
-
import type { Acquired, AcquisitionMode, NativeOutput, ModeOptions } from './acquisition-mode';
|
|
3
|
+
import type { Acquired, AcquisitionMode, AutoOutput, NativeOutput, ModeOptions } from './acquisition-mode';
|
|
4
4
|
import type { TokenDependencyContract } from './token-types';
|
|
5
5
|
/** Cooperative cancellation information supplied to a context-aware acquisition. */
|
|
6
6
|
export interface AcquisitionContext {
|
|
@@ -26,7 +26,7 @@ type FactoryOptions<M extends AcquisitionMode> = 'auto' extends M ? [options?: {
|
|
|
26
26
|
* @typeParam F - The complete callback signature, retaining dependency and output inference.
|
|
27
27
|
* @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
|
|
28
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
|
|
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 & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, options: {
|
|
30
30
|
readonly context: 'acquisition';
|
|
31
31
|
} & ModeOptions<M>): Provider<ContextualFactory<F>, Readonly<{}>, readonly [], TokenDependencyContract, Acquired<ReturnType<F>, M>>;
|
|
32
32
|
/**
|
|
@@ -38,5 +38,5 @@ export declare function fromFactory<F extends (this: void, deps: never, context:
|
|
|
38
38
|
* @typeParam F - The exact factory signature and exposed result.
|
|
39
39
|
* @typeParam M - The raw, nativePromise, or configured auto acquisition policy.
|
|
40
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>>;
|
|
41
|
+
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>>;
|
|
42
42
|
export {};
|
|
@@ -26,6 +26,11 @@ export declare class AcquisitionFamily {
|
|
|
26
26
|
leave(): void;
|
|
27
27
|
ancestry(bindingId: BindingId, ownerId: symbol, label: string, from?: AttemptIdentity): AcquisitionHistory | undefined;
|
|
28
28
|
dependencyPath(from: AttemptIdentity, dependency: string): readonly string[];
|
|
29
|
+
/** Distinct consumer-to-dependency binding edges recorded by live attempts, in attempt order. */
|
|
30
|
+
observedEdges(): readonly {
|
|
31
|
+
readonly from: BindingId;
|
|
32
|
+
readonly to: BindingId;
|
|
33
|
+
}[];
|
|
29
34
|
retireIncoming(attempt: AttemptIdentity): void;
|
|
30
35
|
recordEdge(from: AttemptIdentity, to: AttemptIdentity): void;
|
|
31
36
|
private path;
|
|
@@ -78,6 +78,26 @@ class AcquisitionFamily {
|
|
|
78
78
|
history.reverse();
|
|
79
79
|
return Object.freeze([...history, from.label, dependency]);
|
|
80
80
|
}
|
|
81
|
+
/** Distinct consumer-to-dependency binding edges recorded by live attempts, in attempt order. */
|
|
82
|
+
observedEdges() {
|
|
83
|
+
const seen = new Map();
|
|
84
|
+
const edges = [];
|
|
85
|
+
for (const attempt of this.attempts.values()) {
|
|
86
|
+
for (const dependency of attempt.dependencies) {
|
|
87
|
+
const target = this.attempts.get(dependency);
|
|
88
|
+
if (!target)
|
|
89
|
+
continue;
|
|
90
|
+
let targets = seen.get(attempt.bindingId);
|
|
91
|
+
if (!targets)
|
|
92
|
+
seen.set(attempt.bindingId, targets = new Set());
|
|
93
|
+
if (targets.has(target.bindingId))
|
|
94
|
+
continue;
|
|
95
|
+
targets.add(target.bindingId);
|
|
96
|
+
edges.push(Object.freeze({ from: attempt.bindingId, to: target.bindingId }));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return Object.freeze(edges);
|
|
100
|
+
}
|
|
81
101
|
retireIncoming(attempt) {
|
|
82
102
|
const consumers = this.incoming.get(attempt.id);
|
|
83
103
|
if (!consumers)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LifecycleObservers } from './observers';
|
|
2
|
-
import type { Unsatisfied } from './types';
|
|
2
|
+
import type { 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.
|
|
@@ -28,6 +28,8 @@ export type StageOptions<M extends AcquisitionMode> = 'auto' extends M ? [option
|
|
|
28
28
|
readonly acquisitionMode: M;
|
|
29
29
|
}];
|
|
30
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
|
+
/** 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;
|
|
31
33
|
export declare function acquisitionMode(options: {
|
|
32
34
|
readonly acquisitionMode?: AcquisitionMode;
|
|
33
35
|
} | undefined, fallback?: AcquisitionMode): AcquisitionMode;
|
package/dist/acquisition.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export declare class ScopeAcquisitions {
|
|
|
32
32
|
isTransient(bindingId: BindingId, path?: readonly BindingId[]): boolean;
|
|
33
33
|
/** Relationship uses the effective owner graph; frames use canonical attempts. */
|
|
34
34
|
inspectDescription(bindingId: BindingId): Pick<RegistrationSnapshot<object, readonly unknown[]>, 'registrationMetadata' | 'aliasTarget'>;
|
|
35
|
+
observedEdges(): readonly {
|
|
36
|
+
readonly from: BindingId;
|
|
37
|
+
readonly to: BindingId;
|
|
38
|
+
}[];
|
|
35
39
|
private assertAliasPath;
|
|
36
40
|
assertOpen(): void;
|
|
37
41
|
close(beforeDispose?: Promise<void>, cause?: unknown): Promise<void>;
|
package/dist/acquisition.js
CHANGED
|
@@ -115,6 +115,7 @@ class ScopeAcquisitions {
|
|
|
115
115
|
const owner = this.owner(bindingId);
|
|
116
116
|
return owner === this ? { registrationMetadata: description.metadata } : owner.inspectDescription(bindingId);
|
|
117
117
|
}
|
|
118
|
+
observedEdges() { return this.family.observedEdges(); }
|
|
118
119
|
assertAliasPath(bindingId, path) {
|
|
119
120
|
if (path.includes(bindingId))
|
|
120
121
|
throw (0, errors_1.libraryError)('DI_BAG_CYCLE', `alias cycle: ${[...path, bindingId].map(id => this.graph.label(id)).join(' -> ')}`, { path: Object.freeze([...path, bindingId].map(id => this.graph.label(id))) });
|
|
@@ -239,8 +240,12 @@ class ScopeAcquisitions {
|
|
|
239
240
|
return target === undefined ? undefined : this.takeExposed(this.resolveBinding(target, attempt));
|
|
240
241
|
};
|
|
241
242
|
const references = new Map(description.references.map(reference => [reference.slot, reference]));
|
|
243
|
+
const invalidAccess = (access) => (0, errors_1.libraryError)('DI_BAG_INVALID_DEPENDENCY_ACCESS', `Cannot inspect the dependencies of ${JSON.stringify(attempt.label)}: ${access} is not supported. Read each named dependency directly; the dependency object resolves lazily.`, { operation: 'resolve', consumer: attempt.label, access });
|
|
242
244
|
const deps = new Proxy(Object.create(null), {
|
|
243
245
|
get: (_, key) => {
|
|
246
|
+
// JSON.stringify probes toJSON through get before enumerating; name the real operation.
|
|
247
|
+
if (key === 'toJSON')
|
|
248
|
+
throw invalidAccess('JSON.stringify');
|
|
244
249
|
const reference = typeof key === 'symbol' ? references.get(key) : undefined;
|
|
245
250
|
if (reference)
|
|
246
251
|
return reference.kind === 'lazy' ? () => read(reference.key)
|
|
@@ -249,6 +254,10 @@ class ScopeAcquisitions {
|
|
|
249
254
|
return undefined;
|
|
250
255
|
return read(key);
|
|
251
256
|
},
|
|
257
|
+
// Only `get` is lazy and checked; every other reflection would silently report an empty object.
|
|
258
|
+
has: (_, key) => { throw invalidAccess(`'${String(key)}' in deps`); },
|
|
259
|
+
ownKeys: () => { throw invalidAccess('enumeration (Object.keys, spread, JSON.stringify)'); },
|
|
260
|
+
getOwnPropertyDescriptor: (_, key) => { throw invalidAccess(`descriptor of '${String(key)}'`); },
|
|
252
261
|
});
|
|
253
262
|
this.observeAttempt(attempt, 'acquisition-started');
|
|
254
263
|
this.family.enter(attempt);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Builder } from './di-bag';
|
|
2
|
+
import type { CheckedLifetimes } from './lifetime-types';
|
|
3
|
+
import type { CheckedConstraints, CompleteConstraints, NeedConstraint } from './module-types';
|
|
4
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Entry, RegistrationsFromEntries } from './types';
|
|
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>>;
|
|
7
|
+
/**
|
|
8
|
+
* The compile-time verdict for a builder: `void` when `build()` would be accepted,
|
|
9
|
+
* otherwise the same failure `build()` reports, including its details.
|
|
10
|
+
* Read it through `builder.verifyGraph() satisfies void;` or as `CompositionReport<typeof builder>`.
|
|
11
|
+
*/
|
|
12
|
+
export type CompositionReport<B> = B extends Builder<infer E, infer C> ? [Reports<E, C>] extends [never] ? void : Reports<E, C> : never;
|
|
13
|
+
export {};
|
package/dist/composition.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Acquired, AcquisitionMode, NativeOutput, StageOptions } from './acquisition-mode';
|
|
1
|
+
import type { Acquired, AcquisitionMode, AutoOutput, NativeOutput, StageOptions } from './acquisition-mode';
|
|
2
2
|
import type { Provider } from './provider';
|
|
3
3
|
import type { DependencyReference } from './dependency-references';
|
|
4
4
|
import type { TokenArguments, ReferenceGraph, DependencyTupleAdmission } from './token-types';
|
|
@@ -19,7 +19,7 @@ export type CompositionFunction<T extends readonly DependencyReference[], O = un
|
|
|
19
19
|
* @returns A lazy provider retaining the dependency graph and exact return type.
|
|
20
20
|
* @typeParam F - The exact positional function signature retained by the provider.
|
|
21
21
|
*/
|
|
22
|
-
export declare function fromFunction<const T extends readonly DependencyReference[], F extends CompositionFunction<NoInfer<T>, 'nativePromise' extends M ? Promise<unknown> : unknown>, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, callback: F & CompositionArguments<TokenArguments<NoInfer<T>>, Parameters<NoInfer<F>>> & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<OutputFactory<ReturnType<F>>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<ReturnType<F>, M>>;
|
|
22
|
+
export declare function fromFunction<const T extends readonly DependencyReference[], F extends CompositionFunction<NoInfer<T>, 'nativePromise' extends M ? Promise<unknown> : unknown>, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, callback: F & CompositionArguments<TokenArguments<NoInfer<T>>, Parameters<NoInfer<F>>> & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>> & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<OutputFactory<ReturnType<F>>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<ReturnType<F>, M>>;
|
|
23
23
|
/**
|
|
24
24
|
* Adapt a positional function whose parameters exactly match the selected dependency values.
|
|
25
25
|
* @param tokens - A finite tuple of typed tokens and dependency references.
|
|
@@ -28,7 +28,7 @@ export declare function fromFunction<const T extends readonly DependencyReferenc
|
|
|
28
28
|
* @returns A reusable provider; no dependency or result is implicitly awaited.
|
|
29
29
|
* @typeParam F - The exact positional function signature retained by the provider.
|
|
30
30
|
*/
|
|
31
|
-
export declare function fromFunction<const T extends readonly DependencyReference[], F extends CompositionFunction<NoInfer<T>>, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, callback: F & CompositionArguments<TokenArguments<NoInfer<T>>, Parameters<NoInfer<F>>> & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<OutputFactory<ReturnType<F>>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<ReturnType<F>, M>>;
|
|
31
|
+
export declare function fromFunction<const T extends readonly DependencyReference[], F extends CompositionFunction<NoInfer<T>>, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, callback: F & CompositionArguments<TokenArguments<NoInfer<T>>, Parameters<NoInfer<F>>> & NativeOutput<ReturnType<NoInfer<F>>, NoInfer<M>> & AutoOutput<ReturnType<NoInfer<F>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<OutputFactory<ReturnType<F>>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<ReturnType<F>, M>>;
|
|
32
32
|
/**
|
|
33
33
|
* Adapt a concrete constructor while preserving its prototype, private fields, and `new.target`.
|
|
34
34
|
* @param tokens - A finite tuple whose dependency values match the constructor parameters.
|
|
@@ -37,5 +37,5 @@ export declare function fromFunction<const T extends readonly DependencyReferenc
|
|
|
37
37
|
* @returns A lazy provider that constructs one instance per acquisition attempt.
|
|
38
38
|
* @throws When the supplied runtime value is not constructable.
|
|
39
39
|
*/
|
|
40
|
-
export declare function fromClass<const T extends readonly DependencyReference[], C extends new (...args: TokenArguments<NoInfer<T>>) => unknown, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, constructor: C & CompositionArguments<TokenArguments<NoInfer<T>>, ConstructorParameters<NoInfer<C>>> & NativeOutput<InstanceType<NoInfer<C>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<() => InstanceType<C>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<InstanceType<C>, M>>;
|
|
40
|
+
export declare function fromClass<const T extends readonly DependencyReference[], C extends new (...args: TokenArguments<NoInfer<T>>) => unknown, M extends AcquisitionMode = 'auto'>(tokens: T & DependencyTupleAdmission<T>, constructor: C & CompositionArguments<TokenArguments<NoInfer<T>>, ConstructorParameters<NoInfer<C>>> & NativeOutput<InstanceType<NoInfer<C>>, NoInfer<M>> & AutoOutput<InstanceType<NoInfer<C>>, NoInfer<M>>, ...modeOptions: StageOptions<M>): Provider<() => InstanceType<C>, Readonly<{}>, readonly [], ReferenceGraph<T>, Acquired<InstanceType<C>, M>>;
|
|
41
41
|
export {};
|
package/dist/di-bag.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { withDisposal } from './registration';
|
|
|
6
6
|
import type { FactoryWithDisposal, Factory, Registration, Registrations } from './registration';
|
|
7
7
|
import { BindingGraph, BagRuntime } from './runtime';
|
|
8
8
|
import type { Module } from './module';
|
|
9
|
+
import type { CompositionReport } from './composition-report';
|
|
9
10
|
import type { CheckedConstraints, CompleteConstraints, ExternalRequirements, IncrementalConstraints, ModulePublicProviders, ModuleSealedConstraints, NeedConstraint } from './module-types';
|
|
10
11
|
import type { CheckedLifetimes } from './lifetime-types';
|
|
11
12
|
import { withLifetime } from './lifetime';
|
|
@@ -17,13 +18,13 @@ import { withMetadata, transformService } from './provider';
|
|
|
17
18
|
import { fromFunction, fromClass } from './composition';
|
|
18
19
|
import type { RuntimeContext, RuntimeOptions } from './acquisition-mode';
|
|
19
20
|
import type { ProviderRegistrationMetadata, ProviderAcquisitionMetadata } from './provider';
|
|
20
|
-
import type { RegistrationSnapshot } from './inspection';
|
|
21
|
+
import type { GraphSnapshot, RegistrationSnapshot } from './inspection';
|
|
21
22
|
import { token } from './tokens';
|
|
22
23
|
import type { PluginProviderFactory } from './plugins';
|
|
23
24
|
import type { TokenBase, TokenKey, TokenService } from './tokens';
|
|
24
25
|
import type { TokenBinding, BindingOutput, TokenMember, TokenTupleAdmission, SelectionKey, ReboundSelection } from './token-types';
|
|
25
26
|
import type { BuilderReplacementRegistration, ReplacementAdmission, ReplacedEntries, ZeroDependencyAdmission } from './replacement-types';
|
|
26
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, RegistrationEntries, Entry, EntryKeys, OverrideFactoryContext, RegistrationsFromEntries, IncrementalChecked, Introduces, IntroducesKeys, OverrideRegistrations, Overrides, ServicesOf, ReplacementKeyOf, ReplacementOutput, SelectedRegistrations, Selection, NamedAdmission } from './types';
|
|
27
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, RegistrationEntries, Entry, EntryKeys, OverrideFactoryContext, RegistrationsFromEntries, IncrementalChecked, Introduces, IntroducesKeys, OverrideRegistrations, Overrides, ServicesOf, ReplacementKeyOf, ReplacementOutput, SelectedRegistrations, Selection, NamedAdmission, ThenableAdmission } from './types';
|
|
27
28
|
type ReplacementFactory<O> = (this: void) => O;
|
|
28
29
|
declare const constraintInvariant: unique symbol;
|
|
29
30
|
/**
|
|
@@ -66,6 +67,13 @@ declare class Bag<R extends Registrations, C extends NeedConstraint = never> {
|
|
|
66
67
|
* @returns A frozen point-in-time snapshot. Application-owned metadata payloads are not frozen.
|
|
67
68
|
*/
|
|
68
69
|
inspect<K extends (keyof R & string) | TokenBase>(token: K & ([K] extends [string] ? unknown : TokenMember<R, K>)): RegistrationSnapshot<ProviderRegistrationMetadata<R[SelectionKey<K> & keyof R]>, ProviderAcquisitionMetadata<R[SelectionKey<K> & keyof R]>>;
|
|
70
|
+
/**
|
|
71
|
+
* Describe every binding this bag can resolve and the dependency edges observed so far.
|
|
72
|
+
* Nothing is acquired. Named dependencies declared on factory parameters are not visible
|
|
73
|
+
* until the factory runs; the static graph tool reports them from source.
|
|
74
|
+
* @returns A frozen point-in-time snapshot; application-owned metadata payloads are not frozen.
|
|
75
|
+
*/
|
|
76
|
+
inspectGraph(): GraphSnapshot;
|
|
69
77
|
/**
|
|
70
78
|
* Create a tracked child that borrows selected parent acquisitions.
|
|
71
79
|
* @param options - A checked selection of non-transient services to share lazily.
|
|
@@ -129,14 +137,14 @@ declare class Builder<E extends Entry, C extends NeedConstraint = never> {
|
|
|
129
137
|
*/
|
|
130
138
|
register<N extends {
|
|
131
139
|
[K in keyof N]: Registration;
|
|
132
|
-
}>(more: N & Registrations & ([N] extends [never] ? never : NamedAdmission<N> & IntroducesKeys<EntryKeys<E>, keyof N> & IncrementalChecked<E, N> & CheckedConstraints<C, OverrideRegistrations<RegistrationsFromEntries<E>, N>>)): Builder<E | RegistrationEntries<N>, C>;
|
|
140
|
+
}>(more: N & Registrations & ([N] extends [never] ? never : NamedAdmission<N> & ThenableAdmission<N> & IntroducesKeys<EntryKeys<E>, keyof N> & IncrementalChecked<E, N> & CheckedConstraints<C, OverrideRegistrations<RegistrationsFromEntries<E>, N>>)): Builder<E | RegistrationEntries<N>, C>;
|
|
133
141
|
/**
|
|
134
142
|
* Register a provider to a typed token.
|
|
135
143
|
* @param token - A new typed token identity.
|
|
136
144
|
* @param registration - A registration whose exposed output satisfies the token service type.
|
|
137
145
|
* @returns A new builder retaining the provider's metadata, lifetime, dependencies, and ownership stages.
|
|
138
146
|
*/
|
|
139
|
-
register<T extends TokenBase, V extends Registration>(token: T & TokenTupleAdmission<readonly [T]> & IntroducesKeys<EntryKeys<E>, TokenKey<T>>, registration: V & Registration & BindingOutput<NoInfer<T>, NoInfer<V>> & IncrementalChecked<E, Record<TokenKey<T>, TokenBinding<NoInfer<T>, NoInfer<V>>>> & CheckedConstraints<C, OverrideRegistrations<RegistrationsFromEntries<E>, Record<TokenKey<T>, TokenBinding<NoInfer<T>, NoInfer<V>>>>>): Builder<E | {
|
|
147
|
+
register<T extends TokenBase, V extends Registration>(token: T & TokenTupleAdmission<readonly [T]> & IntroducesKeys<EntryKeys<E>, TokenKey<T>>, registration: V & Registration & BindingOutput<NoInfer<T>, NoInfer<V>> & ThenableAdmission<Record<TokenKey<T>, NoInfer<V>>> & IncrementalChecked<E, Record<TokenKey<T>, TokenBinding<NoInfer<T>, NoInfer<V>>>> & CheckedConstraints<C, OverrideRegistrations<RegistrationsFromEntries<E>, Record<TokenKey<T>, TokenBinding<NoInfer<T>, NoInfer<V>>>>>): Builder<E | {
|
|
140
148
|
key: TokenKey<T>;
|
|
141
149
|
registration: TokenBinding<T, V>;
|
|
142
150
|
}, C>;
|
|
@@ -180,6 +188,13 @@ declare class Builder<E extends Entry, C extends NeedConstraint = never> {
|
|
|
180
188
|
* @returns A new builder exposing only the module's selected exports.
|
|
181
189
|
*/
|
|
182
190
|
installModule<P extends object, R extends object, MC extends NeedConstraint, D extends Registrations>(module: Module<P, R, MC, D> & IntroducesKeys<EntryKeys<E>, keyof D> & IncrementalChecked<E, D> & IncrementalConstraints<C, MC, RegistrationsFromEntries<E>, D>): Builder<E | RegistrationEntries<D>, C | MC>;
|
|
191
|
+
/**
|
|
192
|
+
* Report at the type level why this graph would not build; the runtime call does nothing.
|
|
193
|
+
* Write `builder.verifyGraph() satisfies void;` so a rejected graph fails on that line with
|
|
194
|
+
* the complete message and details, instead of at the start of the builder expression.
|
|
195
|
+
* @returns `void` for a buildable graph; otherwise the failure that `build()` would report.
|
|
196
|
+
*/
|
|
197
|
+
verifyGraph<Self extends Builder<E, C>>(this: Self): CompositionReport<Self>;
|
|
183
198
|
/**
|
|
184
199
|
* Seal this graph as a reusable module and select its public names and typed tokens.
|
|
185
200
|
* Unselected registrations stay private to each installation; unmet dependencies
|
package/dist/di-bag.js
CHANGED
|
@@ -41,6 +41,13 @@ class Bag {
|
|
|
41
41
|
inspect(token) {
|
|
42
42
|
return this.#runtime.inspect(typeof token === 'string' ? token : (0, tokens_1.readTokenKey)(token));
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Describe every binding this bag can resolve and the dependency edges observed so far.
|
|
46
|
+
* Nothing is acquired. Named dependencies declared on factory parameters are not visible
|
|
47
|
+
* until the factory runs; the static graph tool reports them from source.
|
|
48
|
+
* @returns A frozen point-in-time snapshot; application-owned metadata payloads are not frozen.
|
|
49
|
+
*/
|
|
50
|
+
inspectGraph() { return this.#runtime.inspectGraph(); }
|
|
44
51
|
createScope(...args) {
|
|
45
52
|
this.#runtime.assertOpen();
|
|
46
53
|
const { graph, shared } = (0, scope_selection_1.selectScope)(this.#graph, args, key => this.#runtime.isTransient(key));
|
|
@@ -154,6 +161,7 @@ class Builder {
|
|
|
154
161
|
installModule(module) {
|
|
155
162
|
return new Builder(this.#graph.withInstallation((0, module_1.moduleGraph)(module)), this.context);
|
|
156
163
|
}
|
|
164
|
+
verifyGraph() { return undefined; }
|
|
157
165
|
/**
|
|
158
166
|
* Seal this graph as a reusable module and select its public names and typed tokens.
|
|
159
167
|
* Unselected registrations stay private to each installation; unmet dependencies
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { DiBagCleanupError, DiBagPluginValidationError, DiBagStartupError, DiBag
|
|
|
3
3
|
export type { CleanupFailure, DiBagErrorCode, DiBagDiagnostic } from './errors';
|
|
4
4
|
export type { Bag, Builder, DiBagApi, ConfigurationOptions } from './di-bag';
|
|
5
5
|
export type { Module } from './module';
|
|
6
|
+
export type { CompositionReport } from './composition-report';
|
|
6
7
|
export type { ModuleExportedServices, ModuleRequiredServices, ModuleConstraints, ModuleSealedConstraints, SealedConstraints, PublicProviders, ModulePublicProviders, Renamed } from './module-types';
|
|
7
8
|
export type { FactoryWithDisposal, Registration } from './registration';
|
|
8
9
|
export type { Provider, ProviderFactory, ProviderGraphContract, ProviderOutput, ProviderAcquiredValue, ProviderNamedDependencies, ProviderRegistrationMetadata, ProviderAcquisitionMetadata, ProviderRequiredTokens, ProviderOptionalTokens, ProviderCollectionTokens } from './provider';
|
|
@@ -14,8 +15,9 @@ export type { ScopeOptions, DisjointScopeSelection, UnsharedAliases, ScopedAlias
|
|
|
14
15
|
export type { Token, TokenBase, TokenKey, TokenService } from './tokens';
|
|
15
16
|
export type { TokenBinding, TokenMember, TokenDependencyContract, ReboundProviders, ReboundSelection, SelectionKey } from './token-types';
|
|
16
17
|
export type { CheckedLifetimes, CheckedScopeLifetimes, LexicalContext, ModuleScope, Enclosed, RenamedContext, RenamedLifetimeObligation, EnclosedLifetimeObligation, RenamedLifetimeProviders } from './lifetime-types';
|
|
18
|
+
export type { DiBagPolicy } from './types';
|
|
17
19
|
export type { CheckDependencyCompatibility, CheckDependencyCompleteness, RegistrationEntries, OverrideFactoryContext, RegistrationsFromEntries, OverrideRegistrations, Overrides, ServicesOf, SelectedRegistrations, Selection } from './types';
|
|
18
|
-
export type { Presence, AcquisitionMetadataPresence, AcquisitionSnapshot, RegistrationSnapshot } from './inspection';
|
|
20
|
+
export type { Presence, AcquisitionMetadataPresence, AcquisitionSnapshot, RegistrationSnapshot, BindingSnapshot, GraphSnapshot } from './inspection';
|
|
19
21
|
export type { CompositionArguments, CompositionFunction } from './composition';
|
|
20
22
|
export type { OptionalDependency, LazyDependency, CollectionDependency, DependencyReference } from './dependency-references';
|
|
21
23
|
export type { PluginProviderFactory, PluginAcquisitionMode, PluginOptions, PluginOutputValidator, PluginProvider } from './plugins';
|
package/dist/inspection.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AcquisitionMode } from './acquisition-mode';
|
|
2
|
+
import type { Lifetime } from './lifetime';
|
|
1
3
|
/** Structural optional presence; payloads are application-owned and not frozen. */
|
|
2
4
|
export type Presence<T> = {
|
|
3
5
|
readonly present: false;
|
|
@@ -34,3 +36,36 @@ export interface RegistrationSnapshot<M = Readonly<{}>, A extends readonly unkno
|
|
|
34
36
|
/** Point-in-time attempts; inspection does not retain failed-attempt history. */
|
|
35
37
|
readonly acquisitions: readonly AcquisitionSnapshot<A>[];
|
|
36
38
|
}
|
|
39
|
+
/** One binding of a bag's graph, described without acquiring it. */
|
|
40
|
+
export interface BindingSnapshot<M = Readonly<{}>, A extends readonly unknown[] = readonly []> extends RegistrationSnapshot<M, A> {
|
|
41
|
+
/** Public names or token symbols that select this binding, in registration order; empty for a private module binding. */
|
|
42
|
+
readonly keys: readonly (string | symbol)[];
|
|
43
|
+
readonly lifetime: Lifetime;
|
|
44
|
+
readonly acquisitionMode: AcquisitionMode;
|
|
45
|
+
/** True when some stage of the provider accepts ownership through a disposer. */
|
|
46
|
+
readonly owned: boolean;
|
|
47
|
+
/** Typed-token dependencies declared positionally through tokens, `optional`, `lazy`, or `all` references. */
|
|
48
|
+
readonly tokenDependencies: readonly {
|
|
49
|
+
readonly key: symbol;
|
|
50
|
+
readonly kind: 'required' | 'optional' | 'lazy' | 'all';
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A frozen description of every binding a bag can resolve, plus the edges observed so far.
|
|
55
|
+
* Named dependencies read from a factory's object parameter are not knowable until the factory
|
|
56
|
+
* runs; `observedEdges` records them after acquisition. Use the static graph tool for declared edges.
|
|
57
|
+
*/
|
|
58
|
+
export interface GraphSnapshot {
|
|
59
|
+
readonly scopeId: symbol;
|
|
60
|
+
/** Public bindings in registration order, then contributions in group order, then remaining private bindings. */
|
|
61
|
+
readonly bindings: readonly BindingSnapshot<object, readonly unknown[]>[];
|
|
62
|
+
readonly contributions: readonly {
|
|
63
|
+
readonly token: symbol;
|
|
64
|
+
readonly bindingIds: readonly symbol[];
|
|
65
|
+
}[];
|
|
66
|
+
/** Consumer-to-dependency edges recorded by acquisitions in this bag's ownership family. */
|
|
67
|
+
readonly observedEdges: readonly {
|
|
68
|
+
readonly from: symbol;
|
|
69
|
+
readonly to: symbol;
|
|
70
|
+
}[];
|
|
71
|
+
}
|
package/dist/lifetime-types.d.ts
CHANGED
|
@@ -3,8 +3,17 @@ import type { Registration, Registrations } from './registration';
|
|
|
3
3
|
import type { Provider, ProviderFactory, ProviderGraphContract, ProviderNamedDependencies, ProviderRequiredTokens, ProviderOptionalTokens, ProviderCollectionTokens, ProviderRegistrationMetadata, ProviderAcquisitionMetadata, ProviderAcquiredValue } from './provider';
|
|
4
4
|
import type { GraphContract } from './token-types';
|
|
5
5
|
import type { TokenKey } from './tokens';
|
|
6
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Unsatisfied } from './types';
|
|
6
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, NameText, Unsatisfied } from './types';
|
|
7
7
|
import type { CheckedConstraints, CompleteConstraints, NeedConstraint, Renamed } from './module-types';
|
|
8
|
+
type SiteText<S> = S extends {
|
|
9
|
+
readonly key: infer K;
|
|
10
|
+
} ? NameText<K> : S extends {
|
|
11
|
+
readonly kind: 'contribution';
|
|
12
|
+
} ? 'contribution' : never;
|
|
13
|
+
type CaptiveText<C> = C extends {
|
|
14
|
+
readonly root: infer R;
|
|
15
|
+
readonly dependency: infer D;
|
|
16
|
+
} ? `${SiteText<R>} -> ${SiteText<D>}` : never;
|
|
8
17
|
/**
|
|
9
18
|
* A module provider's retained local registrations and public-to-local export mapping.
|
|
10
19
|
* Contexts chain through `parent` when a module was sealed inside another module:
|
|
@@ -178,13 +187,13 @@ type OverrideCaptives<R extends Registrations, O extends Registrations, G> = {
|
|
|
178
187
|
/** Reject root providers introduced by a scope override when they capture scoped dependencies. */
|
|
179
188
|
export type CheckedScopeLifetimes<R extends Registrations, O extends Registrations, G = never> = [
|
|
180
189
|
OverrideCaptives<R, O, G>
|
|
181
|
-
] extends [never] ? unknown : Unsatisfied
|
|
190
|
+
] extends [never] ? unknown : Unsatisfied<`root lifetime cannot capture scoped dependency: ${CaptiveText<OverrideCaptives<R, O, G>>}`, {
|
|
182
191
|
readonly captives: OverrideCaptives<R, O, G>;
|
|
183
192
|
}>;
|
|
184
193
|
/** Reject strict root providers that transitively capture scoped dependencies. */
|
|
185
194
|
export type CheckedLifetimes<R extends Registrations, C extends NeedConstraint> = [
|
|
186
195
|
Captives<R, C>
|
|
187
|
-
] extends [never] ? unknown : unknown extends CheckDependencyCompatibility<R> & CheckDependencyCompleteness<R> & CheckedConstraints<C, R> & CompleteConstraints<C, R> ? Unsatisfied
|
|
196
|
+
] extends [never] ? unknown : unknown extends CheckDependencyCompatibility<R> & CheckDependencyCompleteness<R> & CheckedConstraints<C, R> & CompleteConstraints<C, R> ? Unsatisfied<`root lifetime cannot capture scoped dependency: ${CaptiveText<Captives<R, C>>}`, {
|
|
188
197
|
readonly captives: Captives<R, C>;
|
|
189
198
|
}> : unknown;
|
|
190
199
|
type PolicyEnclosing<H extends Registrations, C, K, Visited> = C extends {
|
package/dist/module-types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ContributionConstraint, CheckedContributions, CompleteContributions, RenamedContribution, ModuleContributionConstraints } from './contribution-types';
|
|
2
2
|
import type { Module } from './module';
|
|
3
3
|
import type { Registrations } from './registration';
|
|
4
|
-
import type { Entry, Needs, RegistrationsFromEntries, ServicesOf, Singleton, Unsatisfied } from './types';
|
|
4
|
+
import type { Entry, NameText, Needs, RegistrationsFromEntries, ServicesOf, Singleton, Unsatisfied } from './types';
|
|
5
5
|
import type { MetadataKeyUnion, Provider, ProviderOutput, ProviderNamedDependencies, ProviderRegistrationMetadata, ProviderAcquisitionMetadata, ProviderAcquiredValue, ProviderGraphContract, ProviderRequiredTokens, ProviderOptionalTokens, ProviderCollectionTokens, BoundToken } from './provider';
|
|
6
6
|
import type { TokenDependencyContract, WrongToken, MissingToken } from './token-types';
|
|
7
7
|
import type { TokenBase, TokenKey, TokenService } from './tokens';
|
|
@@ -76,7 +76,7 @@ export type IncrementalConstraints<C extends NeedConstraint, MC extends NeedCons
|
|
|
76
76
|
}>] extends [never] ? unknown extends CheckedConstraints<C, Incoming> ? CheckedConstraints<MC, import('./types').OverrideRegistrations<Old, Incoming>> : CheckedConstraints<C, Incoming> : CheckedConstraints<C | MC, import('./types').OverrideRegistrations<Old, Incoming>>;
|
|
77
77
|
export type CompleteConstraints<C extends NeedConstraint, A extends Registrations> = [
|
|
78
78
|
MissingConstraint<C, ServicesOf<A>> | MissingTokenConstraint<C, A>
|
|
79
|
-
] extends [never] ? CompleteContributions<C, A> : Unsatisfied
|
|
79
|
+
] extends [never] ? CompleteContributions<C, A> : Unsatisfied<`required service registrations are missing: ${NameText<MissingConstraint<C, ServicesOf<A>> | MissingTokenConstraint<C, A>>}`, {
|
|
80
80
|
missing: MissingConstraint<C, ServicesOf<A>> | MissingTokenConstraint<C, A>;
|
|
81
81
|
relationships: MissingConstraintRelationships<C, ServicesOf<A>>;
|
|
82
82
|
}>;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ScopeAcquisitions } from './acquisition';
|
|
2
2
|
import { normalize } from './registration';
|
|
3
3
|
import type { Registration, Registrations } from './registration';
|
|
4
|
-
import type { RegistrationSnapshot } from './inspection';
|
|
4
|
+
import type { GraphSnapshot, RegistrationSnapshot } from './inspection';
|
|
5
5
|
import type { RuntimeContext } from './acquisition-mode';
|
|
6
6
|
export type BindingId = symbol;
|
|
7
7
|
export type BindingKey = string | symbol;
|
|
@@ -60,6 +60,16 @@ export declare class BindingGraph {
|
|
|
60
60
|
* is kept to produce this order.
|
|
61
61
|
*/
|
|
62
62
|
describe(): GraphDescription;
|
|
63
|
+
/** Every retained binding in `describe()` order, with the public keys that select it. */
|
|
64
|
+
bindingSummaries(): readonly {
|
|
65
|
+
readonly id: BindingId;
|
|
66
|
+
readonly keys: readonly BindingKey[];
|
|
67
|
+
}[];
|
|
68
|
+
/** Every contribution group with its member bindings in contribution order. */
|
|
69
|
+
contributionGroups(): readonly {
|
|
70
|
+
readonly token: symbol;
|
|
71
|
+
readonly bindingIds: readonly BindingId[];
|
|
72
|
+
}[];
|
|
63
73
|
/** Install disjoint public slots atomically, retaining lexical private refs. */
|
|
64
74
|
withInstallation(description: GraphDescription): BindingGraph;
|
|
65
75
|
}
|
|
@@ -80,6 +90,7 @@ export declare class BagRuntime {
|
|
|
80
90
|
acquire(key: BindingKey): Promise<void>;
|
|
81
91
|
isTransient(key: BindingKey): boolean;
|
|
82
92
|
inspect(key: BindingKey): RegistrationSnapshot<object, readonly unknown[]>;
|
|
93
|
+
inspectGraph(): GraphSnapshot;
|
|
83
94
|
private inspectBinding;
|
|
84
95
|
assertOpen(): void;
|
|
85
96
|
scope(graph?: BindingGraph, shared?: readonly BindingId[]): BagRuntime;
|
package/dist/runtime.js
CHANGED
|
@@ -267,6 +267,28 @@ class BindingGraph {
|
|
|
267
267
|
publicSlots.set(key, id);
|
|
268
268
|
return { bindings, publicSlots, contributions };
|
|
269
269
|
}
|
|
270
|
+
/** Every retained binding in `describe()` order, with the public keys that select it. */
|
|
271
|
+
bindingSummaries() {
|
|
272
|
+
const keysById = new Map();
|
|
273
|
+
if (this.#publicOrder)
|
|
274
|
+
for (const key of (0, persistent_sequence_1.materialize)(this.#publicOrder)) {
|
|
275
|
+
const id = this.#publicSlots.get(key);
|
|
276
|
+
if (id === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
const keys = keysById.get(id) ?? [];
|
|
279
|
+
if (!keys.includes(key))
|
|
280
|
+
keys.push(key);
|
|
281
|
+
keysById.set(id, keys);
|
|
282
|
+
}
|
|
283
|
+
return Object.freeze([...this.describe().bindings.keys()].map(id => Object.freeze({ id, keys: Object.freeze(keysById.get(id) ?? []) })));
|
|
284
|
+
}
|
|
285
|
+
/** Every contribution group with its member bindings in contribution order. */
|
|
286
|
+
contributionGroups() {
|
|
287
|
+
const groups = [];
|
|
288
|
+
for (const [key] of this.#contributions)
|
|
289
|
+
groups.push(Object.freeze({ token: key, bindingIds: this.contributionBindings(key) }));
|
|
290
|
+
return Object.freeze(groups);
|
|
291
|
+
}
|
|
270
292
|
/** Install disjoint public slots atomically, retaining lexical private refs. */
|
|
271
293
|
withInstallation(description) {
|
|
272
294
|
for (const key of description.publicSlots.keys()) {
|
|
@@ -338,6 +360,25 @@ class BagRuntime {
|
|
|
338
360
|
inspect(key) {
|
|
339
361
|
return this.inspectBinding(this.graph.publicBinding(key));
|
|
340
362
|
}
|
|
363
|
+
inspectGraph() {
|
|
364
|
+
const bindings = this.graph.bindingSummaries().map(({ id, keys }) => {
|
|
365
|
+
const description = this.graph.registration(id);
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
...this.inspectBinding(id),
|
|
368
|
+
keys,
|
|
369
|
+
lifetime: description.lifetime.kind,
|
|
370
|
+
acquisitionMode: description.acquisitionMode,
|
|
371
|
+
owned: description.dispose !== undefined || description.operations.some(operation => operation.kind === 'owned'),
|
|
372
|
+
tokenDependencies: Object.freeze(description.references.map(reference => Object.freeze({ key: reference.key, kind: reference.kind }))),
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
return Object.freeze({
|
|
376
|
+
scopeId: this.acquisitions.ownerId,
|
|
377
|
+
bindings: Object.freeze(bindings),
|
|
378
|
+
contributions: this.graph.contributionGroups(),
|
|
379
|
+
observedEdges: this.acquisitions.observedEdges(),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
341
382
|
inspectBinding(bindingId) {
|
|
342
383
|
return Object.freeze({
|
|
343
384
|
bindingId,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FactoryWithDisposal, Registration, Registrations } from './registration';
|
|
1
|
+
import type { Factory, FactoryWithDisposal, Registration, Registrations } from './registration';
|
|
2
2
|
import type { ProviderContext, ProviderNamedDependencies, ProviderOutput, ProviderGraphContract, ProviderRequiredTokens, ProviderOptionalTokens } from './provider';
|
|
3
3
|
import type { InvalidGraphs, MissingTokens, SelectionKey, TokenMember, ValidToken, TokenDependencyContract, WrongToken } from './token-types';
|
|
4
4
|
export type Needs<R extends Registration> = ProviderNamedDependencies<R>;
|
|
@@ -24,6 +24,33 @@ export type RegistrationsFromEntries<E extends Entry> = {
|
|
|
24
24
|
};
|
|
25
25
|
/** Replace overlapping registrations in `F` with registrations from `N`. */
|
|
26
26
|
export type OverrideRegistrations<F extends Registrations, N extends Registrations> = Omit<F, keyof N> & N;
|
|
27
|
+
/**
|
|
28
|
+
* Project-wide compile-time policy switches. Augment it to relax a check:
|
|
29
|
+
* `declare module 'di-bag' { interface DiBagPolicy { readonly structuralThenables: 'allow' } }`.
|
|
30
|
+
*/
|
|
31
|
+
export interface DiBagPolicy {
|
|
32
|
+
}
|
|
33
|
+
type StructuralThenablesAllowed = DiBagPolicy extends {
|
|
34
|
+
readonly structuralThenables: 'allow';
|
|
35
|
+
} ? true : false;
|
|
36
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
37
|
+
/** True for a declared output with a callable `then` that is not a native Promise; `any` is exempt. */
|
|
38
|
+
export type StructuralThenable<O> = StructuralThenablesAllowed extends true ? false : IsAny<O> extends true ? false : O extends infer T & {} ? T extends Promise<unknown> ? false : T extends {
|
|
39
|
+
then(...args: never[]): unknown;
|
|
40
|
+
} ? true : false : false;
|
|
41
|
+
type ThenableOutputs<R extends Registrations> = {
|
|
42
|
+
[K in keyof R]: R[K] extends Factory | FactoryWithDisposal<Factory> ? true extends StructuralThenable<ProviderOutput<R[K]>> ? K : never : never;
|
|
43
|
+
}[keyof R];
|
|
44
|
+
/** Reject plain or disposable factories whose declared output auto acquisition would reject at runtime. */
|
|
45
|
+
export type ThenableAdmission<R extends Registrations> = [ThenableOutputs<R>] extends [never] ? unknown : Unsatisfied<`factory output is a structural thenable: ${NameText<ThenableOutputs<R>>}; return a native Promise or use DiBag.fromFactory with acquisitionMode raw or nativePromise`, {
|
|
46
|
+
tokens: ThenableOutputs<R>;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Render dependency names inside diagnostic messages; typed tokens have no printable name.
|
|
50
|
+
* Use it only in checks that run once per graph (build, module completeness, lifetimes, key selection):
|
|
51
|
+
* per-call wrong-shape checks stay plain because templates there cost instantiations on valid graphs.
|
|
52
|
+
*/
|
|
53
|
+
export type NameText<K> = K extends string ? K : K extends number ? `${K}` : 'typed token';
|
|
27
54
|
declare const diBagTypeError: unique symbol;
|
|
28
55
|
export type Unsatisfied<Message extends string, Details> = {
|
|
29
56
|
readonly [diBagTypeError]: Message;
|
|
@@ -107,7 +134,7 @@ export type CheckDependencyCompleteness<R extends Registrations> = [
|
|
|
107
134
|
Exclude<RequiredOf<R>, keyof R> | MissingTokens<CompletionMap<R>>
|
|
108
135
|
] extends [never] ? [InvalidGraphs<CompletionMap<R>>] extends [never] ? unknown : Unsatisfied<'token dependency has an incompatible or opaque contract', {
|
|
109
136
|
tokens: InvalidGraphs<CompletionMap<R>>;
|
|
110
|
-
}> : Unsatisfied
|
|
137
|
+
}> : Unsatisfied<`required service registrations are missing: ${NameText<Exclude<RequiredOf<R>, keyof R> | MissingTokens<CompletionMap<R>>>}`, {
|
|
111
138
|
missing: Exclude<RequiredOf<R>, keyof R> | MissingTokens<CompletionMap<R>>;
|
|
112
139
|
relationships: MissingRelationships<R>;
|
|
113
140
|
}>;
|
|
@@ -117,9 +144,9 @@ type BadOverrides<F extends Registrations, O extends Registrations> = {
|
|
|
117
144
|
/** Admit overrides only for existing keys whose service values remain assignable. */
|
|
118
145
|
export type Overrides<F extends Registrations, O extends Registrations> = [
|
|
119
146
|
Exclude<keyof O, keyof F>
|
|
120
|
-
] extends [never] ? [BadOverrides<F, O>] extends [never] ? unknown : Unsatisfied
|
|
147
|
+
] extends [never] ? [BadOverrides<F, O>] extends [never] ? unknown : Unsatisfied<`override value is not assignable to the original token: ${NameText<BadOverrides<F, O>>}`, {
|
|
121
148
|
tokens: BadOverrides<F, O>;
|
|
122
|
-
}> : Unsatisfied
|
|
149
|
+
}> : Unsatisfied<`fork accepts existing names or typed tokens only: unknown ${NameText<Exclude<keyof O, keyof F>>}`, {
|
|
123
150
|
extra: Exclude<keyof O, keyof F>;
|
|
124
151
|
}>;
|
|
125
152
|
export type Introduces<F extends Registrations, N extends Registrations> = [
|
|
@@ -132,14 +159,14 @@ export type IntroducesKeys<Known extends PropertyKey, New extends PropertyKey> =
|
|
|
132
159
|
duplicates: Known & New;
|
|
133
160
|
}>;
|
|
134
161
|
export type Singleton<K> = [K] extends [never] ? false : [K] extends [string] ? true extends IsUnion<K> ? false : [NonFiniteKeys<Record<K & string, never>>] extends [never] ? true : false : false;
|
|
135
|
-
export type ReplacementKey<R extends Registrations, K extends string> = Singleton<K> extends true ? K extends keyof R ? unknown : Unsatisfied
|
|
162
|
+
export type ReplacementKey<R extends Registrations, K extends string> = Singleton<K> extends true ? K extends keyof R ? unknown : Unsatisfied<`replace requires one existing singleton string-literal key: ${NameText<K>}`, {
|
|
136
163
|
key: K;
|
|
137
|
-
}> : Unsatisfied
|
|
164
|
+
}> : Unsatisfied<`replace requires one existing singleton string-literal key: ${NameText<K>}`, {
|
|
138
165
|
key: K;
|
|
139
166
|
}>;
|
|
140
|
-
export type ReplacementKeyOf<Keys extends PropertyKey, K extends string> = Singleton<K> extends true ? K extends Keys ? unknown : Unsatisfied
|
|
167
|
+
export type ReplacementKeyOf<Keys extends PropertyKey, K extends string> = Singleton<K> extends true ? K extends Keys ? unknown : Unsatisfied<`replace requires one existing singleton string-literal key: ${NameText<K>}`, {
|
|
141
168
|
key: K;
|
|
142
|
-
}> : Unsatisfied
|
|
169
|
+
}> : Unsatisfied<`replace requires one existing singleton string-literal key: ${NameText<K>}`, {
|
|
143
170
|
key: K;
|
|
144
171
|
}>;
|
|
145
172
|
type ReplacementRequirement<N, K extends PropertyKey> = K extends keyof N ? (value: N[K]) => void : never;
|
|
@@ -157,7 +184,7 @@ type InvalidElements<K extends readonly unknown[]> = {
|
|
|
157
184
|
}[number];
|
|
158
185
|
type InvalidMembers<R extends Registrations, T> = T extends string ? never : unknown extends TokenMember<R, T> ? never : T;
|
|
159
186
|
/** Validate a finite tuple of existing singleton names or genuine typed tokens. */
|
|
160
|
-
export type Selection<R extends Registrations, K extends readonly unknown[], Operation extends string = 'fork'> = true extends IsUnion<K> ? InvalidSelection<Operation> : number extends K['length'] ? InvalidSelection<Operation> : K extends Required<K> ? [InvalidElements<K>] extends [never] ? [Exclude<SelectionKey<K[number]>, keyof R> | InvalidMembers<R, K[number]>] extends [never] ? unknown : Unsatisfied<`${Operation} accepts existing names or typed tokens only`, {
|
|
187
|
+
export type Selection<R extends Registrations, K extends readonly unknown[], Operation extends string = 'fork'> = true extends IsUnion<K> ? InvalidSelection<Operation> : number extends K['length'] ? InvalidSelection<Operation> : K extends Required<K> ? [InvalidElements<K>] extends [never] ? [Exclude<SelectionKey<K[number]>, keyof R> | InvalidMembers<R, K[number]>] extends [never] ? unknown : Unsatisfied<`${Operation} accepts existing names or typed tokens only: unknown ${NameText<Exclude<SelectionKey<K[number]>, keyof R> | InvalidMembers<R, K[number]>>}`, {
|
|
161
188
|
extra: Exclude<SelectionKey<K[number]>, keyof R> | InvalidMembers<R, K[number]>;
|
|
162
189
|
}> : InvalidSelection<Operation> : InvalidSelection<Operation>;
|
|
163
190
|
type InvalidSelection<Operation extends string> = Unsatisfied<`${Operation} requires a finite tuple of singleton string-literal names or typed tokens`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "di-bag",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Type-checked dependency composition, private modules, and resource ownership for TypeScript apps and LLM harnesses.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "npm run test:fast && npm run test:compiler",
|
|
22
|
+
"test:fast": "node scripts/test-lane.mjs fast",
|
|
23
|
+
"test:compiler": "node scripts/test-lane.mjs compiler",
|
|
22
24
|
"typecheck": "tsc6 -p tsconfig.json",
|
|
23
25
|
"typecheck:native": "tsc -p tsconfig.json",
|
|
24
26
|
"build:native": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
"check:platform": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/platform-evidence.ts --required",
|
|
34
36
|
"docs:generate": "node tools/docs/generate.mjs",
|
|
35
37
|
"docs:check": "node --test --test-isolation=none tools/docs/test/*.test.mjs && node tools/docs/generate.mjs --check && node tools/docs/site.mjs check",
|
|
38
|
+
"graph:check": "node --test tools/graph/test/*.test.mjs",
|
|
36
39
|
"docs:build": "node tools/docs/site.mjs build",
|
|
37
40
|
"docs:dev": "node tools/docs/site.mjs dev",
|
|
38
41
|
"docs:preview": "node tools/docs/site.mjs preview",
|