@codefast/di 0.11.0 → 0.12.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 +111 -0
- package/README.md +26 -12
- 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 -2
- package/dist/container/container.js +107 -64
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +7 -0
- package/dist/core/binding-declaration.js +3 -2
- 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.js +5 -5
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +3 -3
- package/dist/decorators/{decorator-metadata.js → metadata-record.js} +1 -1
- package/dist/{errors/errors.d.ts → errors.d.ts} +7 -6
- package/dist/{errors/errors.js → errors.js} +8 -7
- 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 +151 -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} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +45 -28
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +8 -19
- 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.js → path.js} +1 -1
- 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 +7 -7
- package/dist/resolution/resolver.js +308 -121
- 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 +79 -71
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- /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/{lifecycle-decorators.d.ts → lifecycle.d.ts} +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-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
- /package/dist/resolution/{path/resolution-path.d.ts → path.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# @codefast/di
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1010](https://github.com/codefastlabs/codefast/pull/1010) **Breaking:** `resolveAsync`, `resolveOptionalAsync` and `resolveAllAsync` on a disposed container — or on a child of
|
|
8
|
+
one — now return a promise rejected with `DisposedContainerError` instead of throwing it, and `resolveAllAsync(token)`
|
|
9
|
+
likewise rejects when a `when()` predicate throws or an alias cycle is followed, so every async method reports its
|
|
10
|
+
failure through the promise it returns. Code that awaits these calls, or chains `.catch()`, behaves as before or better;
|
|
11
|
+
only a caller that wrapped the call itself in `try`/`catch` without awaiting it no longer sees the error there, and
|
|
12
|
+
should await it instead.
|
|
13
|
+
|
|
14
|
+
- [#995](https://github.com/codefastlabs/codefast/pull/995) Add `container.explain(token, options)`, which answers why a request selects the binding it does without instantiating
|
|
15
|
+
anything. It reads the container chain the way `resolve` does and decides by the same rules, so its answer is the
|
|
16
|
+
engine's own. It reports each registry it read with every candidate and its verdict (`eligible`, `slot-mismatch`,
|
|
17
|
+
`predicate-refused`, `collection-member`), the rule that decided (`sole-candidate`, `sole-predicate`, `most-criteria`,
|
|
18
|
+
`default-alias`, `ambiguous`), the binding the request ends on after every alias, and an `outcome` naming the error
|
|
19
|
+
`resolve` would throw when it ends on none. `when()` predicates run as they would in `resolve`, and `options.ancestors`
|
|
20
|
+
explains a request made from inside other resolutions, so a predicate that reads the parent sees the one it would.
|
|
21
|
+
|
|
22
|
+
- [#999](https://github.com/codefastlabs/codefast/pull/999) **Breaking:** a binding's slot is declared before its strategy —
|
|
23
|
+
`bind(Logger).whenNamed("file").to(FileLogger).singleton()`. `when()`, `whenNamed()`, `whenTagged()`, `whenDefault()`
|
|
24
|
+
and `many()` now come before `to*()` and are refused after it with `ChainAlreadyRegisteredError`, so a binding registers
|
|
25
|
+
once, in its final slot, and no later step moves it, displaces something else or brings back what it displaced. Scope
|
|
26
|
+
and lifecycle hooks stay after `to*()`. To migrate, move each chain's slot steps in front of its `to*()` call.
|
|
27
|
+
|
|
28
|
+
The registry no longer parks the bindings a re-slot displaced or restores them, so a bind with a name or a tag costs a
|
|
29
|
+
quarter to two fifths less. `BindingBuilder`, `ConstantBindingBuilder` and `AliasBindingBuilder` lose their `Names` type
|
|
30
|
+
parameter, since the slot is settled before they exist.
|
|
31
|
+
|
|
32
|
+
- [#1005](https://github.com/codefastlabs/codefast/pull/1005) **Breaking for deep subpath imports only** — the root entry `@codefast/di` is unchanged. `src/` now follows three rules,
|
|
33
|
+
and the published subpaths follow `src/` with no exception: a directory names a family of two or more modules and a lone
|
|
34
|
+
module sits flat at its layer; a directory carries the topic and a file its role, so no file repeats its directory's
|
|
35
|
+
name; and the exports map is the source tree, so the `strip` that kept the introspection modules at flat specifiers is
|
|
36
|
+
gone.
|
|
37
|
+
|
|
38
|
+
Moves: the `MetadataReader` verification leaves `resolution/cache/class-introspector` for `metadata/verify` — it was the
|
|
39
|
+
one value import pointing up from `metadata/` into `resolution/`; the `RESOLUTION_DIAGNOSTICS` channel leaves `errors/`
|
|
40
|
+
for `introspection/diagnostics`; the fluent chain's contract leaves `core/binding` for `core/binding-builders`, and the
|
|
41
|
+
class implementing it is `container/binding-chain`; `effectiveBindingScope` folds into `core/binding`.
|
|
42
|
+
|
|
43
|
+
Renamed specifiers: `./errors/errors` → `./errors`; `./errors/diagnostics` → `./introspection/diagnostics`;
|
|
44
|
+
`./ambient/active-container` → `./ambient-container`; `./container/binding-builders` → `./container/binding-chain`;
|
|
45
|
+
`./core/binding-scope` → gone (`effectiveBindingScope` is in `./core/binding`); `./injection/resolve-options` →
|
|
46
|
+
`./injection/dependency-slot`;
|
|
47
|
+
`./metadata/{metadata-keys,metadata-types,metadata-reader-token,symbol-metadata-reader,verifying-metadata-reader}` →
|
|
48
|
+
`./metadata/{keys,types,reader-token,symbol-reader,verifying-reader}`; `./lifecycle/{lifecycle-manager,scope-manager}` →
|
|
49
|
+
`./lifecycle/{hooks,scopes}`; `./decorators/{lifecycle-decorators,decorator-metadata}` →
|
|
50
|
+
`./decorators/{lifecycle,metadata-record}`; `./resolution/path/resolution-path` → `./resolution/path`;
|
|
51
|
+
`./resolution/plan/{instantiation-plan,plan-codegen}` → `./resolution/plan/{compiler,codegen}`;
|
|
52
|
+
`./resolution/select/binding-select` → `./resolution/select/candidates`; `./resolution/cache/binding-lookup-cache` →
|
|
53
|
+
`./resolution/cache/lookup`; `./{inspector,explanation,dependency-graph,graph-adapters/*}` →
|
|
54
|
+
`./introspection/{inspector,explanation,dependency-graph,graph-adapters/*}`. New: `./core/binding-builders`,
|
|
55
|
+
`./metadata/verify`.
|
|
56
|
+
|
|
57
|
+
`@codefast/di-testing` follows the one specifier it imports, `metadata/verifying-reader`.
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- [#996](https://github.com/codefastlabs/codefast/pull/996) A request with criteria that matches no slot is fast again when the container holds no alias: `resolveOptional()` of a
|
|
62
|
+
missing tagged slot no longer probes for a default-slot alias to forward to in a registry that never held one.
|
|
63
|
+
|
|
64
|
+
- [#1009](https://github.com/codefastlabs/codefast/pull/1009) `resolveAsync` no longer rejects with `AsyncResolutionError` when a later sibling reads a singleton or scoped binding
|
|
65
|
+
synchronously — a `toDynamic` factory's `ctx.resolve`, an `@inject` accessor, a top-level `resolve` — while the async
|
|
66
|
+
lane is still materialising it from an earlier sibling. A level of the async lane now yields only where a dependency or
|
|
67
|
+
hook really awaits, so an instance built without yielding is cached before the next sibling starts, and only a
|
|
68
|
+
materialisation that is genuinely pending is published in flight.
|
|
69
|
+
|
|
70
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A scope refinement costs less at bind time. `singleton()`, `transient()` and `scoped()` now do nothing when the scope
|
|
71
|
+
does not change, and release only what exists when it does, so `bind(T).to(X).singleton()` straight after registration
|
|
72
|
+
no longer calls into the scope manager.
|
|
73
|
+
|
|
74
|
+
- [#997](https://github.com/codefastlabs/codefast/pull/997) Every read on a child container is cheaper. The child's check that no ancestor was disposed is now one epoch compare
|
|
75
|
+
that V8 inlines into each entry point, the walk up the chain runs only after some container was disposed, and a new
|
|
76
|
+
child starts confirmed live, so a per-request child skips the walk on its first read.
|
|
77
|
+
|
|
78
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) Resolving a class binding for the first time is cheaper. The metadata cache now keeps one record per class, which a cold
|
|
79
|
+
resolve looks up once instead of reading a separate map for each fact, and a class with no constructor parameters is
|
|
80
|
+
built without allocating an empty argument list. This mostly speeds up containers that bind and resolve many class
|
|
81
|
+
singletons at startup.
|
|
82
|
+
|
|
83
|
+
- [#1001](https://github.com/codefastlabs/codefast/pull/1001) The first resolve of a class binding is cheaper. The class's parameter list, and the check that a subclass is not
|
|
84
|
+
dropping its base's declared dependencies, are settled once per class instead of once per instantiation, and the class
|
|
85
|
+
asked about last is answered without a map lookup, so a hundred singletons of one class bind and materialize about a
|
|
86
|
+
fifth faster.
|
|
87
|
+
|
|
88
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A root-level `resolveAll()` read in a loop over the same token, as an event bus does for every event, is cheaper: the
|
|
89
|
+
lookup cache keeps the last collection in front of its map, so a repeat read skips the map lookup.
|
|
90
|
+
|
|
91
|
+
- [#997](https://github.com/codefastlabs/codefast/pull/997) A container that reads a collection once no longer builds a value list it never reads again. A root-level `resolveAll`
|
|
92
|
+
keeps its stable value list from the second read on, the way an instantiation plan compiles on repeat, so a fresh
|
|
93
|
+
container per request pays only for the members it resolves.
|
|
94
|
+
|
|
95
|
+
- [#996](https://github.com/codefastlabs/codefast/pull/996) A resolve that carries options on a root container gets back much of the speed 0.11.0 took from it. The check that
|
|
96
|
+
refuses a disposed chain grew in that release enough that V8 stopped inlining the lookup behind that entry point; a live
|
|
97
|
+
root now pays two field reads for it, and only a child reads the dispose epoch.
|
|
98
|
+
|
|
99
|
+
- [#998](https://github.com/codefastlabs/codefast/pull/998) A root container's `resolve(token, options)` no longer pays for the check a child makes that no ancestor was disposed.
|
|
100
|
+
Each container is created with the `resolve` of its role, so the root's never carries the child's path and the rule that
|
|
101
|
+
a disposed ancestor refuses its descendants stays as it was.
|
|
102
|
+
|
|
103
|
+
- [#994](https://github.com/codefastlabs/codefast/pull/994) A scoped binding whose scope changes stops handing children the instance they cached before the change. A binding
|
|
104
|
+
registered on a parent, resolved from a child, then refined `scoped()` → `transient()` → `scoped()` gave that child its
|
|
105
|
+
old scoped instance back, because only the registering container's cache was cleared. Every container now files a scoped
|
|
106
|
+
instance under a key the binding replaces when it leaves `scoped`, so the cached instance is never found again.
|
|
107
|
+
|
|
108
|
+
- [#1003](https://github.com/codefastlabs/codefast/pull/1003) A teardown reads a class's `@preDestroy` methods once for a run of bindings of that class, instead of once per binding,
|
|
109
|
+
and names the token only when a hook fails.
|
|
110
|
+
|
|
111
|
+
- [#1002](https://github.com/codefastlabs/codefast/pull/1002) A rebind that owes no teardown allocates nothing for the binding it displaces, and a collection read skips its scan for
|
|
112
|
+
dangling aliases in a chain that never held one.
|
|
113
|
+
|
|
3
114
|
## 0.11.0
|
|
4
115
|
|
|
5
116
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -169,8 +169,8 @@ constraints is its slot — think of a slot as a labelled variant of the token.
|
|
|
169
169
|
`{ name }`:
|
|
170
170
|
|
|
171
171
|
```ts
|
|
172
|
-
container.bind(LoggerToken).
|
|
173
|
-
container.bind(LoggerToken).
|
|
172
|
+
container.bind(LoggerToken).whenNamed("console").toConstantValue(consoleLogger);
|
|
173
|
+
container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
|
|
174
174
|
|
|
175
175
|
container.resolve(LoggerToken, { name: "file" }); // → fileLogger
|
|
176
176
|
```
|
|
@@ -180,7 +180,7 @@ Declare the names on the token and they become checked, completable literals at
|
|
|
180
180
|
```ts
|
|
181
181
|
const LoggerToken = token<Logger, "console" | "file">("app:Logger");
|
|
182
182
|
|
|
183
|
-
container.bind(LoggerToken).
|
|
183
|
+
container.bind(LoggerToken).whenNamed("file").toConstantValue(fileLogger);
|
|
184
184
|
container.resolve(LoggerToken, { name: "file" }); // { name: "fiel" } is a compile error
|
|
185
185
|
```
|
|
186
186
|
|
|
@@ -193,7 +193,7 @@ import { tag } from "@codefast/di";
|
|
|
193
193
|
|
|
194
194
|
const Provider = tag<"s3" | "gcs">("app:provider");
|
|
195
195
|
|
|
196
|
-
container.bind(StorageToken).
|
|
196
|
+
container.bind(StorageToken).whenTagged(Provider.of("s3")).to(S3Storage);
|
|
197
197
|
container.resolve(StorageToken, { tag: Provider.of("s3") }); // → S3Storage
|
|
198
198
|
```
|
|
199
199
|
|
|
@@ -230,7 +230,7 @@ predicates ship from the root entry:
|
|
|
230
230
|
| `whenAnyAncestorTaggedAll(criteria)` | some ancestor's slot carries all criteria in the array |
|
|
231
231
|
|
|
232
232
|
For the exact matching and most-specific-wins rules, see
|
|
233
|
-
[
|
|
233
|
+
[the specification → Slots and last-wins](./spec/binding.md#slots-and-last-wins--the-exact-definition).
|
|
234
234
|
|
|
235
235
|
## Decorators
|
|
236
236
|
|
|
@@ -367,6 +367,18 @@ exits. Synchronous `using` isn't supported — `onDeactivation` may be async —
|
|
|
367
367
|
token's bindings; and `container.generateDependencyGraph()` returns a JSON graph. Adapters render that graph for common
|
|
368
368
|
viewers: `toDotGraph`, `toMermaidGraph`, `toCytoscapeGraph`, and `toReactFlowGraph`.
|
|
369
369
|
|
|
370
|
+
`container.explain(token, options)` answers why a request selects the binding it does, without instantiating anything:
|
|
371
|
+
every candidate each registry offered and what selection made of it, the rule that decided, and the binding it ends on.
|
|
372
|
+
It evaluates `when()` predicates the way `resolve` does, and `options.ancestors` explains a request made from inside
|
|
373
|
+
other resolutions, so a predicate that reads the parent sees the one it would:
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
const explanation = container.explain(Logger, { ancestors: [Settlement] });
|
|
377
|
+
|
|
378
|
+
explanation.selected?.id; // the binding `resolve` would answer with
|
|
379
|
+
explanation.steps[0]?.rule; // "sole-predicate": the only candidate with a `when()` beat the rest
|
|
380
|
+
```
|
|
381
|
+
|
|
370
382
|
## Modules
|
|
371
383
|
|
|
372
384
|
A module is a reusable, stateless bundle of related bindings. Group them once, then load them into any container.
|
|
@@ -465,18 +477,20 @@ Every error extends `DiError` and carries a stable `code`, so you can branch on
|
|
|
465
477
|
|
|
466
478
|
The full taxonomy — including `MissingMetadataError`, `InvalidMetadataError`, `RebindUnboundTokenError`,
|
|
467
479
|
`AsyncModuleLoadError`, `InvalidBindingDeclarationError`, and the rest — is exported from the root entry and from
|
|
468
|
-
`@codefast/di/errors
|
|
480
|
+
`@codefast/di/errors`.
|
|
469
481
|
|
|
470
482
|
## Subpath exports
|
|
471
483
|
|
|
472
484
|
Prefer the root entry: it re-exports the whole public API. Reach for a subpath only to trim a bundle, or to pull in
|
|
473
485
|
something the root doesn't surface.
|
|
474
486
|
|
|
475
|
-
Every module is also published as a subpath that
|
|
476
|
-
(`core/token`, `core/tag`, `core/module`, …),
|
|
477
|
-
`lifecycle/*`, and `resolution/*` (for example
|
|
478
|
-
metadata under `decorators/*` and `metadata
|
|
479
|
-
`@codefast/di/
|
|
487
|
+
Every module is also published as a subpath that is its source path, with no exception: the model under
|
|
488
|
+
`@codefast/di/core/*` (`core/token`, `core/tag`, `core/module`, …), the error taxonomy at `@codefast/di/errors`, the
|
|
489
|
+
runtime under `container/*`, `injection/*`, `lifecycle/*`, and `resolution/*` (for example
|
|
490
|
+
`@codefast/di/resolution/select/constraints`), decorators and metadata under `decorators/*` and `metadata/*`, and
|
|
491
|
+
introspection under `introspection/*`: `@codefast/di/introspection/inspector`, `@codefast/di/introspection/explanation`,
|
|
492
|
+
`@codefast/di/introspection/dependency-graph`, and
|
|
493
|
+
`@codefast/di/introspection/graph-adapters/{dot,mermaid,cytoscape,reactflow}`.
|
|
480
494
|
|
|
481
495
|
## Benchmarks
|
|
482
496
|
|
|
@@ -487,7 +501,7 @@ alongside the method that produced them. Run it yourself rather than taking any
|
|
|
487
501
|
## Documentation
|
|
488
502
|
|
|
489
503
|
- [Rendered docs on codefastlabs.com](https://codefastlabs.com/docs/di)
|
|
490
|
-
- [`
|
|
504
|
+
- [`spec/`](./spec/README.md) — the behavioural contract: public API, semantics, and errors, one document per API area.
|
|
491
505
|
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) — the internal shape and the invariants the hot paths depend on.
|
|
492
506
|
- [`DECISIONS.md`](./DECISIONS.md) — why the API looks like this, and what it did not take from InversifyJS v8.
|
|
493
507
|
- [`examples/`](./examples/README.md) — runnable examples from basic tokens to a multi-file Ports & Adapters app.
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @remarks One object plays every role: each builder step, the record the registry stores, and the
|
|
5
5
|
* binding the resolver reads. The binding fields are declared first and in a fixed order, so every
|
|
6
|
-
* binding in the process shares one hidden class; the return type of each step
|
|
7
|
-
*
|
|
6
|
+
* binding in the process shares one hidden class; the return type of each step pins the chain's
|
|
7
|
+
* order, slot before strategy, so the binding registers once and is never re-slotted.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type { Binding, BindingSlot } from "#core/binding";
|
|
10
|
+
import type { AliasBindingBuilder, BindingBuilder, BindToBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, TransientBindingBuilder } from "#core/binding-builders";
|
|
10
11
|
import type { DeclaredBinding } from "#core/binding-declaration";
|
|
11
12
|
import type { BindingRegistry } from "#core/registry";
|
|
12
13
|
import type { BindingTag } from "#core/tag";
|
|
13
14
|
import type { Token } from "#core/token";
|
|
14
15
|
import type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame } from "#core/types";
|
|
15
16
|
import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
|
|
16
|
-
import type { ScopeManager } from "#lifecycle/
|
|
17
|
+
import type { ScopeManager } from "#lifecycle/scopes";
|
|
17
18
|
/**
|
|
18
19
|
* Where a chain registers, and on whose behalf.
|
|
19
20
|
*
|
|
@@ -26,24 +27,22 @@ export interface BindingRegistration {
|
|
|
26
27
|
readonly registry: BindingRegistry;
|
|
27
28
|
readonly scope: ScopeManager;
|
|
28
29
|
readonly moduleBindingIds: Array<BindingIdentifier> | undefined;
|
|
29
|
-
/** Runs for a binding this registration displaces,
|
|
30
|
+
/** Runs for a binding this registration displaces, deactivating it on the spot. */
|
|
30
31
|
readonly deactivateDisplaced?: ((binding: Binding) => void) | undefined;
|
|
31
|
-
/** Notes a
|
|
32
|
+
/** Notes a displaced binding so the container can still tear it down, when nothing deactivates it on the spot. */
|
|
32
33
|
readonly onDisplaced?: ((binding: Binding) => void) | undefined;
|
|
33
|
-
/** Undoes {@link BindingRegistration.onDisplaced} when a refinement restores the binding to the registry. */
|
|
34
|
-
readonly onRestored?: ((binding: Binding) => void) | undefined;
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
|
|
38
37
|
*
|
|
39
38
|
* @remarks The binding fields come first, in the order every binding shares, and the chain's own
|
|
40
|
-
* bookkeeping follows as private
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* bookkeeping follows as a private field. The slot steps write the fields before anything registers;
|
|
40
|
+
* `to*()` fills the strategy in place and hands this object to the registry once, so a bind is one
|
|
41
|
+
* allocation and one add. A scope or hook afterwards writes the registered object in place.
|
|
43
42
|
*
|
|
44
43
|
* @since 0.5.0-canary.8
|
|
45
44
|
*/
|
|
46
|
-
export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder
|
|
45
|
+
export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder, BindingBuilder<Value>, BindToBuilder<Value, Names>, ConstantBindingBuilder<Value>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
|
|
47
46
|
#private;
|
|
48
47
|
kind: BindingKind;
|
|
49
48
|
readonly identifier: BindingIdentifier;
|
|
@@ -51,8 +50,8 @@ export declare class BindingChain<Value, Names extends string = string> implemen
|
|
|
51
50
|
frame: ResolutionFrame | undefined;
|
|
52
51
|
rootContext: ResolutionContext | undefined;
|
|
53
52
|
activationStamp: number;
|
|
54
|
-
registrationOrder: number;
|
|
55
53
|
instance: unknown;
|
|
54
|
+
scopedCacheKey: BindingIdentifier;
|
|
56
55
|
readonly token: Token<Value, Names> | Constructor<Value>;
|
|
57
56
|
slot: BindingSlot;
|
|
58
57
|
predicate: BindingConstraint | undefined;
|
|
@@ -65,26 +64,20 @@ export declare class BindingChain<Value, Names extends string = string> implemen
|
|
|
65
64
|
activationHook: ActivationHandler<Value> | undefined;
|
|
66
65
|
deactivationHook: DeactivationHandler<Value> | undefined;
|
|
67
66
|
constructor(token: Token<Value, Names> | Constructor<Value>, registration: BindingRegistration);
|
|
68
|
-
/**
|
|
69
|
-
* Registers a declared module's bindings in list order, each a chain of its own that no step
|
|
70
|
-
* will ever refine.
|
|
71
|
-
*
|
|
72
|
-
* @remarks A displaced binding is handled as `to*()` handles it, never parked: nothing refines the
|
|
73
|
-
* chain, so nothing could restore it.
|
|
74
|
-
*/
|
|
67
|
+
/** Registers a declared module's bindings in list order, each a chain of its own, handled as `to*()` handles one. */
|
|
75
68
|
static registerDeclared(declarations: ReadonlyArray<DeclaredBinding>, registration: BindingRegistration): void;
|
|
76
|
-
to(type: Constructor<Value>): BindingBuilder<Value
|
|
77
|
-
toSelf(): BindingBuilder<Value
|
|
78
|
-
toConstantValue(value: Value): ConstantBindingBuilder<Value
|
|
79
|
-
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value
|
|
80
|
-
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value
|
|
69
|
+
to(type: Constructor<Value>): BindingBuilder<Value>;
|
|
70
|
+
toSelf(): BindingBuilder<Value>;
|
|
71
|
+
toConstantValue(value: Value): ConstantBindingBuilder<Value>;
|
|
72
|
+
toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
|
|
73
|
+
toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
|
|
81
74
|
toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
82
75
|
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
83
|
-
}) => Value, deps: Deps): BindingBuilder<Value
|
|
76
|
+
}) => Value, deps: Deps): BindingBuilder<Value>;
|
|
84
77
|
toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
|
|
85
78
|
[K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
|
|
86
|
-
}) => Promise<Value>, deps: Deps): BindingBuilder<Value
|
|
87
|
-
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder
|
|
79
|
+
}) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
|
|
80
|
+
toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
|
|
88
81
|
when(predicate: BindingConstraint): this;
|
|
89
82
|
whenNamed(name: Names): this;
|
|
90
83
|
whenTagged(criterion: BindingTag): this;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { clearBindingFrame, NO_ACTIVATION_STAMP, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE,
|
|
1
|
+
import { clearBindingFrame, NO_ACTIVATION_STAMP, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, withSlotCriterion, } from "#core/binding";
|
|
2
2
|
import { mergingConstraintRequirements } from "#core/constraint-requirement";
|
|
3
3
|
import { slotName } from "#core/tag";
|
|
4
4
|
import { tokenName } from "#core/token";
|
|
5
|
-
import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#errors
|
|
5
|
+
import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#errors";
|
|
6
6
|
import { normalizeToDescriptor } from "#injection/descriptor";
|
|
7
7
|
// ── BindingChain ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
8
8
|
/**
|
|
9
9
|
* The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
|
|
10
10
|
*
|
|
11
11
|
* @remarks The binding fields come first, in the order every binding shares, and the chain's own
|
|
12
|
-
* bookkeeping follows as private
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* bookkeeping follows as a private field. The slot steps write the fields before anything registers;
|
|
13
|
+
* `to*()` fills the strategy in place and hands this object to the registry once, so a bind is one
|
|
14
|
+
* allocation and one add. A scope or hook afterwards writes the registered object in place.
|
|
15
15
|
*
|
|
16
16
|
* @since 0.5.0-canary.8
|
|
17
17
|
*/
|
|
@@ -23,8 +23,8 @@ export class BindingChain {
|
|
|
23
23
|
frame = undefined;
|
|
24
24
|
rootContext = undefined;
|
|
25
25
|
activationStamp = NO_ACTIVATION_STAMP;
|
|
26
|
-
registrationOrder = UNREGISTERED_ORDER;
|
|
27
26
|
instance = NO_INSTANCE;
|
|
27
|
+
scopedCacheKey = this.identifier;
|
|
28
28
|
token;
|
|
29
29
|
slot = DEFAULT_BINDING_SLOT;
|
|
30
30
|
predicate = undefined;
|
|
@@ -38,10 +38,6 @@ export class BindingChain {
|
|
|
38
38
|
deactivationHook = undefined;
|
|
39
39
|
// The chain. Set by the first `to*()`, which is the only one a chain accepts.
|
|
40
40
|
#isRegistered = false;
|
|
41
|
-
// Allocated only by a chain that actually displaces something — most never do.
|
|
42
|
-
#displacedByChain;
|
|
43
|
-
// Registry version after this chain's last write — a mismatch means someone else wrote in between.
|
|
44
|
-
#versionAfterLastWrite = -1;
|
|
45
41
|
#registration;
|
|
46
42
|
constructor(token, registration) {
|
|
47
43
|
this.token = token;
|
|
@@ -51,13 +47,13 @@ export class BindingChain {
|
|
|
51
47
|
get #binding() {
|
|
52
48
|
return this;
|
|
53
49
|
}
|
|
54
|
-
/** Loud failure for a
|
|
50
|
+
/** Loud failure for a scope, a hook or `id()` before `to*()`. */
|
|
55
51
|
#requireRegistered() {
|
|
56
52
|
if (!this.#isRegistered) {
|
|
57
53
|
throw new ChainNotRegisteredError(tokenName(this.token));
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
|
-
/** Loud failure for a second `to*()
|
|
56
|
+
/** Loud failure for a slot step or a second `to*()` once registered, raised before a field is overwritten. */
|
|
61
57
|
#requireUnregistered() {
|
|
62
58
|
if (this.#isRegistered) {
|
|
63
59
|
throw new ChainAlreadyRegisteredError(tokenName(this.token));
|
|
@@ -67,32 +63,22 @@ export class BindingChain {
|
|
|
67
63
|
this.kind = kind;
|
|
68
64
|
this.scope = scope;
|
|
69
65
|
this.#isRegistered = true;
|
|
70
|
-
// A fresh registration parks nothing yet and restores nothing: one add, then the version.
|
|
71
66
|
const registration = this.#registration;
|
|
72
|
-
const
|
|
73
|
-
const displaced = registry.add(this.#binding);
|
|
67
|
+
const displaced = registration.registry.add(this.#binding);
|
|
74
68
|
if (displaced !== undefined) {
|
|
75
69
|
if (registration.deactivateDisplaced !== undefined) {
|
|
76
70
|
registration.deactivateDisplaced(displaced);
|
|
77
71
|
}
|
|
78
72
|
else {
|
|
79
|
-
this.#displacedByChain = [displaced];
|
|
80
73
|
registration.onDisplaced?.(displaced);
|
|
81
74
|
}
|
|
82
75
|
}
|
|
83
76
|
if (registration.moduleBindingIds !== undefined) {
|
|
84
77
|
registration.moduleBindingIds.push(this.identifier);
|
|
85
78
|
}
|
|
86
|
-
this.#versionAfterLastWrite = registry.version;
|
|
87
79
|
return this;
|
|
88
80
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Registers a declared module's bindings in list order, each a chain of its own that no step
|
|
91
|
-
* will ever refine.
|
|
92
|
-
*
|
|
93
|
-
* @remarks A displaced binding is handled as `to*()` handles it, never parked: nothing refines the
|
|
94
|
-
* chain, so nothing could restore it.
|
|
95
|
-
*/
|
|
81
|
+
/** Registers a declared module's bindings in list order, each a chain of its own, handled as `to*()` handles one. */
|
|
96
82
|
static registerDeclared(declarations, registration) {
|
|
97
83
|
const { registry, moduleBindingIds, deactivateDisplaced, onDisplaced } = registration;
|
|
98
84
|
for (let index = 0; index < declarations.length; index += 1) {
|
|
@@ -177,102 +163,67 @@ export class BindingChain {
|
|
|
177
163
|
this.target = target;
|
|
178
164
|
return this.#register("alias", "transient");
|
|
179
165
|
}
|
|
180
|
-
// ──
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*
|
|
184
|
-
* @remarks When it does, a refinement rewrites the live binding in place; otherwise `#reslot`
|
|
185
|
-
* re-checks liveness and restores what the new shape frees.
|
|
186
|
-
*/
|
|
187
|
-
get #isProvablyLive() {
|
|
188
|
-
return this.#registration.registry.version === this.#versionAfterLastWrite && this.#displacedByChain === undefined;
|
|
189
|
-
}
|
|
190
|
-
/** Stamps this chain's last-write version after a mutation it made in place. */
|
|
191
|
-
#recordWrite() {
|
|
192
|
-
this.#versionAfterLastWrite = this.#registration.registry.version;
|
|
193
|
-
}
|
|
194
|
-
/** Bumps the registry for an index-neutral mutation (scope, hook) and records this chain's write. */
|
|
195
|
-
#touchAndRecordWrite() {
|
|
196
|
-
this.#registration.registry.touch();
|
|
197
|
-
this.#recordWrite();
|
|
198
|
-
}
|
|
199
|
-
// Slot and predicate are what the registry indexes on, so a re-slot takes the binding out of the
|
|
200
|
-
// registry, rewrites the two fields while it is out, and registers it again — same object, same id.
|
|
201
|
-
#reslot(slot, predicate) {
|
|
202
|
-
this.#requireRegistered();
|
|
203
|
-
this.#commit(() => {
|
|
204
|
-
this.slot = slot;
|
|
205
|
-
this.predicate = predicate;
|
|
206
|
-
// The frame reports the slot, so a resolve before this call memoized the previous one.
|
|
207
|
-
clearBindingFrame(this.#binding);
|
|
208
|
-
});
|
|
209
|
-
return this;
|
|
210
|
-
}
|
|
211
|
-
#withScope(scope) {
|
|
212
|
-
this.#requireRegistered();
|
|
213
|
-
if (this.scope !== scope) {
|
|
214
|
-
// An instance cached under the old scope must not survive the change — a later flip back
|
|
215
|
-
// to that scope would resurrect it.
|
|
216
|
-
this.#registration.scope.deleteSingleton(this.#binding);
|
|
217
|
-
this.#registration.scope.deleteScoped(this.identifier);
|
|
218
|
-
this.scope = scope;
|
|
219
|
-
}
|
|
220
|
-
// The frame reports the scope, so a resolve before this call memoized the previous one.
|
|
221
|
-
clearBindingFrame(this.#binding);
|
|
222
|
-
this.#touchAndRecordWrite();
|
|
223
|
-
return this;
|
|
224
|
-
}
|
|
225
|
-
// SPEC calls a candidate a binding that passes *all* of a chain's predicates, and the chain type
|
|
226
|
-
// reads as refinement, so a second `when()` narrows rather than replaces.
|
|
166
|
+
// ── Slot ───────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
167
|
+
// SPEC calls a candidate a binding that passes *all* of a chain's predicates, so a second `when()`
|
|
168
|
+
// narrows rather than replaces.
|
|
227
169
|
when(predicate) {
|
|
228
|
-
this.#
|
|
170
|
+
this.#requireUnregistered();
|
|
229
171
|
const previous = this.predicate;
|
|
230
172
|
// The composite carries both sides' requirements, so validate() still sees them.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.#registration.registry.setPredicate(this.#binding, narrowed);
|
|
237
|
-
this.#recordWrite();
|
|
238
|
-
return this;
|
|
239
|
-
}
|
|
240
|
-
return this.#reslot(this.slot, narrowed);
|
|
173
|
+
this.predicate =
|
|
174
|
+
previous === undefined
|
|
175
|
+
? predicate
|
|
176
|
+
: mergingConstraintRequirements((ctx) => previous(ctx) && predicate(ctx), previous, predicate);
|
|
177
|
+
return this;
|
|
241
178
|
}
|
|
242
179
|
whenNamed(name) {
|
|
243
180
|
return this.whenTagged(slotName.of(name));
|
|
244
181
|
}
|
|
245
182
|
whenTagged(criterion) {
|
|
246
|
-
this.#
|
|
183
|
+
this.#requireUnregistered();
|
|
247
184
|
if (this.isMany) {
|
|
248
185
|
throw new ManyBindingSlotError(tokenName(this.token));
|
|
249
186
|
}
|
|
250
|
-
|
|
187
|
+
this.slot = withSlotCriterion(this.slot, criterion);
|
|
188
|
+
return this;
|
|
251
189
|
}
|
|
252
190
|
many() {
|
|
253
|
-
this.#
|
|
191
|
+
this.#requireUnregistered();
|
|
254
192
|
if (this.slot.tags.length !== 0) {
|
|
255
193
|
throw new ManyBindingSlotError(tokenName(this.token));
|
|
256
194
|
}
|
|
257
|
-
|
|
258
|
-
return this;
|
|
259
|
-
}
|
|
260
|
-
// A provably-live binding takes membership in place; the registry only moves it out of the lone
|
|
261
|
-
// map. Otherwise `#commit` re-checks liveness and restores what the freed slot lets back in.
|
|
262
|
-
if (this.#isProvablyLive) {
|
|
263
|
-
this.#registration.registry.setMany(this.#binding);
|
|
264
|
-
this.#recordWrite();
|
|
265
|
-
return this;
|
|
266
|
-
}
|
|
267
|
-
this.#commit(() => {
|
|
268
|
-
this.isMany = true;
|
|
269
|
-
});
|
|
195
|
+
this.isMany = true;
|
|
270
196
|
return this;
|
|
271
197
|
}
|
|
272
198
|
whenDefault() {
|
|
273
|
-
// The default slot is what a fresh
|
|
274
|
-
|
|
199
|
+
// The default slot is what a fresh chain already has; the check keeps it a slot step like the others.
|
|
200
|
+
this.#requireUnregistered();
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
// ── Scope and lifecycle ────────────────────────────────────────────────────────────────────────────────────────────
|
|
204
|
+
#withScope(scope) {
|
|
275
205
|
this.#requireRegistered();
|
|
206
|
+
// Nothing a cache, a frame or the version answers for depends on a scope that did not change.
|
|
207
|
+
if (this.scope === scope) {
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
// An instance cached under the old scope must not survive the change — a later flip back to that
|
|
211
|
+
// scope would resurrect it. A child's scoped cache is out of reach here, so leaving `scoped` retires
|
|
212
|
+
// the key every child filed its instance under.
|
|
213
|
+
const registration = this.#registration;
|
|
214
|
+
if (this.instance !== NO_INSTANCE) {
|
|
215
|
+
registration.scope.deleteSingleton(this.#binding);
|
|
216
|
+
}
|
|
217
|
+
if (this.scope === "scoped") {
|
|
218
|
+
registration.scope.deleteScoped(this.scopedCacheKey);
|
|
219
|
+
this.scopedCacheKey = generateBindingId();
|
|
220
|
+
}
|
|
221
|
+
this.scope = scope;
|
|
222
|
+
// The frame reports the scope, so a resolve before this call memoized the previous one.
|
|
223
|
+
if (this.frame !== undefined || this.rootContext !== undefined) {
|
|
224
|
+
clearBindingFrame(this.#binding);
|
|
225
|
+
}
|
|
226
|
+
registration.registry.touch();
|
|
276
227
|
return this;
|
|
277
228
|
}
|
|
278
229
|
singleton() {
|
|
@@ -287,78 +238,17 @@ export class BindingChain {
|
|
|
287
238
|
onActivation(fn) {
|
|
288
239
|
this.#requireRegistered();
|
|
289
240
|
this.activationHook = fn;
|
|
290
|
-
this.#
|
|
241
|
+
this.#registration.registry.touch();
|
|
291
242
|
return this;
|
|
292
243
|
}
|
|
293
244
|
onDeactivation(fn) {
|
|
294
245
|
this.#requireRegistered();
|
|
295
246
|
this.deactivationHook = fn;
|
|
296
|
-
this.#
|
|
247
|
+
this.#registration.registry.touch();
|
|
297
248
|
return this;
|
|
298
249
|
}
|
|
299
250
|
id() {
|
|
300
251
|
this.#requireRegistered();
|
|
301
252
|
return this.identifier;
|
|
302
253
|
}
|
|
303
|
-
// ── Registry ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
304
|
-
/**
|
|
305
|
-
* Registers this binding; with `rewrite` given, first takes the live binding out, rewrites it, and
|
|
306
|
-
* registers it again.
|
|
307
|
-
*
|
|
308
|
-
* @remarks A `when*()` that follows `to*()` re-slots an already-live binding and can displace one
|
|
309
|
-
* the final shape would never conflict with. Those stay parked in `#displacedByChain` until the
|
|
310
|
-
* chain settles, then get restored.
|
|
311
|
-
*/
|
|
312
|
-
#commit(rewrite) {
|
|
313
|
-
const { registry, moduleBindingIds } = this.#registration;
|
|
314
|
-
const registered = this.#binding;
|
|
315
|
-
if (rewrite !== undefined) {
|
|
316
|
-
// A registry someone else wrote since this chain's last write invalidates the parked
|
|
317
|
-
// snapshot: restoring it could undo an unbind or shadow a newer binding.
|
|
318
|
-
if (registry.version !== this.#versionAfterLastWrite) {
|
|
319
|
-
this.#displacedByChain = undefined;
|
|
320
|
-
}
|
|
321
|
-
if (!registry.reslot(registered, rewrite)) {
|
|
322
|
-
// The chain's binding is no longer live (unbound or displaced) — a refinement must not
|
|
323
|
-
// resurrect it, so the chain goes inert against the registry.
|
|
324
|
-
this.#displacedByChain = undefined;
|
|
325
|
-
this.#versionAfterLastWrite = registry.version;
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
// Each `to*()` starts its own registration, so anything a previous one displaced is not this
|
|
331
|
-
// registration's to restore.
|
|
332
|
-
this.#displacedByChain = undefined;
|
|
333
|
-
}
|
|
334
|
-
const displaced = registry.add(registered);
|
|
335
|
-
if (displaced !== undefined) {
|
|
336
|
-
if (this.#registration.deactivateDisplaced !== undefined && rewrite === undefined) {
|
|
337
|
-
this.#registration.deactivateDisplaced(displaced);
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
(this.#displacedByChain ??= []).push(displaced);
|
|
341
|
-
this.#registration.onDisplaced?.(displaced);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (rewrite !== undefined && this.#displacedByChain !== undefined) {
|
|
345
|
-
this.#restoreNonConflicting(this.#displacedByChain);
|
|
346
|
-
}
|
|
347
|
-
if (rewrite === undefined && moduleBindingIds !== undefined) {
|
|
348
|
-
moduleBindingIds.push(this.identifier);
|
|
349
|
-
}
|
|
350
|
-
this.#versionAfterLastWrite = registry.version;
|
|
351
|
-
}
|
|
352
|
-
#restoreNonConflicting(displaced) {
|
|
353
|
-
for (let index = displaced.length - 1; index >= 0; index -= 1) {
|
|
354
|
-
const candidate = displaced[index];
|
|
355
|
-
// A restore must never displace: a slot that has been re-occupied keeps its occupant, and
|
|
356
|
-
// the candidate stays parked.
|
|
357
|
-
if (!this.#registration.registry.hasSlotOccupant(candidate)) {
|
|
358
|
-
this.#registration.registry.add(candidate);
|
|
359
|
-
this.#registration.onRestored?.(candidate);
|
|
360
|
-
displaced.splice(index, 1);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
254
|
}
|