@codefast/di 0.10.1 → 0.11.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 +265 -0
- package/README.md +69 -6
- package/dist/ambient/active-container.d.ts +7 -2
- package/dist/ambient/active-container.js +6 -1
- package/dist/container/binding-builders.d.ts +19 -1
- package/dist/container/binding-builders.js +75 -16
- package/dist/container/container.js +224 -47
- package/dist/core/binding-declaration.d.ts +120 -0
- package/dist/core/binding-declaration.js +186 -0
- package/dist/core/binding.d.ts +57 -5
- package/dist/core/binding.js +48 -1
- package/dist/core/module.d.ts +7 -4
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +11 -7
- package/dist/core/registry.js +122 -38
- package/dist/core/state-epoch.d.ts +18 -1
- package/dist/core/state-epoch.js +17 -0
- package/dist/core/tag.js +1 -1
- package/dist/decorators/decorator-metadata.d.ts +9 -0
- package/dist/decorators/decorator-metadata.js +20 -0
- package/dist/decorators/inject.js +2 -1
- package/dist/decorators/injectable.js +3 -1
- package/dist/decorators/lifecycle-decorators.js +8 -2
- package/dist/errors/errors.d.ts +77 -3
- package/dist/errors/errors.js +93 -10
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/injection/descriptor.js +3 -7
- package/dist/injection/resolve-options.js +6 -4
- package/dist/introspection/dependency-graph.d.ts +7 -2
- package/dist/introspection/dependency-graph.js +46 -23
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.js +6 -9
- package/dist/lifecycle/lifecycle-manager.js +14 -2
- package/dist/lifecycle/scope-manager.js +28 -10
- package/dist/metadata/verifying-metadata-reader.d.ts +4 -3
- package/dist/metadata/verifying-metadata-reader.js +28 -6
- package/dist/resolution/async-fan-out.d.ts +12 -0
- package/dist/resolution/async-fan-out.js +26 -0
- package/dist/resolution/cache/activation-need.d.ts +0 -1
- package/dist/resolution/cache/activation-need.js +11 -18
- package/dist/resolution/cache/binding-lookup-cache.d.ts +0 -7
- package/dist/resolution/cache/binding-lookup-cache.js +30 -17
- package/dist/resolution/cache/class-introspector.d.ts +11 -0
- package/dist/resolution/cache/class-introspector.js +18 -0
- package/dist/resolution/context.d.ts +15 -23
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path/resolution-path.d.ts +48 -13
- package/dist/resolution/path/resolution-path.js +89 -38
- package/dist/resolution/plan/instantiation-plan.js +61 -21
- package/dist/resolution/plan/plan-codegen.d.ts +7 -4
- package/dist/resolution/plan/plan-codegen.js +60 -32
- package/dist/resolution/resolver.d.ts +4 -5
- package/dist/resolution/resolver.js +288 -258
- package/package.json +14 -2
package/dist/core/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindingSlotEquals,
|
|
1
|
+
import { bindingSlotEquals, UNREGISTERED_ORDER, writableMembership, writablePredicate } 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([]);
|
|
@@ -16,15 +16,29 @@ function createTokenRecord(bindings) {
|
|
|
16
16
|
*
|
|
17
17
|
* @since 0.3.16-canary.0
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* One shared empty map answers every read of a registry nothing was bound into, so a container
|
|
21
|
+
* that only ever resolves through its parent — every per-request child — allocates no map.
|
|
22
|
+
*/
|
|
23
|
+
const EMPTY_LONE = new Map();
|
|
24
|
+
// Process-wide, so registration order compares across every registry a binding could be restored into.
|
|
25
|
+
let registrationCounter = 0;
|
|
26
|
+
/**
|
|
27
|
+
* @since 0.11.0
|
|
28
|
+
*/
|
|
19
29
|
export class BindingRegistry {
|
|
20
30
|
// Monotonic mutation counter — lets resolvers version-stamp lookup caches across a container chain.
|
|
21
31
|
#version = 0;
|
|
22
32
|
// The common token lives here and nowhere else: exactly one default-slot binding, so the hot read
|
|
23
33
|
// of every resolve is a bare `Map.get` and a plain bind is one map write.
|
|
24
|
-
#lone =
|
|
34
|
+
#lone = EMPTY_LONE;
|
|
25
35
|
// Every other token — several bindings, a tagged slot, a predicate — has a record here, and a
|
|
26
36
|
// token is in exactly one of the two maps. Allocated by the first token that needs a record.
|
|
27
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;
|
|
28
42
|
// Built on the first id-keyed read and maintained from then on: a bind-and-resolve container
|
|
29
43
|
// never asks by id, so it never pays for the second map.
|
|
30
44
|
#byId;
|
|
@@ -74,46 +88,62 @@ export class BindingRegistry {
|
|
|
74
88
|
*/
|
|
75
89
|
add(binding) {
|
|
76
90
|
this.#bump();
|
|
91
|
+
if (binding.registrationOrder === UNREGISTERED_ORDER) {
|
|
92
|
+
registrationCounter += 1;
|
|
93
|
+
binding.registrationOrder = registrationCounter;
|
|
94
|
+
}
|
|
77
95
|
if (binding.kind === "constant") {
|
|
78
96
|
this.#heldConstantBinding = true;
|
|
79
97
|
}
|
|
80
98
|
const key = binding.token;
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
|
|
99
|
+
const records = this.#records;
|
|
100
|
+
if (records !== undefined) {
|
|
101
|
+
const record = records.get(key);
|
|
102
|
+
if (record !== undefined) {
|
|
103
|
+
this.#lastAdded = binding;
|
|
104
|
+
this.#lastAddedRecord = record;
|
|
105
|
+
return this.#addToRecord(key, record, binding);
|
|
106
|
+
}
|
|
84
107
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
const occupant = this.#lone.get(key);
|
|
109
|
+
this.#lastAdded = binding;
|
|
110
|
+
if (occupant === undefined) {
|
|
111
|
+
// The common bind: a fresh token taking the lone seat, one probe and one write.
|
|
112
|
+
if (this.#byId !== undefined) {
|
|
113
|
+
this.#byId.set(binding.identifier, binding);
|
|
114
|
+
}
|
|
88
115
|
if (isDefaultSlotBinding(binding)) {
|
|
89
|
-
this.#
|
|
116
|
+
this.#setLone(key, binding);
|
|
117
|
+
this.#lastAddedRecord = undefined;
|
|
90
118
|
}
|
|
91
119
|
else {
|
|
92
|
-
this.#createRecord(key, [binding]);
|
|
120
|
+
this.#lastAddedRecord = this.#createRecord(key, [binding]);
|
|
93
121
|
}
|
|
94
122
|
return undefined;
|
|
95
123
|
}
|
|
96
124
|
// Same slot, last wins: the newcomer takes the lone seat and nothing else moves.
|
|
97
125
|
if (isDefaultSlotBinding(binding)) {
|
|
98
|
-
this.#
|
|
126
|
+
this.#setLone(key, binding);
|
|
127
|
+
this.#lastAddedRecord = undefined;
|
|
99
128
|
if (this.#byId !== undefined) {
|
|
100
|
-
this.#byId.delete(
|
|
129
|
+
this.#byId.delete(occupant.identifier);
|
|
101
130
|
this.#byId.set(binding.identifier, binding);
|
|
102
131
|
}
|
|
103
|
-
return
|
|
132
|
+
return occupant;
|
|
104
133
|
}
|
|
105
134
|
// A second shape joins the token, which is what a record is for.
|
|
106
|
-
this.#
|
|
135
|
+
this.#deleteLone(key);
|
|
107
136
|
this.#byId?.set(binding.identifier, binding);
|
|
108
|
-
this.#createRecord(key, [
|
|
137
|
+
this.#lastAddedRecord = this.#createRecord(key, occupant.registrationOrder < binding.registrationOrder ? [occupant, binding] : [binding, occupant]);
|
|
109
138
|
return undefined;
|
|
110
139
|
}
|
|
111
140
|
/** Remove all bindings for a token. Returns removed bindings. */
|
|
112
141
|
removeByToken(token) {
|
|
113
142
|
this.#bump();
|
|
143
|
+
this.#lastAdded = undefined;
|
|
114
144
|
const lone = this.#lone.get(token);
|
|
115
145
|
if (lone !== undefined) {
|
|
116
|
-
this.#
|
|
146
|
+
this.#deleteLone(token);
|
|
117
147
|
this.#byId?.delete(lone.identifier);
|
|
118
148
|
return [lone];
|
|
119
149
|
}
|
|
@@ -138,10 +168,11 @@ export class BindingRegistry {
|
|
|
138
168
|
return undefined;
|
|
139
169
|
}
|
|
140
170
|
this.#bump();
|
|
171
|
+
this.#lastAdded = undefined;
|
|
141
172
|
byId.delete(id);
|
|
142
173
|
const key = binding.token;
|
|
143
174
|
if (this.#lone.get(key)?.identifier === id) {
|
|
144
|
-
this.#
|
|
175
|
+
this.#deleteLone(key);
|
|
145
176
|
return binding;
|
|
146
177
|
}
|
|
147
178
|
const record = this.#records?.get(key);
|
|
@@ -179,14 +210,6 @@ export class BindingRegistry {
|
|
|
179
210
|
getRecorded(token) {
|
|
180
211
|
return this.#records?.get(token)?.bindings ?? NO_BINDINGS;
|
|
181
212
|
}
|
|
182
|
-
/** How many bindings a token holds, without materialising a lone binding's list. */
|
|
183
|
-
countBindings(token) {
|
|
184
|
-
const record = this.#records?.get(token);
|
|
185
|
-
if (record !== undefined) {
|
|
186
|
-
return record.bindings.length;
|
|
187
|
-
}
|
|
188
|
-
return this.#lone.has(token) ? 1 : 0;
|
|
189
|
-
}
|
|
190
213
|
/** Get binding by ID. */
|
|
191
214
|
getById(id) {
|
|
192
215
|
return this.#ensureById().get(id);
|
|
@@ -213,8 +236,9 @@ export class BindingRegistry {
|
|
|
213
236
|
/** Remove all bindings. Returns all removed. */
|
|
214
237
|
clear() {
|
|
215
238
|
this.#bump();
|
|
239
|
+
this.#lastAdded = undefined;
|
|
216
240
|
const all = this.allBindings();
|
|
217
|
-
this.#
|
|
241
|
+
this.#clearLone();
|
|
218
242
|
this.#records?.clear();
|
|
219
243
|
this.#byId?.clear();
|
|
220
244
|
return all;
|
|
@@ -260,6 +284,26 @@ export class BindingRegistry {
|
|
|
260
284
|
getMultiTagged(token, criterion) {
|
|
261
285
|
return this.#records?.get(token)?.multi?.get(criterion);
|
|
262
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Whether any binding for the token carries a `when()` predicate.
|
|
289
|
+
*
|
|
290
|
+
* @remarks Only a token that keeps a record can hold a predicate — a lone binding is default-slot
|
|
291
|
+
* with none — so an index fast lane consults this to decline to full selection when the
|
|
292
|
+
* more-specific rule's predicate step could apply.
|
|
293
|
+
*/
|
|
294
|
+
hasPredicateCandidate(token) {
|
|
295
|
+
const bindings = this.getRecorded(token);
|
|
296
|
+
for (let index = 0; index < bindings.length; index += 1) {
|
|
297
|
+
if (bindings[index].predicate !== undefined) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
/** The binding holding a token's default slot — lone or recorded — or `undefined` when none does. */
|
|
304
|
+
getDefaultSlotBinding(token) {
|
|
305
|
+
return this.#lone.get(token) ?? this.#records?.get(token)?.defaultOccupant;
|
|
306
|
+
}
|
|
263
307
|
/** A token's lone default-slot binding — the first read of every synchronous resolve. */
|
|
264
308
|
getFastDefault(token) {
|
|
265
309
|
return this.#lone.get(token);
|
|
@@ -276,7 +320,8 @@ export class BindingRegistry {
|
|
|
276
320
|
const key = binding.token;
|
|
277
321
|
if (this.#lone.get(key) === binding) {
|
|
278
322
|
this.#bump();
|
|
279
|
-
this.#
|
|
323
|
+
this.#lastAdded = undefined;
|
|
324
|
+
this.#deleteLone(key);
|
|
280
325
|
}
|
|
281
326
|
else {
|
|
282
327
|
const record = this.#records?.get(key);
|
|
@@ -285,6 +330,7 @@ export class BindingRegistry {
|
|
|
285
330
|
return false;
|
|
286
331
|
}
|
|
287
332
|
this.#bump();
|
|
333
|
+
this.#lastAdded = undefined;
|
|
288
334
|
// Replaced, never spliced: a walk holding the current array must not lose its place.
|
|
289
335
|
record.bindings = record.bindings.toSpliced(index, 1);
|
|
290
336
|
this.#deindexSlot(record, binding);
|
|
@@ -301,6 +347,18 @@ export class BindingRegistry {
|
|
|
301
347
|
this.#bump();
|
|
302
348
|
// Set before any (re)indexing so `#indexSlot` sees a member and leaves it out of every slot.
|
|
303
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
|
+
}
|
|
304
362
|
if (this.#promoteLoneToRecord(binding.token, binding)) {
|
|
305
363
|
return;
|
|
306
364
|
}
|
|
@@ -338,7 +396,7 @@ export class BindingRegistry {
|
|
|
338
396
|
if (this.#lone.get(key) !== binding) {
|
|
339
397
|
return false;
|
|
340
398
|
}
|
|
341
|
-
this.#
|
|
399
|
+
this.#deleteLone(key);
|
|
342
400
|
this.#createRecord(key, [binding]);
|
|
343
401
|
return true;
|
|
344
402
|
}
|
|
@@ -350,10 +408,6 @@ export class BindingRegistry {
|
|
|
350
408
|
}
|
|
351
409
|
return record;
|
|
352
410
|
}
|
|
353
|
-
/** Summarize available slot strings for a token (for error messages). */
|
|
354
|
-
availableSlotStrings(token) {
|
|
355
|
-
return this.getAll(token).map((binding) => bindingSlotToString(binding.slot));
|
|
356
|
-
}
|
|
357
411
|
#createRecord(key, bindings) {
|
|
358
412
|
const record = createTokenRecord(bindings);
|
|
359
413
|
(this.#records ??= new Map()).set(key, record);
|
|
@@ -388,12 +442,10 @@ export class BindingRegistry {
|
|
|
388
442
|
}
|
|
389
443
|
// A selection may be walking this list inside a `when()` predicate. An append past the length it
|
|
390
444
|
// read cannot shift it, so it lands in place; a displacement replaces the array instead.
|
|
391
|
-
if (displacedBinding
|
|
392
|
-
record.bindings.
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
record.bindings = [...record.bindings.filter((candidate) => candidate !== displacedBinding), binding];
|
|
445
|
+
if (displacedBinding !== undefined) {
|
|
446
|
+
record.bindings = record.bindings.filter((candidate) => candidate !== displacedBinding);
|
|
396
447
|
}
|
|
448
|
+
insertInRegistrationOrder(record, binding);
|
|
397
449
|
this.#byId?.set(binding.identifier, binding);
|
|
398
450
|
this.#indexSlot(record, binding);
|
|
399
451
|
this.#settle(key, record);
|
|
@@ -407,8 +459,22 @@ export class BindingRegistry {
|
|
|
407
459
|
}
|
|
408
460
|
else if (bindings.length === 1 && isDefaultSlotBinding(bindings[0])) {
|
|
409
461
|
this.#records.delete(key);
|
|
410
|
-
this.#
|
|
462
|
+
this.#setLone(key, bindings[0]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Every write to the lone map goes through these three, so a cache of its entries has one place to follow them.
|
|
466
|
+
#setLone(key, binding) {
|
|
467
|
+
let lone = this.#lone;
|
|
468
|
+
if (lone === EMPTY_LONE) {
|
|
469
|
+
lone = this.#lone = new Map();
|
|
411
470
|
}
|
|
471
|
+
lone.set(key, binding);
|
|
472
|
+
}
|
|
473
|
+
#deleteLone(key) {
|
|
474
|
+
this.#lone.delete(key);
|
|
475
|
+
}
|
|
476
|
+
#clearLone() {
|
|
477
|
+
this.#lone.clear();
|
|
412
478
|
}
|
|
413
479
|
#ensureById() {
|
|
414
480
|
if (this.#byId === undefined) {
|
|
@@ -474,6 +540,24 @@ export class BindingRegistry {
|
|
|
474
540
|
}
|
|
475
541
|
}
|
|
476
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Places a binding in its record by registration order: a fresh registration appends, and a binding a
|
|
545
|
+
* chain took out and put back returns to its place.
|
|
546
|
+
*/
|
|
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
|
+
}
|
|
477
561
|
/** A binding nothing has to be matched against: the default slot, no predicate, not a collection member. */
|
|
478
562
|
function isDefaultSlotBinding(binding) {
|
|
479
563
|
return binding.slot.tags.length === 0 && binding.predicate === undefined && !binding.isMany;
|
|
@@ -13,4 +13,21 @@ export declare function stateEpoch(): number;
|
|
|
13
13
|
*
|
|
14
14
|
* @since 0.10.0
|
|
15
15
|
*/
|
|
16
|
-
export declare function advanceStateEpoch(): void;
|
|
16
|
+
export declare function advanceStateEpoch(): void;
|
|
17
|
+
/**
|
|
18
|
+
* The container-disposal counter, held in a cell so a hot reader dereferences a field rather than
|
|
19
|
+
* paying a cross-module call — apart from {@link stateEpoch} so a per-request child's `dispose()`
|
|
20
|
+
* does not invalidate the chain-version memo that keeps deep resolves cheap. A child compares it to
|
|
21
|
+
* learn whether an ancestor was disposed, re-walking the chain only after a disposal somewhere.
|
|
22
|
+
*
|
|
23
|
+
* @since 0.11.0
|
|
24
|
+
*/
|
|
25
|
+
export declare const disposeEpochRef: {
|
|
26
|
+
value: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Advances the dispose epoch; a container's teardown calls it so descendants re-check the chain.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.11.0
|
|
32
|
+
*/
|
|
33
|
+
export declare function advanceDisposeEpoch(): void;
|
package/dist/core/state-epoch.js
CHANGED
|
@@ -18,4 +18,21 @@ export function stateEpoch() {
|
|
|
18
18
|
*/
|
|
19
19
|
export function advanceStateEpoch() {
|
|
20
20
|
epoch += 1;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The container-disposal counter, held in a cell so a hot reader dereferences a field rather than
|
|
24
|
+
* paying a cross-module call — apart from {@link stateEpoch} so a per-request child's `dispose()`
|
|
25
|
+
* does not invalidate the chain-version memo that keeps deep resolves cheap. A child compares it to
|
|
26
|
+
* learn whether an ancestor was disposed, re-walking the chain only after a disposal somewhere.
|
|
27
|
+
*
|
|
28
|
+
* @since 0.11.0
|
|
29
|
+
*/
|
|
30
|
+
export const disposeEpochRef = { value: 0 };
|
|
31
|
+
/**
|
|
32
|
+
* Advances the dispose epoch; a container's teardown calls it so descendants re-check the chain.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.11.0
|
|
35
|
+
*/
|
|
36
|
+
export function advanceDisposeEpoch() {
|
|
37
|
+
disposeEpochRef.value += 1;
|
|
21
38
|
}
|
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
|
-
*
|
|
3
|
+
* The bits a tag-key mask holds: the width of a 32-bit integer, a constant of the machine.
|
|
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.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the metadata record a decorator context carries.
|
|
3
|
+
*
|
|
4
|
+
* @throws `MissingDecoratorMetadataError` when the transpiler handed none, which is how a runtime
|
|
5
|
+
* without `Symbol.metadata` shows up.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.11.0
|
|
8
|
+
*/
|
|
9
|
+
export declare function decoratorMetadataOf(context: DecoratorContext, decoratorName: string): Record<string | symbol, unknown>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The metadata record a decorator writes into, checked once for every decorator the library ships.
|
|
3
|
+
*/
|
|
4
|
+
import { MissingDecoratorMetadataError } from "#errors/errors";
|
|
5
|
+
/**
|
|
6
|
+
* Returns the metadata record a decorator context carries.
|
|
7
|
+
*
|
|
8
|
+
* @throws `MissingDecoratorMetadataError` when the transpiler handed none, which is how a runtime
|
|
9
|
+
* without `Symbol.metadata` shows up.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.11.0
|
|
12
|
+
*/
|
|
13
|
+
export function decoratorMetadataOf(context, decoratorName) {
|
|
14
|
+
// Widened, not asserted: the lib types promise a record that a runtime without `Symbol.metadata` never hands over.
|
|
15
|
+
const metadata = context.metadata;
|
|
16
|
+
if (metadata === undefined || metadata === null) {
|
|
17
|
+
throw new MissingDecoratorMetadataError(decoratorName);
|
|
18
|
+
}
|
|
19
|
+
return metadata;
|
|
20
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getActiveContainer, getAmbientResolution } from "#ambient/active-container";
|
|
2
|
+
import { decoratorMetadataOf } from "#decorators/decorator-metadata";
|
|
2
3
|
import { MissingContainerContextError, StaticMemberDecoratorError } from "#errors/errors";
|
|
3
4
|
import { buildInjectionDescriptor } from "#injection/descriptor";
|
|
4
5
|
import { injectionSlotToResolveOptions } from "#injection/resolve-options";
|
|
@@ -36,7 +37,7 @@ export function inject(token, options) {
|
|
|
36
37
|
if (context.static) {
|
|
37
38
|
throw new StaticMemberDecoratorError("inject", String(context.name));
|
|
38
39
|
}
|
|
39
|
-
const meta = context
|
|
40
|
+
const meta = decoratorMetadataOf(context, "inject");
|
|
40
41
|
// Own bucket only: the metadata record inherits the base class's, and pushing into an inherited
|
|
41
42
|
// array would register this accessor on the base class instead.
|
|
42
43
|
if (!Object.hasOwn(meta, INJECT_ACCESSOR_KEY) || !Array.isArray(meta[INJECT_ACCESSOR_KEY])) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/decorator-metadata";
|
|
1
2
|
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
2
3
|
import { INJECTABLE_KEY } from "#metadata/metadata-keys";
|
|
3
4
|
/**
|
|
@@ -23,6 +24,7 @@ export function createAutoRegisterRegistry() {
|
|
|
23
24
|
*/
|
|
24
25
|
export function injectable(deps, options) {
|
|
25
26
|
return function (target, context) {
|
|
27
|
+
const meta = decoratorMetadataOf(context, "injectable");
|
|
26
28
|
const parameterMetadataList = (deps ?? []).map((dependency, index) => {
|
|
27
29
|
const descriptor = normalizeToDescriptor(dependency);
|
|
28
30
|
const baseParameterMetadata = {
|
|
@@ -44,7 +46,7 @@ export function injectable(deps, options) {
|
|
|
44
46
|
});
|
|
45
47
|
// Field decorators run before the class decorator — accessor @inject entries are
|
|
46
48
|
// already on context.metadata by the time this runs.
|
|
47
|
-
|
|
49
|
+
meta[INJECTABLE_KEY] = {
|
|
48
50
|
params: parameterMetadataList,
|
|
49
51
|
};
|
|
50
52
|
if (options?.autoRegister !== undefined) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decoratorMetadataOf } from "#decorators/decorator-metadata";
|
|
2
|
+
import { StaticMemberDecoratorError, SymbolKeyedLifecycleError } from "#errors/errors";
|
|
2
3
|
import { LIFECYCLE_KEY } from "#metadata/metadata-keys";
|
|
3
4
|
/** Records the decorated method under one lifecycle phase; both decorators differ only in that phase. */
|
|
4
5
|
function recordLifecycleMethod(phase) {
|
|
@@ -6,7 +7,12 @@ function recordLifecycleMethod(phase) {
|
|
|
6
7
|
if (context.static) {
|
|
7
8
|
throw new StaticMemberDecoratorError(phase, String(context.name));
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
// The lifecycle reader keys methods by their string name, so a symbol-keyed method can never be
|
|
11
|
+
// found again — fail here, where the declaration is, rather than at resolve.
|
|
12
|
+
if (typeof context.name === "symbol") {
|
|
13
|
+
throw new SymbolKeyedLifecycleError(phase, String(context.name));
|
|
14
|
+
}
|
|
15
|
+
const meta = decoratorMetadataOf(context, phase);
|
|
10
16
|
// Own bucket only: `context.metadata` inherits the base class's record, and writing through an
|
|
11
17
|
// inherited bucket would register this hook on the base class instead.
|
|
12
18
|
if (!Object.hasOwn(meta, LIFECYCLE_KEY)) {
|