@codefast/di 0.11.0 → 0.13.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/CHANGELOG.md +151 -0
- package/README.md +128 -31
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +21 -28
- package/dist/container/{binding-builders.js → binding-chain.js} +53 -163
- package/dist/container/container.d.ts +11 -3
- package/dist/container/container.js +176 -102
- package/dist/core/binding-builders.d.ts +117 -0
- package/dist/core/binding-declaration.d.ts +9 -2
- package/dist/core/binding-declaration.js +5 -4
- package/dist/core/binding.d.ts +21 -149
- package/dist/core/binding.js +15 -25
- package/dist/core/module.d.ts +1 -1
- package/dist/core/registry.d.ts +9 -24
- package/dist/core/registry.js +30 -155
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.d.ts +3 -1
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle.d.ts +20 -0
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +7 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +29 -19
- package/dist/{errors/errors.js → errors.js} +43 -25
- package/dist/index.d.ts +12 -10
- package/dist/index.js +6 -6
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/introspection/dependency-graph.d.ts +1 -1
- package/dist/introspection/dependency-graph.js +3 -4
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +169 -0
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +25 -20
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +11 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +51 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +23 -6
- package/dist/lifecycle/{scope-manager.js → scopes.js} +93 -20
- package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +1 -1
- package/dist/metadata/{metadata-keys.js → keys.js} +1 -1
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +1 -1
- package/dist/metadata/{verifying-metadata-reader.js → verifying-reader.js} +2 -2
- package/dist/resolution/cache/activation-need.d.ts +1 -1
- package/dist/resolution/cache/activation-need.js +5 -1
- package/dist/resolution/cache/class-introspector.d.ts +27 -36
- package/dist/resolution/cache/class-introspector.js +61 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -2
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +40 -8
- package/dist/resolution/context.d.ts +1 -1
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +12 -2
- package/dist/resolution/{path/resolution-path.js → path.js} +23 -3
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +2 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +2 -4
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +4 -4
- package/dist/resolution/resolver.d.ts +19 -7
- package/dist/resolution/resolver.js +343 -125
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +80 -72
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/decorators/lifecycle-decorators.d.ts +0 -14
- /package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +0 -0
- /package/dist/{ambient/active-container.js → ambient-container.js} +0 -0
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{decorator-metadata.d.ts → metadata-record.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/injection/{resolve-options.js → dependency-slot.js} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
package/dist/core/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindingSlotEquals
|
|
1
|
+
import { bindingSlotEquals } from "#core/binding";
|
|
2
2
|
import { getOrInsert } from "#core/map-upsert";
|
|
3
3
|
import { advanceStateEpoch } from "#core/state-epoch";
|
|
4
4
|
const NO_BINDINGS = Object.freeze([]);
|
|
@@ -21,8 +21,8 @@ function createTokenRecord(bindings) {
|
|
|
21
21
|
* that only ever resolves through its parent — every per-request child — allocates no map.
|
|
22
22
|
*/
|
|
23
23
|
const EMPTY_LONE = new Map();
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const HELD_CONSTANT = 1;
|
|
25
|
+
const HELD_ALIAS = 2;
|
|
26
26
|
/**
|
|
27
27
|
* @since 0.11.0
|
|
28
28
|
*/
|
|
@@ -35,18 +35,14 @@ export class BindingRegistry {
|
|
|
35
35
|
// Every other token — several bindings, a tagged slot, a predicate — has a record here, and a
|
|
36
36
|
// token is in exactly one of the two maps. Allocated by the first token that needs a record.
|
|
37
37
|
#records;
|
|
38
|
-
// The binding the last `add` placed and where it landed: the fluent chain refines what it just
|
|
39
|
-
// registered, so a refinement that follows its own add re-slots without a map probe.
|
|
40
|
-
#lastAdded;
|
|
41
|
-
#lastAddedRecord;
|
|
42
38
|
// Built on the first id-keyed read and maintained from then on: a bind-and-resolve container
|
|
43
39
|
// never asks by id, so it never pays for the second map.
|
|
44
40
|
#byId;
|
|
45
41
|
// Set when the first tagged slot lands and never cleared, like the tagged maps it stands for.
|
|
46
42
|
#taggedIndexBuilt = false;
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
#
|
|
43
|
+
// Kinds registered here at least once, never cleared: only the negative answers must be exact, so a
|
|
44
|
+
// container holding no constant skips its teardown sweep and one holding no alias skips its forwarding probe.
|
|
45
|
+
#heldKinds = 0;
|
|
50
46
|
/** Monotonic version — increments on every mutation. */
|
|
51
47
|
get version() {
|
|
52
48
|
return this.#version;
|
|
@@ -59,7 +55,11 @@ export class BindingRegistry {
|
|
|
59
55
|
}
|
|
60
56
|
/** Whether a constant has ever been registered here, and so whether teardown has anything to sweep. */
|
|
61
57
|
get hasHeldConstantBinding() {
|
|
62
|
-
return this.#
|
|
58
|
+
return (this.#heldKinds & HELD_CONSTANT) !== 0;
|
|
59
|
+
}
|
|
60
|
+
/** Whether an alias has ever been registered here, and so whether a candidate list can hold one. */
|
|
61
|
+
get hasHeldAlias() {
|
|
62
|
+
return (this.#heldKinds & HELD_ALIAS) !== 0;
|
|
63
63
|
}
|
|
64
64
|
/** Whether the deferred tagged-slot index has had to be built. */
|
|
65
65
|
get isTaggedIndexBuilt() {
|
|
@@ -88,25 +88,21 @@ export class BindingRegistry {
|
|
|
88
88
|
*/
|
|
89
89
|
add(binding) {
|
|
90
90
|
this.#bump();
|
|
91
|
-
if (binding.registrationOrder === UNREGISTERED_ORDER) {
|
|
92
|
-
registrationCounter += 1;
|
|
93
|
-
binding.registrationOrder = registrationCounter;
|
|
94
|
-
}
|
|
95
91
|
if (binding.kind === "constant") {
|
|
96
|
-
this.#
|
|
92
|
+
this.#heldKinds |= HELD_CONSTANT;
|
|
93
|
+
}
|
|
94
|
+
else if (binding.kind === "alias") {
|
|
95
|
+
this.#heldKinds |= HELD_ALIAS;
|
|
97
96
|
}
|
|
98
97
|
const key = binding.token;
|
|
99
98
|
const records = this.#records;
|
|
100
99
|
if (records !== undefined) {
|
|
101
100
|
const record = records.get(key);
|
|
102
101
|
if (record !== undefined) {
|
|
103
|
-
this.#lastAdded = binding;
|
|
104
|
-
this.#lastAddedRecord = record;
|
|
105
102
|
return this.#addToRecord(key, record, binding);
|
|
106
103
|
}
|
|
107
104
|
}
|
|
108
105
|
const occupant = this.#lone.get(key);
|
|
109
|
-
this.#lastAdded = binding;
|
|
110
106
|
if (occupant === undefined) {
|
|
111
107
|
// The common bind: a fresh token taking the lone seat, one probe and one write.
|
|
112
108
|
if (this.#byId !== undefined) {
|
|
@@ -114,33 +110,30 @@ export class BindingRegistry {
|
|
|
114
110
|
}
|
|
115
111
|
if (isDefaultSlotBinding(binding)) {
|
|
116
112
|
this.#setLone(key, binding);
|
|
117
|
-
this.#lastAddedRecord = undefined;
|
|
118
113
|
}
|
|
119
114
|
else {
|
|
120
|
-
this.#
|
|
115
|
+
this.#createRecord(key, [binding]);
|
|
121
116
|
}
|
|
122
117
|
return undefined;
|
|
123
118
|
}
|
|
124
119
|
// Same slot, last wins: the newcomer takes the lone seat and nothing else moves.
|
|
125
120
|
if (isDefaultSlotBinding(binding)) {
|
|
126
121
|
this.#setLone(key, binding);
|
|
127
|
-
this.#lastAddedRecord = undefined;
|
|
128
122
|
if (this.#byId !== undefined) {
|
|
129
123
|
this.#byId.delete(occupant.identifier);
|
|
130
124
|
this.#byId.set(binding.identifier, binding);
|
|
131
125
|
}
|
|
132
126
|
return occupant;
|
|
133
127
|
}
|
|
134
|
-
// A second shape joins the token, which is what a record is for.
|
|
128
|
+
// A second shape joins the token, which is what a record is for; every add is the newest, so it goes last.
|
|
135
129
|
this.#deleteLone(key);
|
|
136
130
|
this.#byId?.set(binding.identifier, binding);
|
|
137
|
-
this.#
|
|
131
|
+
this.#createRecord(key, [occupant, binding]);
|
|
138
132
|
return undefined;
|
|
139
133
|
}
|
|
140
134
|
/** Remove all bindings for a token. Returns removed bindings. */
|
|
141
135
|
removeByToken(token) {
|
|
142
136
|
this.#bump();
|
|
143
|
-
this.#lastAdded = undefined;
|
|
144
137
|
const lone = this.#lone.get(token);
|
|
145
138
|
if (lone !== undefined) {
|
|
146
139
|
this.#deleteLone(token);
|
|
@@ -168,7 +161,6 @@ export class BindingRegistry {
|
|
|
168
161
|
return undefined;
|
|
169
162
|
}
|
|
170
163
|
this.#bump();
|
|
171
|
-
this.#lastAdded = undefined;
|
|
172
164
|
byId.delete(id);
|
|
173
165
|
const key = binding.token;
|
|
174
166
|
if (this.#lone.get(key)?.identifier === id) {
|
|
@@ -236,28 +228,12 @@ export class BindingRegistry {
|
|
|
236
228
|
/** Remove all bindings. Returns all removed. */
|
|
237
229
|
clear() {
|
|
238
230
|
this.#bump();
|
|
239
|
-
this.#lastAdded = undefined;
|
|
240
231
|
const all = this.allBindings();
|
|
241
232
|
this.#clearLone();
|
|
242
233
|
this.#records?.clear();
|
|
243
234
|
this.#byId?.clear();
|
|
244
235
|
return all;
|
|
245
236
|
}
|
|
246
|
-
/** Whether a slot-based binding currently occupies `binding`'s slot, so adding it would displace. */
|
|
247
|
-
hasSlotOccupant(binding) {
|
|
248
|
-
if (isPurePredicateBinding(binding)) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
if (this.#lone.has(binding.token)) {
|
|
252
|
-
// The lone seat is the default slot, so only a default-slot newcomer collides with it.
|
|
253
|
-
return binding.slot.tags.length === 0;
|
|
254
|
-
}
|
|
255
|
-
const record = this.#records?.get(binding.token);
|
|
256
|
-
if (record === undefined) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
return this.#slotOccupant(record, binding.slot) !== undefined;
|
|
260
|
-
}
|
|
261
237
|
/**
|
|
262
238
|
* The binding indexed under one criterion.
|
|
263
239
|
*
|
|
@@ -304,109 +280,22 @@ export class BindingRegistry {
|
|
|
304
280
|
getDefaultSlotBinding(token) {
|
|
305
281
|
return this.#lone.get(token) ?? this.#records?.get(token)?.defaultOccupant;
|
|
306
282
|
}
|
|
307
|
-
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
308
|
-
getFastDefault(token) {
|
|
309
|
-
return this.#lone.get(token);
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Takes a live binding out of every index, lets `rewrite` change its slot or predicate, and reports
|
|
313
|
-
* whether it was live; the caller registers it again with `add`.
|
|
314
|
-
*
|
|
315
|
-
* @remarks The binding keeps its object and its id, so the id index needs no touch and nothing
|
|
316
|
-
* that holds the object has to be told. `false` means the binding was unbound or displaced since
|
|
317
|
-
* it registered, and a refinement must not resurrect it.
|
|
318
|
-
*/
|
|
319
|
-
reslot(binding, rewrite) {
|
|
320
|
-
const key = binding.token;
|
|
321
|
-
if (this.#lone.get(key) === binding) {
|
|
322
|
-
this.#bump();
|
|
323
|
-
this.#lastAdded = undefined;
|
|
324
|
-
this.#deleteLone(key);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
const record = this.#records?.get(key);
|
|
328
|
-
const index = record === undefined ? -1 : record.bindings.indexOf(binding);
|
|
329
|
-
if (record === undefined || index === -1) {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
this.#bump();
|
|
333
|
-
this.#lastAdded = undefined;
|
|
334
|
-
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
335
|
-
record.bindings = record.bindings.toSpliced(index, 1);
|
|
336
|
-
this.#deindexSlot(record, binding);
|
|
337
|
-
this.#settle(key, record);
|
|
338
|
-
}
|
|
339
|
-
rewrite();
|
|
340
|
-
return true;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Marks a live binding as a collection member in place, moving it out of the lone map: a member is
|
|
344
|
-
* never the token's lone default answer, and nothing else indexes on membership.
|
|
345
|
-
*/
|
|
346
|
-
setMany(binding) {
|
|
347
|
-
this.#bump();
|
|
348
|
-
// Set before any (re)indexing so `#indexSlot` sees a member and leaves it out of every slot.
|
|
349
|
-
writableMembership(binding).isMany = true;
|
|
350
|
-
if (binding === this.#lastAdded) {
|
|
351
|
-
// The chain refining what it just added: where the binding sits is known without a probe.
|
|
352
|
-
const record = this.#lastAddedRecord;
|
|
353
|
-
if (record === undefined) {
|
|
354
|
-
this.#deleteLone(binding.token);
|
|
355
|
-
this.#lastAddedRecord = this.#createRecord(binding.token, [binding]);
|
|
356
|
-
}
|
|
357
|
-
else if (record.defaultOccupant === binding) {
|
|
358
|
-
record.defaultOccupant = undefined;
|
|
359
|
-
}
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
// A default occupant that becomes a member frees its slot, so drop the stale index entry. A
|
|
366
|
-
// member cannot collapse a record back to lone, so this path does not settle.
|
|
367
|
-
this.#clearDefaultOccupant(binding.token, binding);
|
|
368
|
-
}
|
|
369
283
|
/**
|
|
370
|
-
*
|
|
284
|
+
* The token's default-slot alias, which forwards a request's criteria to its target, or `undefined`.
|
|
371
285
|
*
|
|
372
|
-
* @remarks
|
|
373
|
-
*
|
|
374
|
-
* binding moves to a record because the lone map holds default-slot bindings with no predicate.
|
|
286
|
+
* @remarks A registry that never held an alias answers without probing, so a request missing every slot of a
|
|
287
|
+
* token pays for forwarding only where forwarding can happen.
|
|
375
288
|
*/
|
|
376
|
-
|
|
377
|
-
this.#
|
|
378
|
-
|
|
379
|
-
writablePredicate(binding).predicate = predicate;
|
|
380
|
-
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
// The binding may have vacated the default slot, and a narrowed record can collapse back to lone.
|
|
384
|
-
const record = this.#clearDefaultOccupant(binding.token, binding);
|
|
385
|
-
if (record !== undefined) {
|
|
386
|
-
this.#settle(binding.token, record);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Moves a binding still holding the lone seat into a fresh one-binding record, returning whether it did.
|
|
391
|
-
*
|
|
392
|
-
* @remarks Its field (`isMany` or `predicate`) is written before this call, so the founding
|
|
393
|
-
* `#indexSlot` files it under the slot it now holds.
|
|
394
|
-
*/
|
|
395
|
-
#promoteLoneToRecord(key, binding) {
|
|
396
|
-
if (this.#lone.get(key) !== binding) {
|
|
397
|
-
return false;
|
|
289
|
+
defaultSlotAlias(token) {
|
|
290
|
+
if ((this.#heldKinds & HELD_ALIAS) === 0) {
|
|
291
|
+
return undefined;
|
|
398
292
|
}
|
|
399
|
-
this
|
|
400
|
-
|
|
401
|
-
return true;
|
|
293
|
+
const occupant = this.getDefaultSlotBinding(token);
|
|
294
|
+
return occupant !== undefined && occupant.kind === "alias" ? occupant : undefined;
|
|
402
295
|
}
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (record?.defaultOccupant === binding) {
|
|
407
|
-
record.defaultOccupant = undefined;
|
|
408
|
-
}
|
|
409
|
-
return record;
|
|
296
|
+
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
297
|
+
getFastDefault(token) {
|
|
298
|
+
return this.#lone.get(token);
|
|
410
299
|
}
|
|
411
300
|
#createRecord(key, bindings) {
|
|
412
301
|
const record = createTokenRecord(bindings);
|
|
@@ -445,7 +334,7 @@ export class BindingRegistry {
|
|
|
445
334
|
if (displacedBinding !== undefined) {
|
|
446
335
|
record.bindings = record.bindings.filter((candidate) => candidate !== displacedBinding);
|
|
447
336
|
}
|
|
448
|
-
|
|
337
|
+
record.bindings.push(binding);
|
|
449
338
|
this.#byId?.set(binding.identifier, binding);
|
|
450
339
|
this.#indexSlot(record, binding);
|
|
451
340
|
this.#settle(key, record);
|
|
@@ -544,20 +433,6 @@ export class BindingRegistry {
|
|
|
544
433
|
* Places a binding in its record by registration order: a fresh registration appends, and a binding a
|
|
545
434
|
* chain took out and put back returns to its place.
|
|
546
435
|
*/
|
|
547
|
-
function insertInRegistrationOrder(record, binding) {
|
|
548
|
-
const { bindings } = record;
|
|
549
|
-
const order = binding.registrationOrder;
|
|
550
|
-
if (bindings.length === 0 || bindings.at(-1).registrationOrder < order) {
|
|
551
|
-
bindings.push(binding);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
let index = bindings.length - 1;
|
|
555
|
-
while (index > 0 && bindings[index - 1].registrationOrder > order) {
|
|
556
|
-
index -= 1;
|
|
557
|
-
}
|
|
558
|
-
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
559
|
-
record.bindings = bindings.toSpliced(index, 0, binding);
|
|
560
|
-
}
|
|
561
436
|
/** A binding nothing has to be matched against: the default slot, no predicate, not a collection member. */
|
|
562
437
|
function isDefaultSlotBinding(binding) {
|
|
563
438
|
return binding.slot.tags.length === 0 && binding.predicate === undefined && !binding.isMany;
|
package/dist/core/tag.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Tag keys and the interned pairs they mint — the one way a slot criterion is built. */
|
|
2
2
|
/**
|
|
3
|
-
* The bits a tag-key mask holds: the width of a 32-bit integer
|
|
3
|
+
* The bits a tag-key mask holds: the width of a 32-bit integer.
|
|
4
4
|
*
|
|
5
5
|
* @remarks Ids past this wrap, so two keys can share a bit. The mask is a prefilter only — a shared
|
|
6
6
|
* bit costs a false positive that the identity comparison then rejects, never a false negative.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import type { Token } from "#core/token";
|
|
3
3
|
import type { Constructor } from "#core/types";
|
|
4
4
|
import type { InjectionDescriptor, InjectOptions } from "#injection/descriptor";
|
|
5
|
-
type ClassAccessorDecorator<This, Value> = (target: ClassAccessorDecoratorTarget<This, Value>, context: ClassAccessorDecoratorContext<This, Value>
|
|
5
|
+
type ClassAccessorDecorator<This, Value> = (target: ClassAccessorDecoratorTarget<This, Value>, context: ClassAccessorDecoratorContext<This, Value> & {
|
|
6
|
+
readonly static: false;
|
|
7
|
+
}) => ClassAccessorDecoratorResult<This, Value> | void;
|
|
6
8
|
/**
|
|
7
9
|
* Creates a dual-role value: an injection descriptor that also works as a class accessor decorator.
|
|
8
10
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getActiveContainer, getAmbientResolution } from "#ambient
|
|
2
|
-
import { decoratorMetadataOf } from "#decorators/
|
|
3
|
-
import { MissingContainerContextError, StaticMemberDecoratorError } from "#errors
|
|
1
|
+
import { getActiveContainer, getAmbientResolution } from "#ambient-container";
|
|
2
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
3
|
+
import { MissingContainerContextError, StaticMemberDecoratorError } from "#errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#injection/dependency-slot";
|
|
4
5
|
import { buildInjectionDescriptor } from "#injection/descriptor";
|
|
5
|
-
import {
|
|
6
|
-
import { INJECT_ACCESSOR_KEY } from "#metadata/metadata-keys";
|
|
6
|
+
import { INJECT_ACCESSOR_KEY } from "#metadata/keys";
|
|
7
7
|
/**
|
|
8
8
|
* The name of the class being constructed, or `undefined` when there is none to report.
|
|
9
9
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { decoratorMetadataOf } from "#decorators/
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
2
2
|
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
3
|
-
import { INJECTABLE_KEY } from "#metadata/
|
|
3
|
+
import { INJECTABLE_KEY } from "#metadata/keys";
|
|
4
4
|
/**
|
|
5
5
|
* Creates an empty auto-register registry.
|
|
6
6
|
*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The only method a hook can be: the lifecycle reader calls it on the instance by its string name. */
|
|
2
|
+
type LifecycleMethodContext = ClassMethodDecoratorContext & {
|
|
3
|
+
readonly static: false;
|
|
4
|
+
readonly private: false;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
};
|
|
7
|
+
type LifecycleMethodDecorator = (target: unknown, context: LifecycleMethodContext) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Marks an instance method to run after the container constructs and wires the instance.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.3.16-canary.0
|
|
12
|
+
*/
|
|
13
|
+
export declare function postConstruct(): LifecycleMethodDecorator;
|
|
14
|
+
/**
|
|
15
|
+
* Marks an instance method to run when the instance's container or scope is disposed.
|
|
16
|
+
*
|
|
17
|
+
* @since 0.3.16-canary.0
|
|
18
|
+
*/
|
|
19
|
+
export declare function preDestroy(): LifecycleMethodDecorator;
|
|
20
|
+
export {};
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { decoratorMetadataOf } from "#decorators/
|
|
2
|
-
import { StaticMemberDecoratorError, SymbolKeyedLifecycleError } from "#errors
|
|
3
|
-
import { LIFECYCLE_KEY } from "#metadata/
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/metadata-record";
|
|
2
|
+
import { PrivateLifecycleMethodError, StaticMemberDecoratorError, SymbolKeyedLifecycleError } from "#errors";
|
|
3
|
+
import { LIFECYCLE_KEY } from "#metadata/keys";
|
|
4
4
|
/** Records the decorated method under one lifecycle phase; both decorators differ only in that phase. */
|
|
5
5
|
function recordLifecycleMethod(phase) {
|
|
6
|
+
// Typed wide on purpose: the checks below are what an untyped caller meets instead of the compiler.
|
|
6
7
|
return function (target, context) {
|
|
7
8
|
if (context.static) {
|
|
8
9
|
throw new StaticMemberDecoratorError(phase, String(context.name));
|
|
9
10
|
}
|
|
11
|
+
if (context.private) {
|
|
12
|
+
throw new PrivateLifecycleMethodError(phase, String(context.name));
|
|
13
|
+
}
|
|
10
14
|
// The lifecycle reader keys methods by their string name, so a symbol-keyed method can never be
|
|
11
15
|
// found again — fail here, where the declaration is, rather than at resolve.
|
|
12
16
|
if (typeof context.name === "symbol") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The metadata record a decorator writes into, checked once for every decorator the library ships.
|
|
3
3
|
*/
|
|
4
|
-
import { MissingDecoratorMetadataError } from "#errors
|
|
4
|
+
import { MissingDecoratorMetadataError } from "#errors";
|
|
5
5
|
/**
|
|
6
6
|
* Returns the metadata record a decorator context carries.
|
|
7
7
|
*
|
|
@@ -28,7 +28,9 @@ export declare class TokenNotBoundError extends DiError {
|
|
|
28
28
|
readonly name = "TokenNotBoundError";
|
|
29
29
|
readonly code = "TOKEN_NOT_BOUND";
|
|
30
30
|
readonly tokenName: string;
|
|
31
|
-
|
|
31
|
+
/** The token names from the outermost request down to the unbound one, which is last. */
|
|
32
|
+
readonly path: ReadonlyArray<string>;
|
|
33
|
+
constructor(tokenName: string, path?: ReadonlyArray<string>);
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* A token that has bindings, none of whose slots match the given `ResolveOptions`.
|
|
@@ -41,7 +43,9 @@ export declare class NoMatchingBindingError extends DiError {
|
|
|
41
43
|
readonly tokenName: string;
|
|
42
44
|
readonly options: ResolveOptions;
|
|
43
45
|
readonly availableSlots: Array<string>;
|
|
44
|
-
|
|
46
|
+
/** The token names from the outermost request down to the unmatched one, which is last. */
|
|
47
|
+
readonly path: ReadonlyArray<string>;
|
|
48
|
+
constructor(tokenName: string, options: ResolveOptions, availableSlots: Array<string>, path?: ReadonlyArray<string>);
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* Two or more candidate bindings that matched without the more-specific rule deciding a winner.
|
|
@@ -162,7 +166,7 @@ export declare class UnreachableLifecycleHookError extends DiError {
|
|
|
162
166
|
readonly code = "UNREACHABLE_LIFECYCLE_HOOK";
|
|
163
167
|
readonly tokenName: string;
|
|
164
168
|
readonly phase: "onActivation" | "onDeactivation";
|
|
165
|
-
/** Nothing binds the token, or — for a deactivation hook — every binding it has is transient
|
|
169
|
+
/** Nothing binds the token, or — for a deactivation hook — every binding it has is transient. */
|
|
166
170
|
readonly reason: "unbound" | "no-deactivatable-binding";
|
|
167
171
|
constructor(tokenName: string, phase: "onActivation" | "onDeactivation", reason?: "unbound" | "no-deactivatable-binding");
|
|
168
172
|
}
|
|
@@ -207,16 +211,6 @@ export declare class AsyncModuleLoadError extends DiError {
|
|
|
207
211
|
readonly moduleName: string;
|
|
208
212
|
constructor(moduleName: string);
|
|
209
213
|
}
|
|
210
|
-
/**
|
|
211
|
-
* A synchronous disposal attempt on a container whose `onDeactivation` handlers may be async.
|
|
212
|
-
*
|
|
213
|
-
* @since 0.3.16-canary.0
|
|
214
|
-
*/
|
|
215
|
-
export declare class SyncDisposalNotSupportedError extends DiError {
|
|
216
|
-
readonly name = "SyncDisposalNotSupportedError";
|
|
217
|
-
readonly code = "SYNC_DISPOSAL_NOT_SUPPORTED";
|
|
218
|
-
constructor();
|
|
219
|
-
}
|
|
220
214
|
/**
|
|
221
215
|
* A `scoped` binding resolved from a container with no child scope context.
|
|
222
216
|
*
|
|
@@ -246,10 +240,11 @@ export declare class MissingContainerContextError extends DiError {
|
|
|
246
240
|
constructor(className: string | undefined, accessorName: string | symbol);
|
|
247
241
|
}
|
|
248
242
|
/**
|
|
249
|
-
* A second `to*()` on a chain that already registered its binding.
|
|
243
|
+
* A slot step or a second `to*()` on a chain that already registered its binding.
|
|
250
244
|
*
|
|
251
|
-
* @remarks A chain is its binding
|
|
252
|
-
* calls, the second of which displaces the
|
|
245
|
+
* @remarks A chain is its binding and registers exactly once, in its final shape, so its slot is
|
|
246
|
+
* settled by `to*()`; a token bound twice is two `bind()` calls, the second of which displaces the
|
|
247
|
+
* first under slot last-wins.
|
|
253
248
|
*
|
|
254
249
|
* @since 0.10.0
|
|
255
250
|
*/
|
|
@@ -274,11 +269,11 @@ export declare class ManyBindingSlotError extends DiError {
|
|
|
274
269
|
constructor(tokenName: string);
|
|
275
270
|
}
|
|
276
271
|
/**
|
|
277
|
-
* A fluent chain
|
|
272
|
+
* A scope, a lifecycle hook or `id()` asked of a fluent chain before a `to*()` call registered its binding.
|
|
278
273
|
*
|
|
279
274
|
* @remarks The builder types make this unreachable from TypeScript — `bind()` returns
|
|
280
|
-
* `BindToBuilder`, which exposes only `to*()`. It exists for JavaScript callers and
|
|
281
|
-
* casts past the types, so the misuse fails loudly instead of mutating nothing.
|
|
275
|
+
* `BindToBuilder`, which exposes only the slot steps and `to*()`. It exists for JavaScript callers and
|
|
276
|
+
* for anyone who casts past the types, so the misuse fails loudly instead of mutating nothing.
|
|
282
277
|
*
|
|
283
278
|
* @since 0.10.0
|
|
284
279
|
*/
|
|
@@ -341,6 +336,21 @@ export declare class SymbolKeyedLifecycleError extends DiError {
|
|
|
341
336
|
readonly memberName: string;
|
|
342
337
|
constructor(decoratorName: string, memberName: string);
|
|
343
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* A `@postConstruct` or `@preDestroy` on a private method, which the lifecycle reader cannot call by name.
|
|
341
|
+
*
|
|
342
|
+
* @remarks Reported at decoration rather than at resolve, so the error points at the declaration
|
|
343
|
+
* instead of a `MetadataReader` the caller never configured.
|
|
344
|
+
*
|
|
345
|
+
* @since 0.13.0
|
|
346
|
+
*/
|
|
347
|
+
export declare class PrivateLifecycleMethodError extends DiError {
|
|
348
|
+
readonly name = "PrivateLifecycleMethodError";
|
|
349
|
+
readonly code = "PRIVATE_LIFECYCLE_METHOD";
|
|
350
|
+
readonly decoratorName: string;
|
|
351
|
+
readonly memberName: string;
|
|
352
|
+
constructor(decoratorName: string, memberName: string);
|
|
353
|
+
}
|
|
344
354
|
/**
|
|
345
355
|
* A decorator handed no metadata object, because the runtime has no `Symbol.metadata` to key one by.
|
|
346
356
|
*
|
|
@@ -18,6 +18,10 @@ export class InternalError extends DiError {
|
|
|
18
18
|
super(message);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
// A path of one token only repeats the name the message already opens with.
|
|
22
|
+
function pathSuffix(path) {
|
|
23
|
+
return path.length > 1 ? ` Path: ${path.join(" → ")}` : "";
|
|
24
|
+
}
|
|
21
25
|
/**
|
|
22
26
|
* A token with no binding at all, even after walking the parent container chain.
|
|
23
27
|
*
|
|
@@ -27,9 +31,12 @@ export class TokenNotBoundError extends DiError {
|
|
|
27
31
|
name = "TokenNotBoundError";
|
|
28
32
|
code = "TOKEN_NOT_BOUND";
|
|
29
33
|
tokenName;
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
/** The token names from the outermost request down to the unbound one, which is last. */
|
|
35
|
+
path;
|
|
36
|
+
constructor(tokenName, path = [tokenName]) {
|
|
37
|
+
super(`No binding found for token '${tokenName}'. Did you forget container.bind(${tokenName})?${pathSuffix(path)}`);
|
|
32
38
|
this.tokenName = tokenName;
|
|
39
|
+
this.path = path;
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
// Options carry caller values a tag may hold — a bigint or a circular object must not make the
|
|
@@ -78,13 +85,16 @@ export class NoMatchingBindingError extends DiError {
|
|
|
78
85
|
tokenName;
|
|
79
86
|
options;
|
|
80
87
|
availableSlots;
|
|
81
|
-
|
|
88
|
+
/** The token names from the outermost request down to the unmatched one, which is last. */
|
|
89
|
+
path;
|
|
90
|
+
constructor(tokenName, options, availableSlots, path = [tokenName]) {
|
|
82
91
|
const optionsString = describeResolveOptions(options);
|
|
83
92
|
const slotsStr = availableSlots.join(", ");
|
|
84
|
-
super(`No binding for '${tokenName}' matching ${optionsString}. Available slots: [${slotsStr}]
|
|
93
|
+
super(`No binding for '${tokenName}' matching ${optionsString}. Available slots: [${slotsStr}].${pathSuffix(path)}`);
|
|
85
94
|
this.tokenName = tokenName;
|
|
86
95
|
this.options = options;
|
|
87
96
|
this.availableSlots = availableSlots;
|
|
97
|
+
this.path = path;
|
|
88
98
|
}
|
|
89
99
|
}
|
|
90
100
|
/**
|
|
@@ -225,12 +235,12 @@ export class UnreachableLifecycleHookError extends DiError {
|
|
|
225
235
|
code = "UNREACHABLE_LIFECYCLE_HOOK";
|
|
226
236
|
tokenName;
|
|
227
237
|
phase;
|
|
228
|
-
/** Nothing binds the token, or — for a deactivation hook — every binding it has is transient
|
|
238
|
+
/** Nothing binds the token, or — for a deactivation hook — every binding it has is transient. */
|
|
229
239
|
reason;
|
|
230
240
|
constructor(tokenName, phase, reason = "unbound") {
|
|
231
241
|
super(reason === "unbound"
|
|
232
242
|
? `${phase}() is registered for '${tokenName}', which nothing is bound to in this container or its ancestors, so the hook can never run. Bind the token, or — if '${tokenName}' is a class you bound as an implementation via .to(${tokenName}) — register the hook against the token you bound instead.`
|
|
233
|
-
: `${phase}() is registered for '${tokenName}', whose every binding in this container and its ancestors is transient
|
|
243
|
+
: `${phase}() is registered for '${tokenName}', whose every binding in this container and its ancestors is transient, so the hook can never run: a transient instance is never deactivated. Make one of its bindings a singleton or scoped, or drop the hook.`);
|
|
234
244
|
this.tokenName = tokenName;
|
|
235
245
|
this.phase = phase;
|
|
236
246
|
this.reason = reason;
|
|
@@ -286,18 +296,6 @@ export class AsyncModuleLoadError extends DiError {
|
|
|
286
296
|
this.moduleName = moduleName;
|
|
287
297
|
}
|
|
288
298
|
}
|
|
289
|
-
/**
|
|
290
|
-
* A synchronous disposal attempt on a container whose `onDeactivation` handlers may be async.
|
|
291
|
-
*
|
|
292
|
-
* @since 0.3.16-canary.0
|
|
293
|
-
*/
|
|
294
|
-
export class SyncDisposalNotSupportedError extends DiError {
|
|
295
|
-
name = "SyncDisposalNotSupportedError";
|
|
296
|
-
code = "SYNC_DISPOSAL_NOT_SUPPORTED";
|
|
297
|
-
constructor() {
|
|
298
|
-
super("Container cannot be disposed synchronously because onDeactivation handlers may be async. Use `await using` or call container.dispose() explicitly.");
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
299
|
/**
|
|
302
300
|
* A `scoped` binding resolved from a container with no child scope context.
|
|
303
301
|
*
|
|
@@ -337,10 +335,11 @@ export class MissingContainerContextError extends DiError {
|
|
|
337
335
|
}
|
|
338
336
|
}
|
|
339
337
|
/**
|
|
340
|
-
* A second `to*()` on a chain that already registered its binding.
|
|
338
|
+
* A slot step or a second `to*()` on a chain that already registered its binding.
|
|
341
339
|
*
|
|
342
|
-
* @remarks A chain is its binding
|
|
343
|
-
* calls, the second of which displaces the
|
|
340
|
+
* @remarks A chain is its binding and registers exactly once, in its final shape, so its slot is
|
|
341
|
+
* settled by `to*()`; a token bound twice is two `bind()` calls, the second of which displaces the
|
|
342
|
+
* first under slot last-wins.
|
|
344
343
|
*
|
|
345
344
|
* @since 0.10.0
|
|
346
345
|
*/
|
|
@@ -349,7 +348,7 @@ export class ChainAlreadyRegisteredError extends DiError {
|
|
|
349
348
|
code = "CHAIN_ALREADY_REGISTERED";
|
|
350
349
|
tokenName;
|
|
351
350
|
constructor(tokenName) {
|
|
352
|
-
super(`The binding for token '${tokenName}' is already registered on this chain.
|
|
351
|
+
super(`The binding for token '${tokenName}' is already registered on this chain, so its slot is final. Declare when*() and many() before to*(), or call bind() again to register another binding for the token.`);
|
|
353
352
|
this.tokenName = tokenName;
|
|
354
353
|
}
|
|
355
354
|
}
|
|
@@ -371,11 +370,11 @@ export class ManyBindingSlotError extends DiError {
|
|
|
371
370
|
}
|
|
372
371
|
}
|
|
373
372
|
/**
|
|
374
|
-
* A fluent chain
|
|
373
|
+
* A scope, a lifecycle hook or `id()` asked of a fluent chain before a `to*()` call registered its binding.
|
|
375
374
|
*
|
|
376
375
|
* @remarks The builder types make this unreachable from TypeScript — `bind()` returns
|
|
377
|
-
* `BindToBuilder`, which exposes only `to*()`. It exists for JavaScript callers and
|
|
378
|
-
* casts past the types, so the misuse fails loudly instead of mutating nothing.
|
|
376
|
+
* `BindToBuilder`, which exposes only the slot steps and `to*()`. It exists for JavaScript callers and
|
|
377
|
+
* for anyone who casts past the types, so the misuse fails loudly instead of mutating nothing.
|
|
379
378
|
*
|
|
380
379
|
* @since 0.10.0
|
|
381
380
|
*/
|
|
@@ -455,6 +454,25 @@ export class SymbolKeyedLifecycleError extends DiError {
|
|
|
455
454
|
this.memberName = memberName;
|
|
456
455
|
}
|
|
457
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* A `@postConstruct` or `@preDestroy` on a private method, which the lifecycle reader cannot call by name.
|
|
459
|
+
*
|
|
460
|
+
* @remarks Reported at decoration rather than at resolve, so the error points at the declaration
|
|
461
|
+
* instead of a `MetadataReader` the caller never configured.
|
|
462
|
+
*
|
|
463
|
+
* @since 0.13.0
|
|
464
|
+
*/
|
|
465
|
+
export class PrivateLifecycleMethodError extends DiError {
|
|
466
|
+
name = "PrivateLifecycleMethodError";
|
|
467
|
+
code = "PRIVATE_LIFECYCLE_METHOD";
|
|
468
|
+
decoratorName;
|
|
469
|
+
memberName;
|
|
470
|
+
constructor(decoratorName, memberName) {
|
|
471
|
+
super(`@${decoratorName}() does not support a private method ('${memberName}'). Make the lifecycle method public, and call the private code from it.`);
|
|
472
|
+
this.decoratorName = decoratorName;
|
|
473
|
+
this.memberName = memberName;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
458
476
|
/**
|
|
459
477
|
* A decorator handed no metadata object, because the runtime has no `Symbol.metadata` to key one by.
|
|
460
478
|
*
|