di-bag 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/AGENTS.md +149 -0
  2. package/README.md +59 -46
  3. package/dist/acquisition-context.d.ts +8 -2
  4. package/dist/acquisition-mode.d.ts +9 -4
  5. package/dist/acquisition-mode.js +25 -7
  6. package/dist/acquisition.d.ts +2 -0
  7. package/dist/acquisition.js +9 -0
  8. package/dist/alias-types.d.ts +12 -3
  9. package/dist/composition-report.d.ts +3 -2
  10. package/dist/composition.d.ts +8 -2
  11. package/dist/contribution-types.d.ts +26 -18
  12. package/dist/dependency-references.d.ts +16 -4
  13. package/dist/di-bag.d.ts +320 -41
  14. package/dist/di-bag.js +86 -14
  15. package/dist/errors.d.ts +115 -8
  16. package/dist/errors.js +113 -12
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.js +2 -1
  19. package/dist/inspection.d.ts +21 -5
  20. package/dist/lifetime-types.d.ts +281 -180
  21. package/dist/lifetime.d.ts +4 -1
  22. package/dist/module-types.d.ts +87 -30
  23. package/dist/module.d.ts +18 -4
  24. package/dist/module.js +31 -7
  25. package/dist/observers.d.ts +25 -6
  26. package/dist/plugins.d.ts +17 -4
  27. package/dist/provider.d.ts +41 -10
  28. package/dist/provider.js +1 -0
  29. package/dist/registration.d.ts +8 -2
  30. package/dist/registration.js +4 -1
  31. package/dist/runtime.d.ts +12 -3
  32. package/dist/runtime.js +25 -8
  33. package/dist/scope-types.d.ts +20 -5
  34. package/dist/startup.d.ts +19 -1
  35. package/dist/startup.js +72 -11
  36. package/dist/token-types.d.ts +26 -8
  37. package/dist/tokens.d.ts +10 -2
  38. package/dist/tokens.js +2 -0
  39. package/dist/types.d.ts +68 -22
  40. package/docs/agent/api-card.md +337 -0
  41. package/docs/agent/errors.md +1042 -0
  42. package/docs/agent/recipes.md +290 -0
  43. package/package.json +11 -5
@@ -1,223 +1,324 @@
1
1
  import type { ContributionConstraint } from './contribution-types';
2
2
  import type { Registration, Registrations } from './registration';
3
- import type { Provider, ProviderFactory, ProviderGraphContract, ProviderNamedDependencies, ProviderRequiredTokens, ProviderOptionalTokens, ProviderCollectionTokens, ProviderRegistrationMetadata, ProviderAcquisitionMetadata, ProviderAcquiredValue } from './provider';
4
- import type { GraphContract } from './token-types';
5
- import type { TokenKey } from './tokens';
6
- import type { CheckDependencyCompatibility, CheckDependencyCompleteness, NameText, Unsatisfied } from './types';
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;
3
+ import type { ProviderGraphContract, ProviderNamedDependencies, ProviderRequiredTokens, ProviderOptionalTokens, ProviderCollectionTokens } from './provider';
4
+ import type { TokenBase, TokenKey } from './tokens';
5
+ import type { CheckDependencyCompatibility, CheckDependencyCompleteness, NameText, SeeErrors, Unsatisfied } from './types';
6
+ import type { CheckedConstraints, CompleteConstraints, NeedConstraint } from './module-types';
17
7
  /**
18
- * A module provider's retained local registrations and public-to-local export mapping.
19
- * Contexts chain through `parent` when a module was sealed inside another module:
20
- * a name absent from `registrations` resolves in the parent, and the chain end
21
- * resolves in the installing host, matching runtime lexical lookup.
8
+ * Where a sealed lifetime walk leaves its module: an export or external name the installing
9
+ * host resolves, a typed-token collection the host completes, or a private scoped dead end.
10
+ * @see https://dany-fedorov.github.io/di-bag/agent/errors.html#root-capture
22
11
  */
