@codefast/di 0.5.0-canary.7 → 0.5.0-canary.9
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/CHANGELOG.md +205 -0
- package/README.md +6 -2
- package/dist/binding.d.ts +85 -24
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +55 -0
- package/dist/binding.js.map +1 -1
- package/dist/constructor-type.d.ts +4 -5
- package/dist/constructor-type.d.ts.map +1 -1
- package/dist/container/binding-builders.d.ts +32 -11
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +144 -192
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +141 -201
- package/dist/container/container.js.map +1 -1
- package/dist/decorators/inject.d.ts +2 -4
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js.map +1 -1
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/introspection/inspector.d.ts +0 -1
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +3 -8
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/metadata/metadata-keys.d.ts +3 -6
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +3 -6
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/registry.d.ts +14 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +81 -78
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +27 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +68 -0
- package/dist/resolution/activation-need.js.map +1 -0
- package/dist/resolution/binding-lookup-cache.d.ts +41 -0
- package/dist/resolution/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/binding-lookup-cache.js +118 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-scope.d.ts +5 -2
- package/dist/resolution/binding-scope.d.ts.map +1 -1
- package/dist/resolution/binding-scope.js +6 -17
- package/dist/resolution/binding-scope.js.map +1 -1
- package/dist/resolution/binding-select.d.ts +8 -1
- package/dist/resolution/binding-select.d.ts.map +1 -1
- package/dist/resolution/binding-select.js +14 -36
- package/dist/resolution/binding-select.js.map +1 -1
- package/dist/resolution/class-introspector.d.ts +27 -0
- package/dist/resolution/class-introspector.d.ts.map +1 -0
- package/dist/resolution/class-introspector.js +60 -0
- package/dist/resolution/class-introspector.js.map +1 -0
- package/dist/resolution/diagnostics.d.ts +41 -0
- package/dist/resolution/diagnostics.d.ts.map +1 -0
- package/dist/resolution/diagnostics.js +18 -0
- package/dist/resolution/diagnostics.js.map +1 -0
- package/dist/resolution/environment.d.ts +48 -1
- package/dist/resolution/environment.d.ts.map +1 -1
- package/dist/resolution/environment.js +134 -5
- package/dist/resolution/environment.js.map +1 -1
- package/dist/resolution/instantiation-plan.d.ts +15 -15
- package/dist/resolution/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/instantiation-plan.js +69 -49
- package/dist/resolution/instantiation-plan.js.map +1 -1
- package/dist/resolution/lifecycle.d.ts +2 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -1
- package/dist/resolution/lifecycle.js +60 -62
- package/dist/resolution/lifecycle.js.map +1 -1
- package/dist/resolution/resolution-path.d.ts +84 -17
- package/dist/resolution/resolution-path.d.ts.map +1 -1
- package/dist/resolution/resolution-path.js +68 -23
- package/dist/resolution/resolution-path.js.map +1 -1
- package/dist/resolution/resolve-options.d.ts +41 -4
- package/dist/resolution/resolve-options.d.ts.map +1 -1
- package/dist/resolution/resolve-options.js +25 -1
- package/dist/resolution/resolve-options.js.map +1 -1
- package/dist/resolution/resolver.d.ts +33 -10
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +471 -830
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/scope.d.ts +11 -15
- package/dist/resolution/scope.d.ts.map +1 -1
- package/dist/resolution/scope.js +55 -43
- package/dist/resolution/scope.js.map +1 -1
- package/package.json +10 -106
- package/src/binding.ts +146 -24
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +184 -284
- package/src/container/container.ts +161 -221
- package/src/decorators/inject.ts +3 -5
- package/src/errors.ts +38 -0
- package/src/index.ts +4 -1
- package/src/introspection/inspector.ts +3 -9
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/registry.ts +90 -94
- package/src/resolution/activation-need.ts +85 -0
- package/src/resolution/binding-lookup-cache.ts +148 -0
- package/src/resolution/binding-scope.ts +6 -17
- package/src/resolution/binding-select.ts +15 -39
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/resolution/environment.ts +181 -5
- package/src/resolution/instantiation-plan.ts +116 -64
- package/src/resolution/lifecycle.ts +69 -62
- package/src/resolution/resolution-path.ts +122 -43
- package/src/resolution/resolve-options.ts +51 -4
- package/src/resolution/resolver.ts +649 -1081
- package/src/resolution/scope.ts +58 -47
package/src/binding.ts
CHANGED
|
@@ -40,6 +40,13 @@ export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolea
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Cached singleton absent — distinguishes "not resolved yet" from a cached `undefined`.
|
|
45
|
+
*
|
|
46
|
+
* @since 0.5.0-canary.8
|
|
47
|
+
*/
|
|
48
|
+
export const NO_INSTANCE: unique symbol = Symbol("di:no-instance");
|
|
49
|
+
|
|
43
50
|
/**
|
|
44
51
|
* @since 0.3.16-canary.0
|
|
45
52
|
*/
|
|
@@ -67,14 +74,14 @@ export function bindingSlotToString(slot: BindingSlot): string {
|
|
|
67
74
|
interface BindingBase<Value> {
|
|
68
75
|
readonly id: BindingIdentifier;
|
|
69
76
|
/**
|
|
70
|
-
* True while this binding's factory is on the
|
|
71
|
-
* it on enter and clears it on exit, making cycle detection an O(1) field read with no hashing,
|
|
72
|
-
* no path scan, and no side table. Sync resolution is single-threaded, so the flag is exactly
|
|
73
|
-
* path membership; the async lane keeps its own per-path check because chains can interleave.
|
|
77
|
+
* True while this binding's factory is executing on the current synchronous call stack.
|
|
74
78
|
*
|
|
75
|
-
* @remarks
|
|
79
|
+
* @remarks Both cycle guards that can use an `O(1)` flag read this — the sync transient-dynamic
|
|
80
|
+
* lane and the async cascade lane — because synchronous code does not interleave, so the flag *is*
|
|
81
|
+
* exact path membership. Not optional: `createBinding` always sets it, and a field that may be
|
|
82
|
+
* absent is a field that can cost the shared hidden class. Resolver-owned; callers never set it.
|
|
76
83
|
*/
|
|
77
|
-
inFlight
|
|
84
|
+
inFlight: boolean;
|
|
78
85
|
/**
|
|
79
86
|
* Memoized resolution frame for this binding. Its contents derive only from immutable binding
|
|
80
87
|
* fields, so it is computed once on first resolve and reused instead of a per-resolver Map
|
|
@@ -83,6 +90,13 @@ interface BindingBase<Value> {
|
|
|
83
90
|
* @remarks Resolver-owned bookkeeping — `registry.add` normalizes it, so callers never set it.
|
|
84
91
|
*/
|
|
85
92
|
frame?: ResolutionFrame | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Cached singleton instance, or {@link NO_INSTANCE}.
|
|
95
|
+
*
|
|
96
|
+
* @remarks A binding belongs to exactly one container, so its singleton slot is per-binding —
|
|
97
|
+
* a field read replaces a keyed lookup on the hottest resolve shape there is.
|
|
98
|
+
*/
|
|
99
|
+
instance?: unknown;
|
|
86
100
|
readonly token: Token<Value> | Constructor<Value>;
|
|
87
101
|
readonly slot: BindingSlot;
|
|
88
102
|
readonly predicate?: ((ctx: ConstraintContext) => boolean) | undefined;
|
|
@@ -90,74 +104,76 @@ interface BindingBase<Value> {
|
|
|
90
104
|
|
|
91
105
|
type BindingBaseKeys = keyof BindingBase<unknown>;
|
|
92
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The lifecycle hooks every kind but `alias` may carry.
|
|
109
|
+
*
|
|
110
|
+
* @remarks Declared as **methods**, not function-typed properties, so their parameters compare
|
|
111
|
+
* bivariantly and `Binding<Value>` stays assignable to `Binding`. The engine erases the value type at
|
|
112
|
+
* every lane boundary regardless; the public `ActivationHandler` / `DeactivationHandler` keep strict
|
|
113
|
+
* checking, which is where a user's handler is actually verified. Not `readonly`: a fluent chain
|
|
114
|
+
* refines both in place — see {@link RefinableBindingFields}.
|
|
115
|
+
*/
|
|
116
|
+
interface BindingLifecycleHooks<Value> {
|
|
117
|
+
onActivation?(ctx: ResolutionContext, instance: Value): Value | Promise<Value>;
|
|
118
|
+
onDeactivation?(instance: Value): void | Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
// ── Binding kinds ─────────────────────────────────────────────────────────────
|
|
94
122
|
|
|
95
123
|
/**
|
|
96
124
|
* @since 0.3.16-canary.0
|
|
97
125
|
*/
|
|
98
|
-
export interface ClassBinding<Value> extends BindingBase<Value> {
|
|
126
|
+
export interface ClassBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
99
127
|
readonly kind: "class";
|
|
100
128
|
readonly target: Constructor<Value>;
|
|
101
129
|
readonly scope: BindingScope;
|
|
102
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
103
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
/**
|
|
107
133
|
* @since 0.3.16-canary.0
|
|
108
134
|
*/
|
|
109
|
-
export interface DynamicBinding<Value> extends BindingBase<Value> {
|
|
135
|
+
export interface DynamicBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
110
136
|
readonly kind: "dynamic";
|
|
111
137
|
readonly factory: (ctx: ResolutionContext) => Value;
|
|
112
138
|
readonly scope: BindingScope;
|
|
113
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
114
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
115
139
|
}
|
|
116
140
|
|
|
117
141
|
/**
|
|
118
142
|
* @since 0.3.16-canary.0
|
|
119
143
|
*/
|
|
120
|
-
export interface DynamicAsyncBinding<Value> extends BindingBase<Value> {
|
|
144
|
+
export interface DynamicAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
121
145
|
readonly kind: "dynamic-async";
|
|
122
146
|
readonly factory: (ctx: ResolutionContext) => Promise<Value>;
|
|
123
147
|
readonly scope: BindingScope;
|
|
124
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
125
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
/**
|
|
129
151
|
* @since 0.3.16-canary.0
|
|
130
152
|
*/
|
|
131
|
-
export interface ResolvedBinding<Value> extends BindingBase<Value> {
|
|
153
|
+
export interface ResolvedBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
132
154
|
readonly kind: "resolved";
|
|
133
155
|
readonly factory: (...args: Array<unknown>) => Value;
|
|
134
156
|
readonly deps: ReadonlyArray<InjectionDescriptor>;
|
|
135
157
|
readonly scope: BindingScope;
|
|
136
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
137
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
/**
|
|
141
161
|
* @since 0.3.16-canary.0
|
|
142
162
|
*/
|
|
143
|
-
export interface ResolvedAsyncBinding<Value> extends BindingBase<Value> {
|
|
163
|
+
export interface ResolvedAsyncBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
144
164
|
readonly kind: "resolved-async";
|
|
145
165
|
readonly factory: (...args: Array<unknown>) => Promise<Value>;
|
|
146
166
|
readonly deps: ReadonlyArray<InjectionDescriptor>;
|
|
147
167
|
readonly scope: BindingScope;
|
|
148
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
149
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
150
168
|
}
|
|
151
169
|
|
|
152
170
|
/**
|
|
153
171
|
* @since 0.3.16-canary.0
|
|
154
172
|
*/
|
|
155
|
-
export interface ConstantBinding<Value> extends BindingBase<Value> {
|
|
173
|
+
export interface ConstantBinding<Value> extends BindingBase<Value>, BindingLifecycleHooks<Value> {
|
|
156
174
|
readonly kind: "constant";
|
|
157
175
|
readonly value: Value;
|
|
158
176
|
readonly scope: "singleton";
|
|
159
|
-
readonly onActivation?: ActivationHandler<Value> | undefined;
|
|
160
|
-
readonly onDeactivation?: DeactivationHandler<Value> | undefined;
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
/**
|
|
@@ -166,6 +182,13 @@ export interface ConstantBinding<Value> extends BindingBase<Value> {
|
|
|
166
182
|
export interface AliasBinding<Value> extends BindingBase<Value> {
|
|
167
183
|
readonly kind: "alias";
|
|
168
184
|
readonly target: Token<Value> | Constructor<Value>;
|
|
185
|
+
/**
|
|
186
|
+
* Always `transient` — an alias defers scoping to the binding it points at.
|
|
187
|
+
*
|
|
188
|
+
* @remarks Declared so `scope` is present on every kind, which is what lets the engine read it
|
|
189
|
+
* as a plain field instead of testing for the one kind that lacks it.
|
|
190
|
+
*/
|
|
191
|
+
readonly scope: "transient";
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
/**
|
|
@@ -204,6 +227,105 @@ export function generateBindingId(): BindingIdentifier {
|
|
|
204
227
|
return String(++bindingIdCounter) as BindingIdentifier;
|
|
205
228
|
}
|
|
206
229
|
|
|
230
|
+
// ── Construction ──────────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
// Superset of every kind's fields, so one literal can copy any binding shape.
|
|
233
|
+
/** Union of every key any binding kind declares — `keyof` a union would give the intersection. */
|
|
234
|
+
type BindingFieldName = Binding<unknown> extends infer Kind ? (Kind extends unknown ? keyof Kind : never) : never;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Completeness guard for {@link createBinding}'s literal.
|
|
238
|
+
*
|
|
239
|
+
* @remarks The literal is `satisfies` this, so a field added to any binding kind that the literal
|
|
240
|
+
* forgets to write is a compile error rather than a binding silently missing it.
|
|
241
|
+
*/
|
|
242
|
+
type ConstructedBindingFields = Record<BindingFieldName, unknown>;
|
|
243
|
+
|
|
244
|
+
type BindingFieldSuperset = {
|
|
245
|
+
readonly kind: Binding["kind"];
|
|
246
|
+
readonly instance?: unknown;
|
|
247
|
+
readonly scope: BindingScope;
|
|
248
|
+
readonly target?: unknown;
|
|
249
|
+
readonly factory?: unknown;
|
|
250
|
+
readonly deps?: unknown;
|
|
251
|
+
readonly value?: unknown;
|
|
252
|
+
readonly onActivation?: unknown;
|
|
253
|
+
readonly onDeactivation?: unknown;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The single construction site for bindings — one literal, one V8 hidden class.
|
|
258
|
+
*
|
|
259
|
+
* @see `ARCHITECTURE.md` — why the field order and the single site are load-bearing.
|
|
260
|
+
*
|
|
261
|
+
* @param source - the kind-specific payload, or an existing binding to re-slot
|
|
262
|
+
* @param id - reuse a caller's id to keep a fluent chain's `id()` stable across refinements
|
|
263
|
+
*
|
|
264
|
+
* @since 0.5.0-canary.8
|
|
265
|
+
*/
|
|
266
|
+
export function createBinding<Value>(
|
|
267
|
+
source: PartialBinding<Value> | Binding<Value>,
|
|
268
|
+
token: Token<Value> | Constructor<Value>,
|
|
269
|
+
slot: BindingSlot,
|
|
270
|
+
predicate: ((ctx: ConstraintContext) => boolean) | undefined,
|
|
271
|
+
id: BindingIdentifier = generateBindingId(),
|
|
272
|
+
): Binding<Value> {
|
|
273
|
+
const fields = source as BindingFieldSuperset;
|
|
274
|
+
return {
|
|
275
|
+
kind: fields.kind,
|
|
276
|
+
id,
|
|
277
|
+
inFlight: false,
|
|
278
|
+
frame: undefined,
|
|
279
|
+
instance: (source as { instance?: unknown }).instance ?? NO_INSTANCE,
|
|
280
|
+
token,
|
|
281
|
+
slot,
|
|
282
|
+
predicate,
|
|
283
|
+
scope: fields.scope,
|
|
284
|
+
target: fields.target,
|
|
285
|
+
factory: fields.factory,
|
|
286
|
+
deps: fields.deps,
|
|
287
|
+
value: fields.value,
|
|
288
|
+
onActivation: fields.onActivation,
|
|
289
|
+
onDeactivation: fields.onDeactivation,
|
|
290
|
+
} satisfies ConstructedBindingFields as Binding<Value>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Writable view of the only fields a fluent chain may refine after registration.
|
|
295
|
+
*
|
|
296
|
+
* @remarks No registry index is keyed on these, so a builder that owns the registered object
|
|
297
|
+
* can write them directly instead of re-registering. `token`, `slot`, `predicate` and `id`
|
|
298
|
+
* are excluded on purpose — changing those means re-indexing.
|
|
299
|
+
*
|
|
300
|
+
* @since 0.5.0-canary.8
|
|
301
|
+
*/
|
|
302
|
+
export interface RefinableBindingFields<Value> {
|
|
303
|
+
onActivation: ActivationHandler<Value> | undefined;
|
|
304
|
+
onDeactivation: DeactivationHandler<Value> | undefined;
|
|
305
|
+
scope: BindingScope;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Narrows a registered binding to the fields a fluent chain may still refine.
|
|
310
|
+
*
|
|
311
|
+
* @since 0.5.0-canary.8
|
|
312
|
+
*/
|
|
313
|
+
export function refinableFields<Value>(binding: Binding<Value>): RefinableBindingFields<Value> {
|
|
314
|
+
return binding as RefinableBindingFields<Value>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Drops the memoized resolution frame, for a refinement that changes what the frame reports.
|
|
319
|
+
*
|
|
320
|
+
* @remarks `scope` is the only field a chain writes in place that the frame derives from — a
|
|
321
|
+
* re-slot builds a fresh binding, whose frame starts empty anyway.
|
|
322
|
+
*
|
|
323
|
+
* @since 0.5.0-canary.9
|
|
324
|
+
*/
|
|
325
|
+
export function clearBindingFrame<Value>(binding: Binding<Value>): void {
|
|
326
|
+
(binding as { frame: ResolutionFrame | undefined }).frame = undefined;
|
|
327
|
+
}
|
|
328
|
+
|
|
207
329
|
// ── Builder interfaces ────────────────────────────────────────────────────────
|
|
208
330
|
|
|
209
331
|
/**
|
package/src/constructor-type.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A class
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* this alias is the DI “class token” surface only.
|
|
2
|
+
* A class token: newable, producing `Value`.
|
|
3
|
+
*
|
|
4
|
+
* @remarks Rest parameters are `never[]` so classes with typed constructors stay assignable under
|
|
5
|
+
* `strictFunctionTypes`. Construction uses the real shape; this alias is the token surface only.
|
|
7
6
|
*
|
|
8
7
|
* @since 0.3.16-canary.0
|
|
9
8
|
*/
|