di-bag 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +150 -0
- package/README.md +68 -51
- package/dist/acquisition-context.d.ts +80 -5
- package/dist/acquisition-context.js +30 -2
- package/dist/acquisition-mode.d.ts +17 -4
- package/dist/acquisition-mode.js +33 -7
- package/dist/acquisition.d.ts +13 -2
- package/dist/acquisition.js +44 -8
- package/dist/alias-types.d.ts +12 -3
- package/dist/composition-report.d.ts +3 -2
- package/dist/composition.d.ts +8 -2
- package/dist/contribution-types.d.ts +26 -18
- package/dist/dependency-references.d.ts +16 -4
- package/dist/di-bag.d.ts +344 -42
- package/dist/di-bag.js +87 -15
- package/dist/errors.d.ts +115 -8
- package/dist/errors.js +113 -12
- package/dist/index.d.ts +6 -6
- package/dist/index.js +2 -1
- package/dist/inspection.d.ts +21 -5
- package/dist/lifetime-types.d.ts +281 -180
- package/dist/lifetime.d.ts +4 -1
- package/dist/module-types.d.ts +87 -30
- package/dist/module.d.ts +18 -4
- package/dist/module.js +31 -7
- package/dist/observers.d.ts +25 -6
- package/dist/plugins.d.ts +17 -4
- package/dist/provider-execution.d.ts +38 -2
- package/dist/provider-execution.js +145 -17
- package/dist/provider.d.ts +41 -10
- package/dist/provider.js +1 -0
- package/dist/registration.d.ts +8 -2
- package/dist/registration.js +4 -1
- package/dist/runtime.d.ts +13 -3
- package/dist/runtime.js +37 -9
- package/dist/scope-types.d.ts +20 -5
- package/dist/startup.d.ts +19 -1
- package/dist/startup.js +81 -11
- package/dist/token-types.d.ts +26 -8
- package/dist/tokens.d.ts +10 -2
- package/dist/tokens.js +2 -0
- package/dist/types.d.ts +69 -23
- package/docs/agent/api-card.md +354 -0
- package/docs/agent/errors.md +1138 -0
- package/docs/agent/recipes.md +403 -0
- package/package.json +18 -5
package/dist/acquisition.js
CHANGED
|
@@ -5,6 +5,19 @@ const errors_1 = require("./errors");
|
|
|
5
5
|
const errors_2 = require("./errors");
|
|
6
6
|
const provider_execution_1 = require("./provider-execution");
|
|
7
7
|
const acquisition_family_1 = require("./acquisition-family");
|
|
8
|
+
/**
|
|
9
|
+
* The reason a close() without a cause aborts with. Built once at load: an error
|
|
10
|
+
* created inside close() keeps an unformatted stack whose frames retain the
|
|
11
|
+
* closing callbacks, and through them the scope and its graph, on every signal an
|
|
12
|
+
* application kept after the bag closed. It stays a plain `AbortError`, so its
|
|
13
|
+
* legacy numeric `code` is what an automatic abort reason had; the message names
|
|
14
|
+
* the diagnostic.
|
|
15
|
+
*/
|
|
16
|
+
const closingReason = (() => {
|
|
17
|
+
const reason = new DOMException((0, errors_1.diagnosticMessage)('DI_BAG_CLOSING', 'bag is closing'), 'AbortError');
|
|
18
|
+
void reason.stack; // format the load-time frames now, so nothing is retained lazily
|
|
19
|
+
return Object.freeze(reason);
|
|
20
|
+
})();
|
|
8
21
|
/** Mutable, runtime-local attempts. Binding descriptions never carry ownership. */
|
|
9
22
|
class ScopeAcquisitions {
|
|
10
23
|
graph;
|
|
@@ -21,7 +34,6 @@ class ScopeAcquisitions {
|
|
|
21
34
|
state = 'open';
|
|
22
35
|
closing;
|
|
23
36
|
controller;
|
|
24
|
-
acquisitionContext;
|
|
25
37
|
cancellationStarted = false;
|
|
26
38
|
cancellationCause;
|
|
27
39
|
shared;
|
|
@@ -132,20 +144,43 @@ class ScopeAcquisitions {
|
|
|
132
144
|
this.closing = Promise.resolve().then(() => {
|
|
133
145
|
// Every descendant admission gate is closed before abort listeners run.
|
|
134
146
|
this.cancellationStarted = true;
|
|
135
|
-
this.cancellationCause = cause;
|
|
136
|
-
this.controller?.abort(
|
|
147
|
+
this.cancellationCause = cause === undefined ? closingReason : cause;
|
|
148
|
+
this.controller?.abort(this.cancellationCause);
|
|
137
149
|
return this.disposeAll(beforeDispose);
|
|
138
150
|
});
|
|
139
151
|
return this.closing;
|
|
140
152
|
}
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
/** Labels of this scope's running disposers and of acquisitions close is still draining. */
|
|
154
|
+
collectProgress(pending, acquiring) {
|
|
155
|
+
for (const attempt of this.attempts.values()) {
|
|
156
|
+
if (attempt.state === 'disposing' || this.retired.has(attempt.id) || attempt.execution.rollingBack)
|
|
157
|
+
pending.push(attempt.label);
|
|
158
|
+
else if (attempt.state === 'creating' || attempt.state === 'pending')
|
|
159
|
+
acquiring.push(attempt.label);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** One controller per scope; every acquisition observes the same cancellation. */
|
|
163
|
+
cancellationSignal() {
|
|
164
|
+
if (!this.controller) {
|
|
143
165
|
this.controller = new AbortController();
|
|
144
166
|
if (this.cancellationStarted)
|
|
145
167
|
this.controller.abort(this.cancellationCause);
|
|
146
|
-
this.acquisitionContext = Object.freeze({ signal: this.controller.signal });
|
|
147
168
|
}
|
|
148
|
-
return this.
|
|
169
|
+
return this.controller.signal;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The signal is scope-wide; deferred cleanup is local to this attempt, so each
|
|
173
|
+
* contextual acquisition receives its own frozen context. The context captures
|
|
174
|
+
* only its disposer stack, never the execution or this scope, so an
|
|
175
|
+
* application that retains it past `close()` retains nothing else. Built here
|
|
176
|
+
* rather than in `resolveBinding` for the same reason: every closure of a
|
|
177
|
+
* function shares one scope, and a factory can retain the dependency proxy.
|
|
178
|
+
*/
|
|
179
|
+
acquisitionContext(disposers) {
|
|
180
|
+
return Object.freeze({
|
|
181
|
+
signal: this.cancellationSignal(),
|
|
182
|
+
pushDisposer: (disposer) => { disposers.push(disposer); },
|
|
183
|
+
});
|
|
149
184
|
}
|
|
150
185
|
resolveBinding(bindingId, from, path = []) {
|
|
151
186
|
// Sharing an alias borrows its lexical parent graph before following targets.
|
|
@@ -262,6 +297,7 @@ class ScopeAcquisitions {
|
|
|
262
297
|
this.observeAttempt(attempt, 'acquisition-started');
|
|
263
298
|
this.family.enter(attempt);
|
|
264
299
|
const directSource = !description.contextual && !description.operations.length;
|
|
300
|
+
const { disposers } = execution;
|
|
265
301
|
try {
|
|
266
302
|
let value;
|
|
267
303
|
if (directSource) {
|
|
@@ -270,7 +306,7 @@ class ScopeAcquisitions {
|
|
|
270
306
|
execution.publishSource(value, description);
|
|
271
307
|
}
|
|
272
308
|
else {
|
|
273
|
-
value = execution.evaluate(description, deps,
|
|
309
|
+
value = execution.evaluate(description, deps, disposers && this.acquisitionContext(disposers));
|
|
274
310
|
}
|
|
275
311
|
attempt.exposed = value;
|
|
276
312
|
attempt.state = attempt.execution.state;
|
package/dist/alias-types.d.ts
CHANGED
|
@@ -6,14 +6,23 @@ import type { Singleton, Unsatisfied } from './types';
|
|
|
6
6
|
export type AliasSelection = string | TokenBase;
|
|
7
7
|
export type AliasAdmission<T> = Singleton<T> extends true ? unknown : ValidToken<T> extends true ? unknown : Unsatisfied<'alias requires one singleton name or genuine token', {}>;
|
|
8
8
|
export type AliasTarget<R extends Registrations, T> = T extends string ? T extends keyof R ? unknown : Unsatisfied<'alias requires an existing named target', {}> : [WrongToken<T, R>] extends [never] ? unknown : Unsatisfied<'token dependency has an incompatible or opaque contract', {}>;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the service type exposed by a possible alias target.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
12
|
+
*/
|
|
10
13
|
export type AliasOutput<R extends Registrations, T> = T extends string ? T extends keyof R ? ProviderOutput<R[T]> : never : TokenService<T>;
|
|
11
14
|
export type AliasDestination<R extends Registrations, D, T> = D extends TokenBase ? [AliasOutput<R, T>] extends [TokenService<D>] ? unknown : Unsatisfied<'alias output is not assignable to destination service', {}> : unknown;
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A provider contract that forwards a destination to a canonical target acquisition.
|
|
17
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
18
|
+
*/
|
|
13
19
|
export type AliasRegistration<R extends Registrations, D, T> = Provider<(this: void, deps: T extends string ? Record<T, AliasOutput<R, T>> : Record<never, never>) => AliasOutput<R, T>, Readonly<object>, readonly unknown[], TokenDependencyContract<T extends TokenBase ? readonly [T] : readonly [], D extends TokenBase ? D : never> & {
|
|
14
20
|
readonly alias: SelectionKey<T>;
|
|
15
21
|
}, unknown>;
|
|
16
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The single registration-map entry introduced by an alias operation.
|
|
24
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#give-a-dependency-another-lookup-name
|
|
25
|
+
*/
|
|
17
26
|
export type AliasEntries<R extends Registrations, D, T> = Record<SelectionKey<D>, AliasRegistration<R, D, T>>;
|
|
18
27
|
/** Reflected generic methods cannot introduce a checked singleton destination. */
|
|
19
28
|
export type AliasEntry<R extends Registrations, D, T> = unknown extends AliasAdmission<D> & AliasAdmission<T> ? {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Builder } from './di-bag';
|
|
2
2
|
import type { CheckedLifetimes } from './lifetime-types';
|
|
3
3
|
import type { CheckedConstraints, CompleteConstraints, NeedConstraint } from './module-types';
|
|
4
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Entry, RegistrationsFromEntries } from './types';
|
|
4
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, ConsumerReport, Entry, RegistrationsFromEntries } from './types';
|
|
5
5
|
type ReportOf<Check> = unknown extends Check ? never : Check;
|
|
6
|
-
type Reports<E extends Entry, C extends NeedConstraint> = ReportOf<CheckDependencyCompatibility<RegistrationsFromEntries<E
|
|
6
|
+
type Reports<E extends Entry, C extends NeedConstraint> = ReportOf<ConsumerReport<CheckDependencyCompatibility<RegistrationsFromEntries<E>>>> | ReportOf<CheckDependencyCompleteness<RegistrationsFromEntries<E>>> | ReportOf<ConsumerReport<CheckedConstraints<C, RegistrationsFromEntries<E>>>> | ReportOf<CompleteConstraints<C, RegistrationsFromEntries<E>>> | ReportOf<CheckedLifetimes<RegistrationsFromEntries<E>, C>>;
|
|
7
7
|
/**
|
|
8
8
|
* The compile-time verdict for a builder: `void` when `build()` would be accepted,
|
|
9
9
|
* otherwise the same failure `build()` reports, including its details.
|
|
10
10
|
* Read it through `builder.verifyGraph() satisfies void;` or as `CompositionReport<typeof builder>`.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#read-compile-time-rejections
|
|
11
12
|
*/
|
|
12
13
|
export type CompositionReport<B> = B extends Builder<infer E, infer C> ? [Reports<E, C>] extends [never] ? void : Reports<E, C> : never;
|
|
13
14
|
export {};
|
package/dist/composition.d.ts
CHANGED
|
@@ -4,12 +4,18 @@ import type { DependencyReference } from './dependency-references';
|
|
|
4
4
|
import type { TokenArguments, ReferenceGraph, DependencyTupleAdmission } from './token-types';
|
|
5
5
|
import type { Unsatisfied } from './types';
|
|
6
6
|
type OutputFactory<O> = () => O;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Compile-time admission that checks supplied token values against a callable's parameter tuple.
|
|
9
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#adapt-classes-and-positional-functions
|
|
10
|
+
*/
|
|
8
11
|
export type CompositionArguments<A extends readonly unknown[], P extends readonly unknown[]> = [A] extends [P] ? unknown : Unsatisfied<'composition arguments must match the declared parameter tuple', {
|
|
9
12
|
supplied: A;
|
|
10
13
|
parameters: P;
|
|
11
14
|
}>;
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A receiver-free positional callback matching the values supplied by a dependency tuple.
|
|
17
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#adapt-classes-and-positional-functions
|
|
18
|
+
*/
|
|
13
19
|
export type CompositionFunction<T extends readonly DependencyReference[], O = unknown> = TokenArguments<T> extends [...infer A] ? (this: void, ...args: A) => O : never;
|
|
14
20
|
/**
|
|
15
21
|
* Adapt a positional function without awaiting its arguments or return value.
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import type { Registration, Registrations } from './registration';
|
|
2
2
|
import type { TokenBase, TokenKey, TokenService } from './tokens';
|
|
3
3
|
import type { ValidToken, TokenTupleAdmission, BindingOutput } from './token-types';
|
|
4
|
-
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, Unsatisfied, Entry, RegistrationsFromEntries } from './types';
|
|
4
|
+
import type { CheckDependencyCompatibility, CheckDependencyCompleteness, SeeErrors, Unsatisfied, Entry, RegistrationsFromEntries } from './types';
|
|
5
5
|
import type { ProviderCollectionTokens } from './provider';
|
|
6
6
|
import type { Module } from './module';
|
|
7
7
|
import type { RegistrationConstraints, PublicProvider, NeedConstraint, CheckedConstraints } from './module-types';
|
|
8
|
-
import type { LexicalContext, ModuleScope, Enclosed, RenamedContext } from './lifetime-types';
|
|
9
8
|
declare const contributionSite: unique symbol;
|
|
10
|
-
/**
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Each union member retains one independently checked provider and its group.
|
|
11
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
12
|
+
*/
|
|
13
|
+
export type Contribution<T extends TokenBase = TokenBase, V extends Registration = Registration> = {
|
|
12
14
|
readonly kind: 'contribution';
|
|
13
15
|
readonly token: T;
|
|
14
16
|
readonly registration: V;
|
|
15
|
-
readonly context: L;
|
|
16
17
|
};
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The erased contribution contract retained by checked builders and modules.
|
|
20
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
21
|
+
*/
|
|
22
|
+
export type ContributionConstraint = Contribution<TokenBase, Registration>;
|
|
19
23
|
type Groups<C> = Extract<C, ContributionConstraint>;
|
|
20
24
|
type Same<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
|
|
21
25
|
type WrongMember<T, G> = G extends ContributionConstraint ? TokenKey<T> extends TokenKey<G['token']> ? Same<T, G['token']> extends true ? never : TokenKey<T> : never : never;
|
|
@@ -33,25 +37,29 @@ type AllNeeds<C, A extends Registrations> = ProviderCollectionTokens<A[keyof A]>
|
|
|
33
37
|
token: infer T;
|
|
34
38
|
} ? T : never);
|
|
35
39
|
type GroupErrors<C, A extends Registrations> = WrongGroup<Groups<C>['token'] | AllNeeds<C, A>, C>;
|
|
36
|
-
export type CheckedContributions<C, A extends Registrations> = [Groups<C>] extends [never] ? unknown : [GroupErrors<C, A>] extends [never] ? [WrongProvider<C, A>] extends [never] ? unknown : Unsatisfied
|
|
40
|
+
export type CheckedContributions<C, A extends Registrations> = [Groups<C>] extends [never] ? unknown : [GroupErrors<C, A>] extends [never] ? [WrongProvider<C, A>] extends [never] ? unknown : Unsatisfied<`contribution service is incompatible with its consumer dependency contract${SeeErrors<'unsatisfied-consumer'>}`, {
|
|
37
41
|
readonly failures: ContributionFailures<WrongProvider<C, A>, A>;
|
|
38
42
|
}> : Unsatisfied<'collection token has an incompatible or opaque contract', {}>;
|
|
39
|
-
export type CompleteContributions<C, A extends Registrations> = [MissingProvider<C, A>] extends [never] ? unknown : Unsatisfied
|
|
43
|
+
export type CompleteContributions<C, A extends Registrations> = [MissingProvider<C, A>] extends [never] ? unknown : Unsatisfied<`required service registrations are missing${SeeErrors<'missing-service'>}`, {
|
|
40
44
|
readonly contributions: MissingProvider<C, A>;
|
|
41
45
|
}>;
|
|
42
46
|
/**
|
|
43
|
-
* Retain a contribution's provider
|
|
44
|
-
*
|
|
45
|
-
*
|
|
47
|
+
* Retain a contribution's projected provider and its checked needs when its builder seals.
|
|
48
|
+
* Lifetime reach is retained separately as compact obligations. A contribution retained
|
|
49
|
+
* from an inner installation is already projected and has no needs left to re-scope.
|
|
50
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
51
|
+
*/
|
|
52
|
+
export type ModuleContributionConstraints<C, R extends Registrations, P extends keyof R> = C extends ContributionConstraint ? Contribution<C['token'], PublicProvider<C['registration']>> | RegistrationConstraints<C['registration'], R, P> : never;
|
|
53
|
+
/**
|
|
54
|
+
* Project a module's typed-token collections as readonly service arrays.
|
|
55
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
46
56
|
*/
|
|
47
|
-
export type ModuleContributionConstraints<C, R extends Registrations, P extends keyof R> = C extends ContributionConstraint ? C['context'] extends LexicalContext ? Contribution<C['token'], C['registration'], Enclosed<C['context'], ModuleScope<R, P>>> : Contribution<C['token'], PublicProvider<C['registration']>, ModuleScope<R, P> & {
|
|
48
|
-
readonly registration: C['registration'];
|
|
49
|
-
}> | RegistrationConstraints<C['registration'], R, P> : never;
|
|
50
|
-
export type RenamedContribution<C extends ContributionConstraint, Old extends string, New extends string> = C['context'] extends LexicalContext ? Contribution<C['token'], C['registration'], RenamedContext<C['context'], Old, New>> : C;
|
|
51
|
-
/** Project a module's typed-token collections as readonly service arrays. */
|
|
52
57
|
export type ModuleContributions<M> = M extends Module<infer _P, infer _R, infer C, infer _D> ? Readonly<{
|
|
53
58
|
[T in Groups<C>['token'] as TokenKey<T>]: ReadonlyArray<TokenService<T>>;
|
|
54
59
|
}> : never;
|
|
55
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* The checked generic `contribute` callable exposed by a builder.
|
|
62
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
63
|
+
*/
|
|
56
64
|
export type BuilderContribute<E extends Entry, C extends NeedConstraint> = <T extends TokenBase, V extends Registration>(token: T & TokenTupleAdmission<readonly [T]>, registration: V & Registration & BindingOutput<NoInfer<T>, NoInfer<V>> & CheckedConstraints<C | Contribution<NoInfer<T>, NoInfer<V>>, RegistrationsFromEntries<E>>, ...invalid: [T] extends [never] ? [never] : [V] extends [never] ? [never] : []) => import('./di-bag').Builder<E, C | Contribution<T, V>>;
|
|
57
65
|
export {};
|
|
@@ -7,13 +7,25 @@ declare class ReferenceBase {
|
|
|
7
7
|
declare class DependencyHandle<T extends TokenBase, K extends 'optional' | 'lazy' | 'all'> extends ReferenceBase {
|
|
8
8
|
readonly [referenceInvariant]: (value: [T, K]) => [T, K];
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* A positional dependency that yields the token service or `undefined` when unbound.
|
|
12
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#declare-optional-and-lazy-dependencies
|
|
13
|
+
*/
|
|
11
14
|
export type OptionalDependency<T extends TokenBase> = DependencyHandle<T, 'optional'>;
|
|
12
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* A positional dependency that yields all contributions for a token as a readonly array.
|
|
17
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#compose-an-ordered-collection
|
|
18
|
+
*/
|
|
13
19
|
export type CollectionDependency<T extends TokenBase> = DependencyHandle<T, 'all'>;
|
|
14
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* A positional dependency that yields a function which resolves the token on demand.
|
|
22
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#declare-optional-and-lazy-dependencies
|
|
23
|
+
*/
|
|
15
24
|
export type LazyDependency<T extends TokenBase> = DependencyHandle<T, 'lazy'>;
|
|
16
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* A typed token or one of the positional dependency-reference handles.
|
|
27
|
+
* @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#adapt-classes-and-positional-functions
|
|
28
|
+
*/
|
|
17
29
|
export type DependencyReference = TokenBase | ReferenceBase;
|
|
18
30
|
type ReferenceParts<R> = R extends {
|
|
19
31
|
readonly [referenceInvariant]: (...args: never[]) => [infer T extends TokenBase, infer K];
|