23
- export type LexicalContext<R extends Registrations = Registrations, E extends object = object> = {
24
- readonly registrations: R;
25
- /** Current public name -> original local name. */
26
- readonly exports: E;
27
- readonly parent?: LexicalContext;
12
+ export type Reach = {
13
+ readonly kind: 'export' | 'external';
14
+ readonly key: PropertyKey;
15
+ } | {
16
+ readonly kind: 'collection';
17
+ readonly key: symbol;
18
+ } | {
19
+ readonly kind: 'scoped';
20
+ readonly key: PropertyKey;
28
21
  };
29
- /** The lexical scope a sealing builder gives its retained constraints and providers. */
30
- export type ModuleScope<R extends Registrations, P extends PropertyKey> = LexicalContext<R, ExportMap<P>>;
31
- type Extras<C> = Pick<C, Exclude<keyof C, keyof LexicalContext>>;
32
- type WithExtras<C, X> = [Exclude<keyof C, keyof LexicalContext>] extends [never] ? X : X & Extras<C>;
33
- /** Attach an enclosing scope at the end of a lexical chain; `undefined` leaves the chain unchanged. */
34
- export type Enclosed<C, Parent> = [Parent] extends [undefined] ? C : C extends LexicalContext<infer R, infer E> ? C extends {
35
- readonly parent: infer P extends LexicalContext;
36
- } ? WithExtras<C, LexicalContext<R, E> & {
37
- readonly parent: Enclosed<P, Parent>;
38
- }> : WithExtras<C, LexicalContext<R, E> & {
39
- readonly parent: Parent;
40
- }> : C;
41
- /** Rename one public export at the chain end, where names meet the installing host. */
42
- export type RenamedContext<C, Old extends string, New extends string> = C extends LexicalContext<infer R, infer E> ? C extends {
43
- readonly parent: infer P extends LexicalContext;
44
- } ? WithExtras<C, LexicalContext<R, E> & {
45
- readonly parent: RenamedContext<P, Old, New>;
46
- }> : WithExtras<C, LexicalContext<R, Renamed<E, Old, New>>> : C;
47
- /** The plain scope of a sealed contribution, without its retained original registration. */
48
- type Scope<C> = C extends LexicalContext<infer R, infer E> ? C extends {
49
- readonly parent: infer P extends LexicalContext;
50
- } ? LexicalContext<R, E> & {
51
- readonly parent: P;
52
- } : LexicalContext<R, E> : undefined;
53
- type WithoutLexical<G> = G extends {
54
- readonly lexical: unknown;
55
- } ? Omit<G, 'lexical'> : G;
22
+ /**
23
+ * A compact seal-time lifetime record that replaces a module's private registrations:
24
+ * `root-reach` names a private strict root, `export-reach` an export the host checks as a root or
25
+ * walks through as a transient or alias, and `contribution-reach` a sealed contribution group
26
+ * that is checked as a root or walked by collecting roots. Each record carries one reach.
27
+ * @see https://dany-fedorov.github.io/di-bag/agent/errors.html#root-capture
28
+ */
56
29
  export type LifetimeObligation = {
57
- readonly kind: 'lifetime';
58
- readonly source: PropertyKey;
59
- readonly context: LexicalContext;
30
+ readonly kind: 'root-reach';
31
+ readonly root: PropertyKey;
32
+ readonly reach: Reach;
33
+ } | {
34
+ readonly kind: 'export-reach';
35
+ readonly export: PropertyKey;
36
+ readonly reach: Reach;
37
+ } | {
38
+ readonly kind: 'contribution-reach';
39
+ readonly group: symbol;
40
+ readonly policy: 'root' | 'transient';
41
+ readonly reach: Reach;
60
42
  };
61
- type ExportMap<P extends PropertyKey> = {
62
- readonly [K in P]: K;
43
+ type ContributionSite = {
44
+ readonly kind: 'contribution';
63
45
  };
64
- type Strict<R> = ProviderGraphContract<R> extends infer G ? G extends {
46
+ type SiteText<S> = S extends ContributionSite ? 'contribution' : NameText<S>;
47
+ type CaptiveText<C> = C extends {
48
+ readonly root: infer R;
49
+ readonly dependency: infer D;
50
+ } ? `${SiteText<R>} -> ${SiteText<D>}` : never;
51
+ type Members<V> = V extends infer T & {} ? T extends Registration ? T : never : never;
52
+ type Strict<T> = ProviderGraphContract<T> extends infer G ? G extends {
65
53
  readonly lifetime: {
66
54
  readonly kind: 'root';
67
- readonly allowScopedDependencies: infer C;
55
+ readonly allowScopedDependencies: infer A;
68
56
  };
69
- } ? [C] extends [true] ? false : true : false : false;
70
- export type PrivateLifetimes<R extends Registrations, P extends keyof R> = {
71
- [K in Exclude<keyof R, P>]: true extends Strict<R[K]> ? {
72
- readonly kind: 'lifetime';
73
- readonly source: K;
74
- readonly context: LexicalContext<R, ExportMap<P>>;
75
- } : never;
76
- }[Exclude<keyof R, P>];
77
- export type LexicalProvider<V extends Registration, R extends Registrations, P extends keyof R, K extends keyof R> = V extends infer T & {} ? T extends Registration ? [Extract<ProviderGraphContract<T>, {
57
+ } ? [A] extends [true] ? false : true : false : false;
58
+ type Carrying<T> = ProviderGraphContract<T> extends infer G ? G extends {
59
+ readonly alias: PropertyKey;
60
+ } | {
78
61
  readonly lifetime: {
79
- readonly kind: 'root' | 'transient';
62
+ readonly kind: 'transient';
80
63
  };
64
+ } ? true : false : false;
65
+ type StrictMembers<V> = Members<V> extends infer T ? T extends Registration ? true extends Strict<T> ? T : never : never : never;
66
+ type CarrierMembers<V> = Members<V> extends infer T ? T extends Registration ? true extends Strict<T> | Carrying<T> ? T : never : never : never;
67
+ type Dependencies<V> = V extends Registration ? keyof ProviderNamedDependencies<V> | TokenKey<ProviderRequiredTokens<V> | ProviderOptionalTokens<V>> : never;
68
+ type AliasKeys<V> = ProviderGraphContract<V> extends infer G ? G extends {
69
+ readonly alias: infer A;
70
+ } ? A : never : never;
71
+ type CollectionKeys<V> = ProviderCollectionTokens<V> extends infer T ? T extends TokenBase ? TokenKey<T> : never : never;
72
+ type Lifetimed<V> = V extends infer T & {} ? ProviderGraphContract<T> extends infer G ? G extends {
73
+ readonly lifetime: unknown;
74
+ } | {
75
+ readonly alias: unknown;
81
76
  } | {
77
+ readonly sharedAlias: unknown;
78
+ } ? true : never : never : never;
79
+ type ContributionRegistrations<C> = C extends ContributionConstraint ? C['registration'] : never;
80
+ type NeedsLifetimeWalk<R extends Registrations, C> = [Extract<C, LifetimeObligation>] extends [never] ? Lifetimed<R[keyof R] | ContributionRegistrations<C>> : true;
81
+ type ExportReaches<C, K> = C extends {
82
+ readonly kind: 'export-reach';
83
+ readonly export: K;
84
+ readonly reach: infer X;
85
+ } ? X : never;
86
+ type Reached<R extends Registrations, P, C, D, Visited> = D extends P ? {
87
+ readonly kind: 'export';
88
+ readonly key: D;
89
+ } : D extends keyof R ? D extends Visited ? never : ReachTarget<R, P, C, R[D], D, Visited | D> : D extends PropertyKey ? {
90
+ readonly kind: 'external';
91
+ readonly key: D;
92
+ } : never;
93
+ type ReachTarget<R extends Registrations, P, C, V, D, Visited> = Members<V> extends infer T ? T extends Registration ? ProviderGraphContract<T> extends infer G ? G extends {
94
+ readonly kind: 'opaque';
95
+ } ? never : G extends {
82
96
  readonly alias: PropertyKey;
83
- }>] extends [never] ? T : Provider<ProviderFactory<T>, ProviderRegistrationMetadata<T> & object, ProviderAcquisitionMetadata<T>, WithoutLexical<ProviderGraphContract<T>> & {
84
- readonly lexical: {
85
- readonly source: K;
86
- readonly context: ModuleScope<R, P>;
97
+ } ? Reaches<R, P, C, T, D, Visited> : G extends {
98
+ readonly lifetime: {
99
+ readonly kind: 'root';
87
100
  };
88
- }, ProviderAcquiredValue<T>> : never : never;
89
- type RenamedGraph<G extends GraphContract, Old extends string, New extends string> = G extends {
90
- readonly lexical: {
91
- readonly source: infer K;
92
- readonly context: infer L extends LexicalContext;
101
+ } ? never : G extends {
102
+ readonly lifetime: {
103
+ readonly kind: 'transient';
93
104
  };
94
- } ? Omit<G, 'lexical'> & {
95
- readonly lexical: {
96
- readonly source: K;
97
- readonly context: RenamedContext<L, Old, New>;
105
+ } ? Reaches<R, P, C, T, D, Visited> : {
106
+ readonly kind: 'scoped';
107
+ readonly key: D;
108
+ } : never : never : never;
109
+ type Reaches<R extends Registrations, P, C, V, K, Visited> = Reached<R, P, C, Dependencies<V> | AliasKeys<V>, Visited> | Follow<R, P, C, ExportReaches<C, K>, Visited> | (CollectionKeys<V> extends infer T ? T extends symbol ? {
110
+ readonly kind: 'collection';
111
+ readonly key: T;
112
+ } : never : never);
113
+ type Follow<R extends Registrations, P, C, X, Visited> = X extends {
114
+ readonly kind: 'export' | 'external';
115
+ readonly key: infer D;
116
+ } ? Reached<R, P, C, D, Visited> : X;
117
+ type AsRoot<Root, X> = X extends Reach ? {
118
+ readonly kind: 'root-reach';
119
+ readonly root: Root;
120
+ readonly reach: X;
121
+ } : never;
122
+ type AsExport<K, X> = X extends Reach ? {
123
+ readonly kind: 'export-reach';
124
+ readonly export: K;
125
+ readonly reach: X;
126
+ } : never;
127
+ type AsContribution<T, Policy, X> = X extends Reach ? {
128
+ readonly kind: 'contribution-reach';
129
+ readonly group: T;
130
+ readonly policy: Policy;
131
+ readonly reach: X;
132
+ } : never;
133
+ type PrivateRoots<R extends Registrations, P, C> = {
134
+ [K in Exclude<keyof R, P>]: [StrictMembers<R[K]>] extends [never] ? never : AsRoot<K, Reaches<R, P, C, StrictMembers<R[K]>, K, K>>;
135
+ }[Exclude<keyof R, P>];
136
+ type RetainedRoots<R extends Registrations, P, C, O = C> = O extends {
137
+ readonly kind: 'root-reach';
138
+ readonly root: infer Root;
139
+ readonly reach: infer X;
140
+ } ? AsRoot<Root, Follow<R, P, C, X, never>> : never;
141
+ type ExportObligations<R extends Registrations, P, C> = {
142
+ [K in P & keyof R]: [CarrierMembers<R[K]>] extends [never] ? never : AsExport<K, Reaches<R, P, C, CarrierMembers<R[K]>, K, K>>;
143
+ }[P & keyof R];
144
+ type ContributionPolicy<T> = true extends Strict<T> ? 'root' : ProviderGraphContract<T> extends infer G ? G extends {
145
+ readonly lifetime: {
146
+ readonly kind: 'transient';
147
+ };
148
+ } ? 'transient' : never : never;
149
+ type OwnContributions<R extends Registrations, P, C, I = Extract<C, ContributionConstraint>> = I extends ContributionConstraint ? Members<I['registration']> extends infer T ? T extends Registration ? ContributionPolicy<T> extends infer Policy ? Policy extends 'root' | 'transient' ? AsContribution<TokenKey<I['token']>, Policy, Reaches<R, P, C, T, never, never>> : never : never : never : never : never;
150
+ type RetainedContributions<R extends Registrations, P, C, O = C> = O extends {
151
+ readonly kind: 'contribution-reach';
152
+ readonly group: infer T;
153
+ readonly policy: infer Policy;
154
+ readonly reach: infer X;
155
+ } ? AsContribution<T, Policy, Follow<R, P, C, X, never>> : never;
156
+ type ContributionObligations<R extends Registrations, P, C> = OwnContributions<R, P, C> | RetainedContributions<R, P, C>;
157
+ type Scoped = {
158
+ readonly reach: {
159
+ readonly kind: 'scoped';
98
160
  };
99
- } : G;
100
- export type RenamedLifetimeProvider<V extends Registration, Old extends string, New extends string> = V extends infer T & {} ? T extends Registration ? ProviderGraphContract<T> extends {
101
- readonly lexical: unknown;
102
- } ? Provider<ProviderFactory<T>, ProviderRegistrationMetadata<T> & object, ProviderAcquisitionMetadata<T>, RenamedGraph<ProviderGraphContract<T>, Old, New>, ProviderAcquiredValue<T>> : T : never : never;
103
- /** Rename public lifetime-carrier registrations while preserving their lexical sources. */
104
- export type RenamedLifetimeProviders<D extends Registrations, Old extends string, New extends string> = {
105
- [K in keyof D as K extends Old ? New : K]: RenamedLifetimeProvider<D[K], Old, New>;
106
- };
107
- /** Rename a retained lifetime obligation's public export view. */
108
- export type RenamedLifetimeObligation<C extends LifetimeObligation, Old extends string, New extends string> = {
109
- readonly kind: 'lifetime';
110
- readonly source: C['source'];
111
- readonly context: RenamedContext<C['context'], Old, New>;
112
- };
113
- /** Re-scope a retained obligation when the builder holding it seals into a module. */
114
- export type EnclosedLifetimeObligation<C extends LifetimeObligation, R extends Registrations, P extends PropertyKey> = {
115
- readonly kind: 'lifetime';
116
- readonly source: C['source'];
117
- readonly context: Enclosed<C['context'], ModuleScope<R, P>>;
118
- };
119
- type PublicSite<K> = {
120
- readonly kind: 'public';
121
- readonly key: K;
122
- };
123
- type PrivateSite<C, K> = {
124
- readonly kind: 'private';
125
- readonly context: C;
126
- readonly key: K;
127
161
  };
128
- type Captive<Root, Site> = {
162
+ type Unreplaceable<R extends Registrations, P, C> = PrivateRoots<R, P, C> | RetainedRoots<R, P, C> | Extract<ContributionObligations<R, P, C>, {
163
+ readonly policy: 'root';
164
+ }>;
165
+ type SealCaptives<R extends Registrations, P, C> = Extract<Unreplaceable<R, P, C>, Scoped>;
166
+ type SealCaptiveText<O> = O extends {
167
+ readonly reach: {
168
+ readonly key: infer D;
169
+ };
170
+ } ? `${O extends {
171
+ readonly root: infer Root;
172
+ } ? SiteText<Root> : 'contribution'} -> ${NameText<D>}` : never;
173
+ /** Every compact lifetime obligation a sealing builder retains for its installing host. */
174
+ export type SealedLifetimes<R extends Registrations, P extends PropertyKey, C> = [NeedsLifetimeWalk<R, C>] extends [never] ? never : unknown extends CheckDependencyCompatibility<R> ? Exclude<Unreplaceable<R, P, C>, Scoped> | ExportObligations<R, P, C> | Extract<ContributionObligations<R, P, C>, {
175
+ readonly policy: 'transient';
176
+ }> : never;
177
+ /** Reject sealing when a root the host cannot replace captures a scoped service of the same module. */
178
+ export type SealAdmission<R extends Registrations, P extends PropertyKey, C> = [NeedsLifetimeWalk<R, C>] extends [never] ? unknown : [SealCaptives<R, P, C>] extends [never] ? unknown : unknown extends CheckDependencyCompatibility<R> ? Unsatisfied<`root lifetime cannot capture scoped dependency: ${SealCaptiveText<SealCaptives<R, P, C>>}${SeeErrors<'root-capture'>}`, {
179
+ readonly captives: SealCaptives<R, P, C>;
180
+ }> : unknown;
181
+ type RenamedReach<X, Old, New> = X extends {
182
+ readonly kind: 'export';
183
+ readonly key: Old;
184
+ } ? {
185
+ readonly kind: 'export';
186
+ readonly key: New;
187
+ } : X;
188
+ /** Rename one export inside retained lifetime obligations. */
189
+ export type RenamedObligation<O, Old extends string, New extends string> = O extends {
190
+ readonly kind: 'export-reach';
191
+ readonly export: infer K;
192
+ readonly reach: infer X;
193
+ } ? {
194
+ readonly kind: 'export-reach';
195
+ readonly export: K extends Old ? New : K;
196
+ readonly reach: RenamedReach<X, Old, New>;
197
+ } : O extends {
198
+ readonly kind: 'root-reach';
199
+ readonly root: infer Root;
200
+ readonly reach: infer X;
201
+ } ? {
202
+ readonly kind: 'root-reach';
129
203
  readonly root: Root;
130
- readonly dependency: Site;
204
+ readonly reach: RenamedReach<X, Old, New>;
205
+ } : O extends {
206
+ readonly kind: 'contribution-reach';
207
+ readonly group: infer T;
208
+ readonly policy: infer Policy;
209
+ readonly reach: infer X;
210
+ } ? {
211
+ readonly kind: 'contribution-reach';
212
+ readonly group: T;
213
+ readonly policy: Policy;
214
+ readonly reach: RenamedReach<X, Old, New>;
215
+ } : O;
216
+ /** Drop the obligations of replaced exports: the replacement brings its own graph. */
217
+ export type WithoutExportObligations<C, K> = [Extract<C, {
218
+ readonly kind: 'export-reach';
219
+ readonly export: K;
220
+ }>] extends [never] ? C : Exclude<C, {
221
+ readonly kind: 'export-reach';
222
+ readonly export: K;
223
+ }>;
224
+ type Captured<D> = {
225
+ readonly captured: D;
131
226
  };
132
- type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
133
- type Seen<S, V> = true extends (V extends unknown ? Equal<S, V> : never) ? true : false;
134
- type PublicKey<E, K> = {
135
- [P in keyof E]: Equal<E[P], K> extends true ? P : never;
136
- }[keyof E];
137
- type Dependencies<V extends Registration> = keyof ProviderNamedDependencies<V> | TokenKey<ProviderRequiredTokens<V> | ProviderOptionalTokens<V>>;
138
- type WalkEnclosing<H extends Registrations, C, K, Root, Visited, G> = C extends {
139
- readonly parent: infer P extends LexicalContext;
140
- } ? WalkDependency<H, P, K, Root, Visited, G> : WalkPublic<H, K, Root, Visited, G>;
141
- type WalkDependency<H extends Registrations, C, K, Root, Visited, G> = K extends PropertyKey ? C extends LexicalContext<infer R, infer E> ? K extends keyof R ? [PublicKey<E, K>] extends [never] ? WalkTarget<H, R[K], C, Root, PrivateSite<C, K>, Visited, G> : WalkEnclosing<H, C, PublicKey<E, K>, Root, Visited, G> : WalkEnclosing<H, C, K, Root, Visited, G> : WalkPublic<H, K, Root, Visited, G> : never;
142
- type WalkPublic<H extends Registrations, K, Root, Visited, G> = K extends keyof H ? WalkTarget<H, H[K], undefined, Root, PublicSite<K>, Visited, G> : never;
143
- type WalkTarget<H extends Registrations, V extends Registration, C, Root, Site, Visited, G> = V extends infer T & {} ? T extends Registration ? ProviderGraphContract<T> extends infer PG ? PG extends {
227
+ type GroupReaches<C, T> = C extends {
228
+ readonly kind: 'contribution-reach';
229
+ readonly group: T;
230
+ readonly policy: 'transient';
231
+ readonly reach: infer X;
232
+ } ? X : never;
233
+ type Collected<T> = {
234
+ readonly collection: T;
235
+ };
236
+ type HostReach<R extends Registrations, C, D, Visited> = D extends keyof R ? D extends Visited ? never : HostTarget<R, C, R[D], D, Visited | D> : never;
237
+ type HostTarget<R extends Registrations, C, V, D, Visited> = Members<V> extends infer T ? T extends Registration ? ProviderGraphContract<T> extends infer G ? G extends {
144
238
  readonly sharedAlias: {
145
- readonly registrations: infer P extends Registrations;
239
+ readonly registrations: infer S extends Registrations;
146
240
  readonly source: infer K;
147
241
  };
148
- } ? WalkPublic<P, K, Root, never, G> : PG extends {
242
+ } ? HostReach<S, C, K, never> : G extends {
149
243
  readonly kind: 'opaque';
150
- } ? never : PG extends {
244
+ } ? never : G extends {
151
245
  readonly alias: PropertyKey;
152
- } ? Seen<Site, Visited> extends true ? never : WalkSource<H, T, C, Root, Visited | Site, G> : PG extends {
246
+ } ? HostReaches<R, C, T, D, Visited> : G extends {
153
247
  readonly lifetime: {
154
248
  readonly kind: 'root';
155
249
  };
156
- } ? never : PG extends {
250
+ } ? never : G extends {
157
251
  readonly lifetime: {
158
252
  readonly kind: 'transient';
159
253
  };
160
- } ? Seen<Site, Visited> extends true ? never : WalkSource<H, T, C, Root, Visited | Site, G> : Captive<Root, Site> : never : never : never;
161
- type WalkSource<H extends Registrations, V extends Registration, C, Root, Visited, G> = ProviderGraphContract<V> extends {
162
- readonly lexical: {
163
- readonly source: infer K;
164
- readonly context: infer L extends LexicalContext;
254
+ } ? HostReaches<R, C, T, D, Visited> : Captured<D> : never : never : never;
255
+ type HostReaches<R extends Registrations, C, V, K, Visited> = HostReach<R, C, Dependencies<V> | AliasKeys<V>, Visited> | HostFollow<R, C, ExportReaches<C, K>, Visited> | HostCollection<R, C, CollectionKeys<V>, Visited>;
256
+ type HostFollow<R extends Registrations, C, X, Visited> = X extends {
257
+ readonly kind: 'scoped';
258
+ readonly key: infer S;
259
+ } ? Captured<S> : X extends {
260
+ readonly kind: 'collection';
261
+ readonly key: infer T;
262
+ } ? HostCollection<R, C, T, Visited> : X extends {
263
+ readonly key: infer D;
264
+ } ? HostReach<R, C, D, Visited> : never;
265
+ type HostCollection<R extends Registrations, C, T, Visited> = T extends symbol ? Collected<T> extends Visited ? never : HostContributions<R, C, T, Visited | Collected<T>> | HostFollow<R, C, GroupReaches<C, T>, Visited | Collected<T>> : never;
266
+ type HostContributions<R extends Registrations, C, T, Visited, I = Extract<C, ContributionConstraint>> = I extends ContributionConstraint ? TokenKey<I['token']> extends T ? Members<I['registration']> extends infer M ? M extends Registration ? ProviderGraphContract<M> extends infer G ? G extends {
267
+ readonly kind: 'opaque';
268
+ } ? never : G extends {
269
+ readonly lifetime: {
270
+ readonly kind: 'root';
165
271
  };
166
- } ? K extends keyof L['registrations'] ? WalkSource<H, L['registrations'][K], Enclosed<L, C>, Root, Visited, G> : never : WalkDeclared<H, V, C, Root, Visited, G>;
167
- type WalkDeclared<H extends Registrations, V extends Registration, C, Root, Visited, G> = unknown extends (C extends LexicalContext ? CheckDependencyCompatibility<C['registrations']> : unknown) ? WalkDependency<H, C, Dependencies<V>, Root, Visited, G> | WalkCollection<H, ProviderCollectionTokens<V>, Root, Visited, G> : never;
168
- type CheckRoot<H extends Registrations, V extends Registration, C, Site, G> = V extends infer T & {} ? T extends Registration ? true extends Strict<T> ? WalkSource<H, T, C, Site, Site, G> : never : never : never;
169
- type PublicCaptives<H extends Registrations, G> = {
170
- [K in keyof H]: CheckRoot<H, H[K], undefined, PublicSite<K>, G>;
171
- }[keyof H];
172
- type PrivateCaptives<H extends Registrations, C, G> = C extends LifetimeObligation ? C['source'] extends keyof C['context']['registrations'] ? CheckRoot<H, C['context']['registrations'][C['source']], C['context'], PrivateSite<C['context'], C['source']>, G> : never : never;
173
- type ContributionSite<C> = {
174
- readonly kind: 'contribution';
175
- readonly contribution: C;
176
- };
177
- type ContributionRegistration<I extends ContributionConstraint> = I['context'] extends {
178
- readonly registration: infer O extends Registration;
179
- } ? O : I['registration'];
180
- type ContributionScope<I extends ContributionConstraint> = Scope<I['context']>;
181
- type WalkCollection<H extends Registrations, T, Root, Visited, G, Items = Extract<G, ContributionConstraint>> = Items extends ContributionConstraint ? TokenKey<Items['token']> extends TokenKey<T> ? WalkTarget<H, ContributionRegistration<Items>, ContributionScope<Items>, Root, ContributionSite<Items>, Visited, G> : never : never;
182
- type ContributionCaptives<H extends Registrations, G, Items = Extract<G, ContributionConstraint>> = Items extends ContributionConstraint ? CheckRoot<H, ContributionRegistration<Items>, ContributionScope<Items>, ContributionSite<Items>, G> : never;
183
- type Captives<R extends Registrations, C> = PublicCaptives<R, C> | PrivateCaptives<R, C, C> | ContributionCaptives<R, C>;
184
- type OverrideCaptives<R extends Registrations, O extends Registrations, G> = {
185
- [K in keyof O & keyof R]: CheckRoot<R, R[K], undefined, PublicSite<K>, G>;
186
- }[keyof O & keyof R];
187
- /** Reject root providers introduced by a scope override when they capture scoped dependencies. */
188
- export type CheckedScopeLifetimes<R extends Registrations, O extends Registrations, G = never> = [
189
- OverrideCaptives<R, O, G>
190
- ] extends [never] ? unknown : Unsatisfied<`root lifetime cannot capture scoped dependency: ${CaptiveText<OverrideCaptives<R, O, G>>}`, {
191
- readonly captives: OverrideCaptives<R, O, G>;
192
- }>;
193
- /** Reject strict root providers that transitively capture scoped dependencies. */
194
- export type CheckedLifetimes<R extends Registrations, C extends NeedConstraint> = [
195
- Captives<R, C>
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>>}`, {
272
+ } ? never : G extends {
273
+ readonly lifetime: {
274
+ readonly kind: 'transient';
275
+ };
276
+ } ? HostReaches<R, C, M, never, Visited> : Captured<ContributionSite> : never : never : never : never : never;
277
+ type Captive<Root, X> = X extends Captured<infer D> ? {
278
+ readonly root: Root;
279
+ readonly dependency: D;
280
+ } : never;
281
+ type RootCaptives<R extends Registrations, C, Keys extends keyof R> = {
282
+ [K in Keys]: [StrictMembers<R[K]>] extends [never] ? never : Captive<K, HostReaches<R, C, StrictMembers<R[K]>, K, K>>;
283
+ }[Keys];
284
+ type ContributionRootCaptives<R extends Registrations, C, I = Extract<C, ContributionConstraint>> = I extends ContributionConstraint ? [StrictMembers<I['registration']>] extends [never] ? never : Captive<ContributionSite, HostReaches<R, C, StrictMembers<I['registration']>, never, never>> : never;
285
+ type ObligationCaptives<R extends Registrations, C, O = C> = O extends {
286
+ readonly kind: 'root-reach';
287
+ readonly root: infer Root;
288
+ readonly reach: infer X;
289
+ } ? Captive<Root, HostFollow<R, C, X, never>> : O extends {
290
+ readonly kind: 'contribution-reach';
291
+ readonly policy: 'root';
292
+ readonly reach: infer X;
293
+ } ? Captive<ContributionSite, HostFollow<R, C, X, never>> : never;
294
+ type Captives<R extends Registrations, C> = RootCaptives<R, C, keyof R> | ContributionRootCaptives<R, C> | ObligationCaptives<R, C>;
295
+ /**
296
+ * Reject strict root providers that transitively capture scoped dependencies.
297
+ * @see https://dany-fedorov.github.io/di-bag/agent/errors.html#root-capture
298
+ */
299
+ export type CheckedLifetimes<R extends Registrations, C extends NeedConstraint> = [NeedsLifetimeWalk<R, C>] extends [never] ? unknown : [Captives<R, C>] 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>>}${SeeErrors<'root-capture'>}`, {
197
300
  readonly captives: Captives<R, C>;
198
301
  }> : unknown;
199
- type PolicyEnclosing<H extends Registrations, C, K, Visited> = C extends {
200
- readonly parent: infer P extends LexicalContext;
201
- } ? PolicyDependency<H, P, K, Visited> : PolicyPublic<H, K, Visited>;
202
- type PolicyDependency<H extends Registrations, C, K, Visited> = K extends PropertyKey ? C extends LexicalContext<infer R, infer E> ? K extends keyof R ? [PublicKey<E, K>] extends [never] ? PolicyTarget<H, R[K], C, PrivateSite<C, K>, Visited> : PolicyEnclosing<H, C, PublicKey<E, K>, Visited> : PolicyEnclosing<H, C, K, Visited> : PolicyPublic<H, K, Visited> : never;
203
- type PolicyPublic<H extends Registrations, K, Visited> = K extends keyof H ? PolicyTarget<H, H[K], undefined, PublicSite<K>, Visited> : never;
204
- type PolicyTarget<H extends Registrations, V extends Registration, C, Site, Visited> = Seen<Site, Visited> extends true ? never : PolicyGraph<H, V, C, Visited | Site>;
205
- type PolicyGraph<H extends Registrations, V extends Registration, C, Visited> = ProviderGraphContract<V> extends infer G ? G extends {
302
+ type OverrideCaptives<R extends Registrations, O extends Registrations, C> = RootCaptives<R, C, keyof O & keyof R>;
303
+ /**
304
+ * Reject root providers introduced by a scope override when they capture scoped dependencies.
305
+ * @see https://dany-fedorov.github.io/di-bag/agent/errors.html#root-capture
306
+ */
307
+ export type CheckedScopeLifetimes<R extends Registrations, O extends Registrations, C = never> = [NeedsLifetimeWalk<R, C>] extends [never] ? unknown : [OverrideCaptives<R, O, C>] extends [never] ? unknown : Unsatisfied<`root lifetime cannot capture scoped dependency: ${CaptiveText<OverrideCaptives<R, O, C>>}${SeeErrors<'root-capture'>}`, {
308
+ readonly captives: OverrideCaptives<R, O, C>;
309
+ }>;
310
+ type PolicyOf<R extends Registrations, K, Visited> = K extends keyof R ? K extends Visited ? never : PolicyTarget<R, R[K], Visited | K> : never;
311
+ type PolicyTarget<R extends Registrations, V, Visited> = ProviderGraphContract<V> extends infer G ? G extends {
206
312
  readonly sharedAlias: {
207
313
  readonly registrations: infer P extends Registrations;
208
314
  readonly source: infer K;
209
315
  };
210
- } ? PolicyPublic<P, K, never> : G extends {
211
- readonly lexical: {
212
- readonly source: infer S;
213
- readonly context: infer L extends LexicalContext;
214
- };
215
- } ? S extends keyof L['registrations'] ? PolicyGraph<H, L['registrations'][S], Enclosed<L, C>, Visited> : never : G extends {
316
+ } ? PolicyOf<P, K, never> : G extends {
216
317
  readonly alias: infer K;
217
- } ? PolicyDependency<H, C, K, Visited> : G extends {
318
+ } ? PolicyOf<R, K, Visited> : G extends {
218
319
  readonly lifetime: {
219
- readonly kind: infer K;
320
+ readonly kind: infer L;
220
321
  };
221
- } ? K : 'scoped' : never;
222
- export type CanonicalLifetime<R extends Registrations, K extends keyof R> = PolicyPublic<R, K, never>;
322
+ } ? L : 'scoped' : never;
323
+ export type CanonicalLifetime<R extends Registrations, K extends keyof R> = PolicyOf<R, K, never>;
223
324
  export {};
@@ -2,7 +2,10 @@ import type { Registration } from './registration';
2
2
  import type { Provider, ProviderFactory, RetainedMetadata, ProviderAcquisitionMetadata, ProviderGraphContract, ProviderAcquiredValue } from './provider';
3
3
  import type { GraphContract } from './token-types';
4
4
  import type { Singleton, Unsatisfied } from './types';
5
- /** Cache at the ownership-family root, once per scope, or once per resolution. */
5
+ /**
6
+ * Cache at the ownership-family root, once per scope, or once per resolution.
7
+ * @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#choose-root-scoped-or-transient-caching
8
+ */
6
9
  export type Lifetime = 'root' | 'scoped' | 'transient';
7
10
  export interface LifetimePolicy {
8
11
  readonly kind: Lifetime;