@codefast/di 0.3.16-canary.2 → 0.3.16-canary.3
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 +8 -0
- package/dist/binding.d.mts +24 -24
- package/dist/constraints.d.mts +3 -3
- package/dist/container.mjs +82 -150
- package/dist/decorators/inject.d.mts +3 -2
- package/dist/decorators/inject.mjs +13 -40
- package/dist/decorators/injectable.mjs +4 -12
- package/dist/decorators/lifecycle-decorators.mjs +11 -72
- package/dist/dependency-graph.mjs +6 -4
- package/dist/graph-adapters/cytoscape.d.mts +12 -12
- package/dist/graph-adapters/cytoscape.mjs +7 -7
- package/dist/graph-adapters/reactflow.d.mts +15 -15
- package/dist/graph-adapters/reactflow.mjs +6 -9
- package/dist/index.d.mts +3 -3
- package/dist/inspector.d.mts +3 -2
- package/dist/inspector.mjs +7 -10
- package/dist/lifecycle.mjs +2 -12
- package/dist/metadata/metadata-keys.d.mts +8 -33
- package/dist/metadata/metadata-keys.mjs +8 -21
- package/dist/metadata/metadata-types.d.mts +5 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +1 -0
- package/dist/metadata/symbol-metadata-reader.mjs +11 -33
- package/dist/registry.mjs +3 -22
- package/dist/resolve-options.d.mts +2 -2
- package/dist/resolve-options.mjs +10 -8
- package/dist/resolver.d.mts +5 -0
- package/dist/resolver.mjs +15 -21
- package/dist/types.d.mts +10 -4
- package/package.json +39 -13
- package/src/binding-scope.ts +26 -0
- package/src/binding-select.ts +167 -0
- package/src/binding.ts +281 -0
- package/src/constraints.ts +149 -0
- package/src/constructor-type.ts +19 -0
- package/src/container.ts +1213 -0
- package/src/decorators/inject.ts +233 -0
- package/src/decorators/injectable.ts +85 -0
- package/src/decorators/lifecycle-decorators.ts +55 -0
- package/src/dependency-graph.ts +116 -0
- package/src/environment.ts +232 -0
- package/src/errors.ts +262 -0
- package/src/graph-adapters/cytoscape.ts +64 -0
- package/src/graph-adapters/dot.ts +22 -0
- package/src/graph-adapters/reactflow.ts +58 -0
- package/src/index.ts +101 -0
- package/src/inspector.ts +133 -0
- package/src/lifecycle.ts +238 -0
- package/src/metadata/metadata-keys.ts +25 -0
- package/src/metadata/metadata-reader-token.ts +8 -0
- package/src/metadata/metadata-types.ts +53 -0
- package/src/metadata/symbol-metadata-reader.ts +57 -0
- package/src/module.ts +93 -0
- package/src/registry.ts +241 -0
- package/src/resolve-options.ts +42 -0
- package/src/resolver.ts +1837 -0
- package/src/scope.ts +77 -0
- package/src/token.ts +40 -0
- package/src/types.ts +145 -0
- package/dist/graph-adapters/types.d.mts +0 -2
- package/dist/graph-adapters/types.mjs +0 -1
package/dist/resolve-options.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
//#region src/resolve-options.ts
|
|
2
|
+
/** Shared core: build a ResolveOptions from already-normalised name + tags. */
|
|
3
|
+
function buildOptions(name, tags) {
|
|
4
|
+
if (name === void 0 && tags === void 0) return;
|
|
5
|
+
const options = {};
|
|
6
|
+
if (name !== void 0) options.name = name;
|
|
7
|
+
if (tags !== void 0) options.tags = tags;
|
|
8
|
+
return options;
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Builds a {@link ResolveOptions} safe for `exactOptionalPropertyTypes`:
|
|
4
12
|
* omits keys instead of assigning `undefined`.
|
|
@@ -6,10 +14,7 @@
|
|
|
6
14
|
* @since 0.3.16-canary.0
|
|
7
15
|
*/
|
|
8
16
|
function injectionSlotToResolveOptions(injectionSlot) {
|
|
9
|
-
|
|
10
|
-
if (injectionSlot.name !== void 0) options.name = injectionSlot.name;
|
|
11
|
-
if (injectionSlot.tags !== void 0) options.tags = injectionSlot.tags;
|
|
12
|
-
return options.name !== void 0 || options.tags !== void 0 ? options : void 0;
|
|
17
|
+
return buildOptions(injectionSlot.name, injectionSlot.tags);
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Hint from a binding {@link BindingSlot} (tags may be empty; omits when nothing to match).
|
|
@@ -17,10 +22,7 @@ function injectionSlotToResolveOptions(injectionSlot) {
|
|
|
17
22
|
* @since 0.3.16-canary.0
|
|
18
23
|
*/
|
|
19
24
|
function bindingSlotToResolveOptions(bindingSlot) {
|
|
20
|
-
|
|
21
|
-
if (bindingSlot.name !== void 0) options.name = bindingSlot.name;
|
|
22
|
-
if (bindingSlot.tags.length > 0) options.tags = bindingSlot.tags;
|
|
23
|
-
return options.name !== void 0 || options.tags !== void 0 ? options : void 0;
|
|
25
|
+
return buildOptions(bindingSlot.name, bindingSlot.tags.length > 0 ? bindingSlot.tags : void 0);
|
|
24
26
|
}
|
|
25
27
|
//#endregion
|
|
26
28
|
export { bindingSlotToResolveOptions, injectionSlotToResolveOptions };
|
package/dist/resolver.d.mts
CHANGED
|
@@ -82,6 +82,11 @@ declare class DependencyResolver {
|
|
|
82
82
|
private _getResolutionFrame;
|
|
83
83
|
private _needsActivation;
|
|
84
84
|
private _refreshClassPostConstructCache;
|
|
85
|
+
/**
|
|
86
|
+
* Refreshes the post-construct cache for class bindings on first instantiation and
|
|
87
|
+
* returns the (possibly updated) shouldActivate flag.
|
|
88
|
+
*/
|
|
89
|
+
private _refreshActivationCacheIfNeeded;
|
|
85
90
|
private _requiresResolutionContext;
|
|
86
91
|
private _acquireSyncResolutionContext;
|
|
87
92
|
}
|
package/dist/resolver.mjs
CHANGED
|
@@ -176,13 +176,7 @@ var DependencyResolver = class {
|
|
|
176
176
|
try {
|
|
177
177
|
const resolutionCtx = needsActivation || this._requiresResolutionContext(binding) ? this._acquireSyncResolutionContext(resolutionPath, resolutionStack, hint) : void 0;
|
|
178
178
|
const instance = this._instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
179
|
-
|
|
180
|
-
if (binding.kind === "class" && this._classHasPostConstruct.get(binding.target) === void 0) {
|
|
181
|
-
this._refreshClassPostConstructCache(binding.target);
|
|
182
|
-
this._activationNeedByBindingId.delete(binding.id);
|
|
183
|
-
shouldActivate = this._needsActivation(binding);
|
|
184
|
-
}
|
|
185
|
-
const activated = shouldActivate ? this._lifecycle.runActivationSync(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
179
|
+
const activated = this._refreshActivationCacheIfNeeded(binding, needsActivation) ? this._lifecycle.runActivationSync(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
186
180
|
if (scope === "singleton") this._scope.setSingleton(binding.id, activated);
|
|
187
181
|
else if (scope === "scoped") this._scope.setScoped(binding.id, activated);
|
|
188
182
|
return activated;
|
|
@@ -351,13 +345,7 @@ var DependencyResolver = class {
|
|
|
351
345
|
if (scope === "singleton") {
|
|
352
346
|
const createSingletonPromise = async () => {
|
|
353
347
|
const instance = await this._instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
354
|
-
|
|
355
|
-
if (binding.kind === "class" && this._classHasPostConstruct.get(binding.target) === void 0) {
|
|
356
|
-
this._refreshClassPostConstructCache(binding.target);
|
|
357
|
-
this._activationNeedByBindingId.delete(binding.id);
|
|
358
|
-
shouldActivate = this._needsActivation(binding);
|
|
359
|
-
}
|
|
360
|
-
const activated = shouldActivate ? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
348
|
+
const activated = this._refreshActivationCacheIfNeeded(binding, needsActivation) ? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
361
349
|
this._scope.setSingleton(binding.id, activated);
|
|
362
350
|
this._scope.clearInflight(binding.id);
|
|
363
351
|
return activated;
|
|
@@ -370,13 +358,7 @@ var DependencyResolver = class {
|
|
|
370
358
|
return await singletonPromise;
|
|
371
359
|
}
|
|
372
360
|
const instance = await this._instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
373
|
-
|
|
374
|
-
if (binding.kind === "class" && this._classHasPostConstruct.get(binding.target) === void 0) {
|
|
375
|
-
this._refreshClassPostConstructCache(binding.target);
|
|
376
|
-
this._activationNeedByBindingId.delete(binding.id);
|
|
377
|
-
shouldActivate = this._needsActivation(binding);
|
|
378
|
-
}
|
|
379
|
-
const activated = shouldActivate ? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
361
|
+
const activated = this._refreshActivationCacheIfNeeded(binding, needsActivation) ? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader) : instance;
|
|
380
362
|
if (scope === "scoped") this._scope.setScoped(binding.id, activated);
|
|
381
363
|
return activated;
|
|
382
364
|
} finally {
|
|
@@ -754,6 +736,18 @@ var DependencyResolver = class {
|
|
|
754
736
|
const hasPostConstruct = lifecycle !== void 0 && lifecycle.postConstruct !== void 0 && lifecycle.postConstruct.length > 0;
|
|
755
737
|
this._classHasPostConstruct.set(target, hasPostConstruct);
|
|
756
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Refreshes the post-construct cache for class bindings on first instantiation and
|
|
741
|
+
* returns the (possibly updated) shouldActivate flag.
|
|
742
|
+
*/
|
|
743
|
+
_refreshActivationCacheIfNeeded(binding, needsActivation) {
|
|
744
|
+
if (binding.kind === "class" && this._classHasPostConstruct.get(binding.target) === void 0) {
|
|
745
|
+
this._refreshClassPostConstructCache(binding.target);
|
|
746
|
+
this._activationNeedByBindingId.delete(binding.id);
|
|
747
|
+
return this._needsActivation(binding);
|
|
748
|
+
}
|
|
749
|
+
return needsActivation;
|
|
750
|
+
}
|
|
757
751
|
_requiresResolutionContext(binding) {
|
|
758
752
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
759
753
|
}
|
package/dist/types.d.mts
CHANGED
|
@@ -2,6 +2,12 @@ import { Constructor } from "./constructor-type.mjs";
|
|
|
2
2
|
import { Token } from "./token.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A single [tag, value] pair used in slot constraints and resolve hints.
|
|
7
|
+
*
|
|
8
|
+
* @since 0.3.16-canary.0
|
|
9
|
+
*/
|
|
10
|
+
type BindingTag = readonly [tag: string, value: unknown];
|
|
5
11
|
/**
|
|
6
12
|
* Token or class constructor used as a binding / injection / resolve key.
|
|
7
13
|
*
|
|
@@ -40,8 +46,8 @@ interface ResolveOptions {
|
|
|
40
46
|
* Single-tag shorthand — semantics match including one pair in `tags`.
|
|
41
47
|
* Enables fast-path lookup alongside `tags`.
|
|
42
48
|
*/
|
|
43
|
-
tag?:
|
|
44
|
-
tags?: ReadonlyArray<
|
|
49
|
+
tag?: BindingTag;
|
|
50
|
+
tags?: ReadonlyArray<BindingTag>;
|
|
45
51
|
}
|
|
46
52
|
/**
|
|
47
53
|
* @since 0.3.16-canary.0
|
|
@@ -53,7 +59,7 @@ interface ResolutionFrame {
|
|
|
53
59
|
readonly kind: BindingKind;
|
|
54
60
|
readonly slot: {
|
|
55
61
|
readonly name: string | undefined;
|
|
56
|
-
readonly tags: ReadonlyArray<
|
|
62
|
+
readonly tags: ReadonlyArray<BindingTag>;
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
@@ -83,4 +89,4 @@ interface ResolutionContext {
|
|
|
83
89
|
*/
|
|
84
90
|
type TokenValue<Type> = Type extends Token<infer Value> ? Value : Type extends Constructor<infer Value> ? Value : never;
|
|
85
91
|
//#endregion
|
|
86
|
-
export { ActivationHandler, BindingIdentifier, BindingKind, BindingScope, ConstraintContext, type Constructor, DeactivationHandler, DependencyKey, ResolutionContext, ResolutionFrame, ResolveOptions, TokenValue };
|
|
92
|
+
export { ActivationHandler, BindingIdentifier, BindingKind, BindingScope, BindingTag, ConstraintContext, type Constructor, DeactivationHandler, DependencyKey, ResolutionContext, ResolutionFrame, ResolveOptions, TokenValue };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/di",
|
|
3
|
-
"version": "0.3.16-canary.
|
|
3
|
+
"version": "0.3.16-canary.3",
|
|
4
4
|
"description": "Lightweight dependency injection primitives for Codefast",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codefast",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
+
"src",
|
|
21
22
|
"CHANGELOG.md",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE"
|
|
@@ -45,122 +46,147 @@
|
|
|
45
46
|
},
|
|
46
47
|
"exports": {
|
|
47
48
|
".": {
|
|
49
|
+
"source": "./src/index.ts",
|
|
48
50
|
"types": "./dist/index.d.mts",
|
|
49
51
|
"import": "./dist/index.mjs"
|
|
50
52
|
},
|
|
51
53
|
"./binding": {
|
|
54
|
+
"source": "./src/binding.ts",
|
|
52
55
|
"types": "./dist/binding.d.mts",
|
|
53
56
|
"import": "./dist/binding.mjs"
|
|
54
57
|
},
|
|
55
58
|
"./binding-scope": {
|
|
59
|
+
"source": "./src/binding-scope.ts",
|
|
56
60
|
"types": "./dist/binding-scope.d.mts",
|
|
57
61
|
"import": "./dist/binding-scope.mjs"
|
|
58
62
|
},
|
|
59
63
|
"./binding-select": {
|
|
64
|
+
"source": "./src/binding-select.ts",
|
|
60
65
|
"types": "./dist/binding-select.d.mts",
|
|
61
66
|
"import": "./dist/binding-select.mjs"
|
|
62
67
|
},
|
|
63
68
|
"./constraints": {
|
|
69
|
+
"source": "./src/constraints.ts",
|
|
64
70
|
"types": "./dist/constraints.d.mts",
|
|
65
71
|
"import": "./dist/constraints.mjs"
|
|
66
72
|
},
|
|
67
73
|
"./constructor-type": {
|
|
74
|
+
"source": "./src/constructor-type.ts",
|
|
68
75
|
"types": "./dist/constructor-type.d.mts",
|
|
69
76
|
"import": "./dist/constructor-type.mjs"
|
|
70
77
|
},
|
|
71
78
|
"./container": {
|
|
79
|
+
"source": "./src/container.ts",
|
|
72
80
|
"types": "./dist/container.d.mts",
|
|
73
81
|
"import": "./dist/container.mjs"
|
|
74
82
|
},
|
|
75
83
|
"./decorators/inject": {
|
|
84
|
+
"source": "./src/decorators/inject.ts",
|
|
76
85
|
"types": "./dist/decorators/inject.d.mts",
|
|
77
86
|
"import": "./dist/decorators/inject.mjs"
|
|
78
87
|
},
|
|
79
88
|
"./decorators/injectable": {
|
|
89
|
+
"source": "./src/decorators/injectable.ts",
|
|
80
90
|
"types": "./dist/decorators/injectable.d.mts",
|
|
81
91
|
"import": "./dist/decorators/injectable.mjs"
|
|
82
92
|
},
|
|
83
93
|
"./decorators/lifecycle-decorators": {
|
|
94
|
+
"source": "./src/decorators/lifecycle-decorators.ts",
|
|
84
95
|
"types": "./dist/decorators/lifecycle-decorators.d.mts",
|
|
85
96
|
"import": "./dist/decorators/lifecycle-decorators.mjs"
|
|
86
97
|
},
|
|
87
98
|
"./dependency-graph": {
|
|
99
|
+
"source": "./src/dependency-graph.ts",
|
|
88
100
|
"types": "./dist/dependency-graph.d.mts",
|
|
89
101
|
"import": "./dist/dependency-graph.mjs"
|
|
90
102
|
},
|
|
91
103
|
"./environment": {
|
|
104
|
+
"source": "./src/environment.ts",
|
|
92
105
|
"types": "./dist/environment.d.mts",
|
|
93
106
|
"import": "./dist/environment.mjs"
|
|
94
107
|
},
|
|
95
108
|
"./errors": {
|
|
109
|
+
"source": "./src/errors.ts",
|
|
96
110
|
"types": "./dist/errors.d.mts",
|
|
97
111
|
"import": "./dist/errors.mjs"
|
|
98
112
|
},
|
|
99
113
|
"./graph-adapters/cytoscape": {
|
|
114
|
+
"source": "./src/graph-adapters/cytoscape.ts",
|
|
100
115
|
"types": "./dist/graph-adapters/cytoscape.d.mts",
|
|
101
116
|
"import": "./dist/graph-adapters/cytoscape.mjs"
|
|
102
117
|
},
|
|
103
118
|
"./graph-adapters/dot": {
|
|
119
|
+
"source": "./src/graph-adapters/dot.ts",
|
|
104
120
|
"types": "./dist/graph-adapters/dot.d.mts",
|
|
105
121
|
"import": "./dist/graph-adapters/dot.mjs"
|
|
106
122
|
},
|
|
107
123
|
"./graph-adapters/reactflow": {
|
|
124
|
+
"source": "./src/graph-adapters/reactflow.ts",
|
|
108
125
|
"types": "./dist/graph-adapters/reactflow.d.mts",
|
|
109
126
|
"import": "./dist/graph-adapters/reactflow.mjs"
|
|
110
127
|
},
|
|
111
|
-
"./graph-adapters/types": {
|
|
112
|
-
"types": "./dist/graph-adapters/types.d.mts",
|
|
113
|
-
"import": "./dist/graph-adapters/types.mjs"
|
|
114
|
-
},
|
|
115
128
|
"./inspector": {
|
|
129
|
+
"source": "./src/inspector.ts",
|
|
116
130
|
"types": "./dist/inspector.d.mts",
|
|
117
131
|
"import": "./dist/inspector.mjs"
|
|
118
132
|
},
|
|
119
133
|
"./lifecycle": {
|
|
134
|
+
"source": "./src/lifecycle.ts",
|
|
120
135
|
"types": "./dist/lifecycle.d.mts",
|
|
121
136
|
"import": "./dist/lifecycle.mjs"
|
|
122
137
|
},
|
|
123
138
|
"./metadata/metadata-keys": {
|
|
139
|
+
"source": "./src/metadata/metadata-keys.ts",
|
|
124
140
|
"types": "./dist/metadata/metadata-keys.d.mts",
|
|
125
141
|
"import": "./dist/metadata/metadata-keys.mjs"
|
|
126
142
|
},
|
|
127
143
|
"./metadata/metadata-reader-token": {
|
|
144
|
+
"source": "./src/metadata/metadata-reader-token.ts",
|
|
128
145
|
"types": "./dist/metadata/metadata-reader-token.d.mts",
|
|
129
146
|
"import": "./dist/metadata/metadata-reader-token.mjs"
|
|
130
147
|
},
|
|
131
148
|
"./metadata/metadata-types": {
|
|
149
|
+
"source": "./src/metadata/metadata-types.ts",
|
|
132
150
|
"types": "./dist/metadata/metadata-types.d.mts",
|
|
133
151
|
"import": "./dist/metadata/metadata-types.mjs"
|
|
134
152
|
},
|
|
135
153
|
"./metadata/symbol-metadata-reader": {
|
|
154
|
+
"source": "./src/metadata/symbol-metadata-reader.ts",
|
|
136
155
|
"types": "./dist/metadata/symbol-metadata-reader.d.mts",
|
|
137
156
|
"import": "./dist/metadata/symbol-metadata-reader.mjs"
|
|
138
157
|
},
|
|
139
158
|
"./module": {
|
|
159
|
+
"source": "./src/module.ts",
|
|
140
160
|
"types": "./dist/module.d.mts",
|
|
141
161
|
"import": "./dist/module.mjs"
|
|
142
162
|
},
|
|
143
163
|
"./registry": {
|
|
164
|
+
"source": "./src/registry.ts",
|
|
144
165
|
"types": "./dist/registry.d.mts",
|
|
145
166
|
"import": "./dist/registry.mjs"
|
|
146
167
|
},
|
|
147
168
|
"./resolve-options": {
|
|
169
|
+
"source": "./src/resolve-options.ts",
|
|
148
170
|
"types": "./dist/resolve-options.d.mts",
|
|
149
171
|
"import": "./dist/resolve-options.mjs"
|
|
150
172
|
},
|
|
151
173
|
"./resolver": {
|
|
174
|
+
"source": "./src/resolver.ts",
|
|
152
175
|
"types": "./dist/resolver.d.mts",
|
|
153
176
|
"import": "./dist/resolver.mjs"
|
|
154
177
|
},
|
|
155
178
|
"./scope": {
|
|
179
|
+
"source": "./src/scope.ts",
|
|
156
180
|
"types": "./dist/scope.d.mts",
|
|
157
181
|
"import": "./dist/scope.mjs"
|
|
158
182
|
},
|
|
159
183
|
"./token": {
|
|
184
|
+
"source": "./src/token.ts",
|
|
160
185
|
"types": "./dist/token.d.mts",
|
|
161
186
|
"import": "./dist/token.mjs"
|
|
162
187
|
},
|
|
163
188
|
"./types": {
|
|
189
|
+
"source": "./src/types.ts",
|
|
164
190
|
"types": "./dist/types.d.mts",
|
|
165
191
|
"import": "./dist/types.mjs"
|
|
166
192
|
},
|
|
@@ -170,20 +196,20 @@
|
|
|
170
196
|
"access": "public"
|
|
171
197
|
},
|
|
172
198
|
"devDependencies": {
|
|
173
|
-
"@babel/plugin-proposal-decorators": "
|
|
199
|
+
"@babel/plugin-proposal-decorators": "8.0.0-rc.6",
|
|
174
200
|
"@rolldown/plugin-babel": "^0.2.3",
|
|
175
|
-
"@types/node": "^25.
|
|
176
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
177
|
-
"@vitest/coverage-v8": "^4.1.
|
|
201
|
+
"@types/node": "^25.9.1",
|
|
202
|
+
"@typescript/native-preview": "7.0.0-dev.20260526.1",
|
|
203
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
178
204
|
"expect-type": "^1.3.0",
|
|
179
205
|
"tsdown": "^0.22.0",
|
|
180
|
-
"tsx": "^4.22.
|
|
206
|
+
"tsx": "^4.22.3",
|
|
181
207
|
"typescript": "^6.0.3",
|
|
182
|
-
"vitest": "^4.1.
|
|
183
|
-
"@codefast/typescript-config": "0.3.16-canary.
|
|
208
|
+
"vitest": "^4.1.7",
|
|
209
|
+
"@codefast/typescript-config": "0.3.16-canary.3"
|
|
184
210
|
},
|
|
185
211
|
"engines": {
|
|
186
|
-
"node": ">=
|
|
212
|
+
"node": ">=24.0.0"
|
|
187
213
|
},
|
|
188
214
|
"scripts": {
|
|
189
215
|
"build": "tsdown",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Binding } from "#/binding";
|
|
2
|
+
import type { BindingScope } from "#/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runtime scope used for validation and introspection. Alias bindings are treated as transient
|
|
6
|
+
* because they defer scope to the aliased target at resolve time.
|
|
7
|
+
*
|
|
8
|
+
* @since 0.3.16-canary.0
|
|
9
|
+
*/
|
|
10
|
+
export function effectiveBindingScope(binding: Binding): BindingScope {
|
|
11
|
+
switch (binding.kind) {
|
|
12
|
+
case "alias":
|
|
13
|
+
return "transient";
|
|
14
|
+
case "class":
|
|
15
|
+
case "constant":
|
|
16
|
+
case "dynamic":
|
|
17
|
+
case "dynamic-async":
|
|
18
|
+
case "resolved":
|
|
19
|
+
case "resolved-async":
|
|
20
|
+
return binding.scope;
|
|
21
|
+
default: {
|
|
22
|
+
const exhaustive: never = binding;
|
|
23
|
+
return exhaustive;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Binding } from "#/binding";
|
|
2
|
+
import type { BindingTag, ConstraintContext, ResolveOptions } from "#/types";
|
|
3
|
+
import { AmbiguousBindingError } from "#/errors";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Select a single candidate from a list of bindings using slot matching + predicates.
|
|
7
|
+
* Returns undefined if no match, throws AmbiguousBindingError if multiple match.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.3.16-canary.0
|
|
10
|
+
*/
|
|
11
|
+
export function selectBinding(
|
|
12
|
+
bindings: ReadonlyArray<Binding>,
|
|
13
|
+
hint: ResolveOptions | undefined,
|
|
14
|
+
ctx: ConstraintContext,
|
|
15
|
+
tokenDisplayName: string,
|
|
16
|
+
): Binding | undefined {
|
|
17
|
+
const candidates = filterBindings(bindings, hint, ctx);
|
|
18
|
+
if (candidates.length === 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (candidates.length === 1) {
|
|
22
|
+
return candidates[0];
|
|
23
|
+
}
|
|
24
|
+
// Multiple candidates — check if any is unambiguous (slot-based selection)
|
|
25
|
+
// Slot-based bindings already have last-wins applied in registry, so
|
|
26
|
+
// multiple candidates here means ambiguous predicate-only bindings
|
|
27
|
+
throw new AmbiguousBindingError(
|
|
28
|
+
tokenDisplayName,
|
|
29
|
+
candidates.map((c) => c.id),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Select all candidates matching hint + predicates.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.3.16-canary.0
|
|
37
|
+
*/
|
|
38
|
+
export function selectAllBindings(
|
|
39
|
+
bindings: ReadonlyArray<Binding>,
|
|
40
|
+
hint: ResolveOptions | undefined,
|
|
41
|
+
ctx: ConstraintContext,
|
|
42
|
+
): Array<Binding> {
|
|
43
|
+
return filterBindings(bindings, hint, ctx, "all");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function filterBindings(
|
|
47
|
+
bindings: ReadonlyArray<Binding>,
|
|
48
|
+
hint: ResolveOptions | undefined,
|
|
49
|
+
ctx: ConstraintContext,
|
|
50
|
+
selectionMode: "single" | "all" = "single",
|
|
51
|
+
): Array<Binding> {
|
|
52
|
+
if (hint === undefined) {
|
|
53
|
+
const resultWithoutHint: Array<Binding> = [];
|
|
54
|
+
if (selectionMode === "all") {
|
|
55
|
+
for (const binding of bindings) {
|
|
56
|
+
if (matchesPredicate(binding, ctx)) {
|
|
57
|
+
resultWithoutHint.push(binding);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
for (const binding of bindings) {
|
|
62
|
+
const slot = binding.slot;
|
|
63
|
+
if (slot.name === undefined && slot.tags.length === 0 && matchesPredicate(binding, ctx)) {
|
|
64
|
+
resultWithoutHint.push(binding);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return resultWithoutHint;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result: Array<Binding> = [];
|
|
72
|
+
for (const binding of bindings) {
|
|
73
|
+
const slotMatched =
|
|
74
|
+
selectionMode === "all"
|
|
75
|
+
? matchesSlotForResolveAll(binding, hint)
|
|
76
|
+
: matchesSlot(binding, hint);
|
|
77
|
+
if (slotMatched && matchesPredicate(binding, ctx)) {
|
|
78
|
+
result.push(binding);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function matchesSlotForResolveAll(binding: Binding, hint: ResolveOptions | undefined): boolean {
|
|
85
|
+
const hasExplicitSlotFilter =
|
|
86
|
+
hint !== undefined &&
|
|
87
|
+
(hint.name !== undefined ||
|
|
88
|
+
(hint.tags !== undefined && hint.tags.length > 0) ||
|
|
89
|
+
hint.tag !== undefined);
|
|
90
|
+
if (!hasExplicitSlotFilter) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return matchesSlot(binding, hint);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function matchesSlot(binding: Binding, hint: ResolveOptions | undefined): boolean {
|
|
97
|
+
const slot = binding.slot;
|
|
98
|
+
const hintName = hint?.name;
|
|
99
|
+
const hintTags = hint?.tags;
|
|
100
|
+
const singleHintTag = hint?.tag;
|
|
101
|
+
const hasHintTags = (hintTags?.length ?? 0) > 0 || singleHintTag !== undefined;
|
|
102
|
+
|
|
103
|
+
// Match by name
|
|
104
|
+
if (slot.name !== undefined) {
|
|
105
|
+
if (hintName === undefined) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (slot.name !== hintName) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
} else if (hintName !== undefined) {
|
|
112
|
+
// Binding has no name but hint requests a specific name — no match
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Match by tags — binding's tags must all be present in hint
|
|
117
|
+
if (slot.tags.length > 0) {
|
|
118
|
+
if (!hasHintTags) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
for (const [tagKey, tagValue] of slot.tags) {
|
|
122
|
+
if (!matchHintTag(tagKey, tagValue, hintTags, singleHintTag)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (hasHintTags) {
|
|
127
|
+
// Binding has no tags but hint requires tags — no match for tagged slots
|
|
128
|
+
// But: default slot (no tags, no name) can match when hint has tags if there are no tag-slotted bindings
|
|
129
|
+
// Actually per spec: resolveAll with tags only returns bindings that have those tags
|
|
130
|
+
// and resolve with tags requires exact match
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function matchHintTag(
|
|
138
|
+
tagKey: string,
|
|
139
|
+
tagValue: unknown,
|
|
140
|
+
hintTags: ReadonlyArray<BindingTag> | undefined,
|
|
141
|
+
singleHintTag: BindingTag | undefined,
|
|
142
|
+
): boolean {
|
|
143
|
+
if (
|
|
144
|
+
singleHintTag !== undefined &&
|
|
145
|
+
singleHintTag[0] === tagKey &&
|
|
146
|
+
Object.is(singleHintTag[1], tagValue)
|
|
147
|
+
) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (hintTags === undefined || hintTags.length === 0) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
for (let index = 0; index < hintTags.length; index += 1) {
|
|
154
|
+
const hintTag = hintTags[index]!;
|
|
155
|
+
if (hintTag[0] === tagKey && Object.is(hintTag[1], tagValue)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function matchesPredicate(binding: Binding, ctx: ConstraintContext): boolean {
|
|
163
|
+
if (binding.predicate === undefined) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
return binding.predicate(ctx);
|
|
167
|
+
}
|