@codefast/di 0.8.1 → 0.10.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 +164 -0
- package/LICENSE +1 -1
- package/README.md +29 -20
- package/dist/ambient/active-container.d.ts +13 -0
- package/dist/ambient/active-container.js +24 -0
- package/dist/container/binding-builders.d.ts +42 -20
- package/dist/container/binding-builders.js +155 -87
- package/dist/container/container.d.ts +10 -10
- package/dist/container/container.js +55 -23
- package/dist/core/binding.d.ts +55 -57
- package/dist/core/binding.js +11 -37
- package/dist/core/constraint-requirement.d.ts +19 -4
- package/dist/core/constraint-requirement.js +19 -9
- package/dist/core/registry.d.ts +47 -4
- package/dist/core/registry.js +361 -159
- package/dist/core/state-epoch.d.ts +16 -0
- package/dist/core/state-epoch.js +21 -0
- package/dist/core/tag.d.ts +2 -2
- package/dist/core/tag.js +2 -2
- package/dist/core/token.d.ts +24 -4
- package/dist/core/token.js +1 -1
- package/dist/core/types.d.ts +15 -6
- package/dist/decorators/inject.d.ts +1 -1
- package/dist/decorators/inject.js +6 -4
- package/dist/errors/diagnostics.d.ts +2 -0
- package/dist/errors/errors.d.ts +33 -1
- package/dist/errors/errors.js +44 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/injection/descriptor.d.ts +16 -8
- package/dist/injection/descriptor.js +3 -1
- package/dist/injection/resolve-options.d.ts +6 -0
- package/dist/injection/resolve-options.js +16 -0
- package/dist/introspection/dependency-graph.js +10 -5
- package/dist/introspection/inspector.d.ts +3 -1
- package/dist/introspection/inspector.js +10 -24
- package/dist/lifecycle/lifecycle-manager.js +10 -8
- package/dist/lifecycle/scope-manager.js +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/resolution/cache/activation-need.d.ts +2 -0
- package/dist/resolution/cache/activation-need.js +13 -6
- package/dist/resolution/cache/binding-lookup-cache.d.ts +34 -1
- package/dist/resolution/cache/binding-lookup-cache.js +96 -12
- package/dist/resolution/cache/class-introspector.d.ts +1 -1
- package/dist/resolution/cache/class-introspector.js +28 -14
- package/dist/resolution/context.d.ts +8 -8
- package/dist/resolution/plan/instantiation-plan.d.ts +12 -0
- package/dist/resolution/plan/instantiation-plan.js +156 -65
- package/dist/resolution/plan/plan-codegen.d.ts +100 -0
- package/dist/resolution/plan/plan-codegen.js +185 -0
- package/dist/resolution/resolver.d.ts +14 -4
- package/dist/resolution/resolver.js +375 -134
- package/dist/resolution/select/binding-select.js +12 -7
- package/dist/resolution/select/constraints.d.ts +7 -4
- package/dist/resolution/select/constraints.js +34 -13
- package/package.json +14 -40
|
@@ -7,7 +7,7 @@ import { AmbiguousBindingError } from "#/errors/errors";
|
|
|
7
7
|
* @since 0.3.16-canary.0
|
|
8
8
|
*/
|
|
9
9
|
export function selectBinding(bindings, options, ctx, tokenDisplayName) {
|
|
10
|
-
const candidates = filterBindings(bindings, options, ctx, true);
|
|
10
|
+
const candidates = filterBindings(bindings, options, ctx, true, false);
|
|
11
11
|
if (candidates.length === 0) {
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
@@ -36,7 +36,7 @@ export function selectBinding(bindings, options, ctx, tokenDisplayName) {
|
|
|
36
36
|
if (mostSpecific !== undefined) {
|
|
37
37
|
return mostSpecific;
|
|
38
38
|
}
|
|
39
|
-
throw new AmbiguousBindingError(tokenDisplayName, candidates.map((c) => c.
|
|
39
|
+
throw new AmbiguousBindingError(tokenDisplayName, candidates.map((c) => c.identifier));
|
|
40
40
|
}
|
|
41
41
|
/** The lone candidate declaring more criteria than every other, or `undefined` when that is a tie. */
|
|
42
42
|
function mostSpecificByCriterionCount(candidates) {
|
|
@@ -64,7 +64,7 @@ function mostSpecificByCriterionCount(candidates) {
|
|
|
64
64
|
export function selectAllBindings(bindings, options, ctx) {
|
|
65
65
|
// `resolveAll` matches the slot only when the request carries a criterion; with none it takes
|
|
66
66
|
// every binding, where `resolve` would read an absent criterion as "the default slot".
|
|
67
|
-
return filterBindings(bindings, options, ctx, options !== undefined && hasSlotCriterion(options));
|
|
67
|
+
return filterBindings(bindings, options, ctx, options !== undefined && hasSlotCriterion(options), true);
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
70
|
* @param bindings - the token's candidates, in registration order
|
|
@@ -72,13 +72,18 @@ export function selectAllBindings(bindings, options, ctx) {
|
|
|
72
72
|
* @param ctx - what the constraint predicates read
|
|
73
73
|
* @param requiresSlotMatch - `resolve` always matches the slot, where an absent criterion means
|
|
74
74
|
* "the default slot"; `resolveAll` matches only when the request carries one
|
|
75
|
+
* @param includesMembers - `resolveAll` takes collection members; a single `resolve` never does
|
|
75
76
|
*/
|
|
76
|
-
function filterBindings(bindings, options, ctx, requiresSlotMatch) {
|
|
77
|
+
function filterBindings(bindings, options, ctx, requiresSlotMatch, includesMembers) {
|
|
77
78
|
const result = [];
|
|
78
|
-
// A predicate is user code that may rebind the token mid-walk
|
|
79
|
-
//
|
|
80
|
-
|
|
79
|
+
// A predicate is user code that may rebind the token mid-walk. A removal replaces the list, and an
|
|
80
|
+
// append lands past the length read here, so the walk offers exactly the candidates it started with.
|
|
81
|
+
const length = bindings.length;
|
|
82
|
+
for (let index = 0; index < length; index += 1) {
|
|
81
83
|
const binding = bindings[index];
|
|
84
|
+
if (!includesMembers && binding.isMany) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
82
87
|
if (requiresSlotMatch && !matchesSlot(binding.slot, options)) {
|
|
83
88
|
continue;
|
|
84
89
|
}
|
|
@@ -26,17 +26,20 @@ export declare function whenAnyAncestorIs(token: Token<unknown> | Constructor):
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function whenNoAncestorIs(token: Token<unknown> | Constructor): BindingConstraint;
|
|
28
28
|
/**
|
|
29
|
-
* Matches when the direct parent slot
|
|
29
|
+
* Matches when the direct parent resolves the given token at the slot carrying the given name.
|
|
30
|
+
*
|
|
31
|
+
* @remarks A slot name is a label on one token's bindings, so the token is part of the question — and
|
|
32
|
+
* what types `name` to the names that token declares.
|
|
30
33
|
*
|
|
31
34
|
* @since 0.3.16-canary.0
|
|
32
35
|
*/
|
|
33
|
-
export declare function whenParentNamed(name:
|
|
36
|
+
export declare function whenParentNamed<Names extends string>(token: Token<unknown, Names> | Constructor, name: NoInfer<Names>): BindingConstraint;
|
|
34
37
|
/**
|
|
35
|
-
* Matches when at least one ancestor slot
|
|
38
|
+
* Matches when at least one ancestor resolves the given token at the slot carrying the given name.
|
|
36
39
|
*
|
|
37
40
|
* @since 0.3.16-canary.0
|
|
38
41
|
*/
|
|
39
|
-
export declare function whenAnyAncestorNamed(name:
|
|
42
|
+
export declare function whenAnyAncestorNamed<Names extends string>(token: Token<unknown, Names> | Constructor, name: NoInfer<Names>): BindingConstraint;
|
|
40
43
|
/**
|
|
41
44
|
* Matches when the direct parent slot carries the given tag pair.
|
|
42
45
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { requiringAncestorSlotName } from "#/core/constraint-requirement";
|
|
2
|
-
import { coversTagKeys, tagKeyMaskOf } from "#/core/tag";
|
|
1
|
+
import { requiringAncestorSlotName, requiringAncestorSlotNames } from "#/core/constraint-requirement";
|
|
2
|
+
import { coversTagKeys, slotName, tagKeyMaskOf } from "#/core/tag";
|
|
3
3
|
import { tokenName } from "#/core/token";
|
|
4
4
|
import { EmptyTagCriteriaError } from "#/errors/errors";
|
|
5
5
|
/**
|
|
@@ -39,20 +39,27 @@ export function whenNoAncestorIs(token) {
|
|
|
39
39
|
return (constraintContext) => constraintContext.ancestors.every((ancestorFrame) => ancestorFrame.tokenName !== tokenDisplayName);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* Matches when the direct parent slot
|
|
42
|
+
* Matches when the direct parent resolves the given token at the slot carrying the given name.
|
|
43
|
+
*
|
|
44
|
+
* @remarks A slot name is a label on one token's bindings, so the token is part of the question — and
|
|
45
|
+
* what types `name` to the names that token declares.
|
|
43
46
|
*
|
|
44
47
|
* @since 0.3.16-canary.0
|
|
45
48
|
*/
|
|
46
|
-
export function whenParentNamed(name) {
|
|
47
|
-
|
|
49
|
+
export function whenParentNamed(token, name) {
|
|
50
|
+
const parentTokenName = tokenName(token);
|
|
51
|
+
return requiringAncestorSlotName((constraintContext) => constraintContext.parent !== undefined &&
|
|
52
|
+
constraintContext.parent.tokenName === parentTokenName &&
|
|
53
|
+
constraintContext.parent.slot.name === name, { tokenName: parentTokenName, name, helperName: "whenParentNamed" });
|
|
48
54
|
}
|
|
49
55
|
/**
|
|
50
|
-
* Matches when at least one ancestor slot
|
|
56
|
+
* Matches when at least one ancestor resolves the given token at the slot carrying the given name.
|
|
51
57
|
*
|
|
52
58
|
* @since 0.3.16-canary.0
|
|
53
59
|
*/
|
|
54
|
-
export function whenAnyAncestorNamed(name) {
|
|
55
|
-
|
|
60
|
+
export function whenAnyAncestorNamed(token, name) {
|
|
61
|
+
const ancestorTokenName = tokenName(token);
|
|
62
|
+
return requiringAncestorSlotName((constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.tokenName === ancestorTokenName && ancestorFrame.slot.name === name), { tokenName: ancestorTokenName, name, helperName: "whenAnyAncestorNamed" });
|
|
56
63
|
}
|
|
57
64
|
/**
|
|
58
65
|
* Matches when the direct parent slot carries the given tag pair.
|
|
@@ -60,7 +67,7 @@ export function whenAnyAncestorNamed(name) {
|
|
|
60
67
|
* @since 0.3.16-canary.0
|
|
61
68
|
*/
|
|
62
69
|
export function whenParentTagged(criterion) {
|
|
63
|
-
return (constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.tags.includes(criterion);
|
|
70
|
+
return requiringReservedNamesAmong((constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.tags.includes(criterion), [criterion], "whenParentTagged");
|
|
64
71
|
}
|
|
65
72
|
/**
|
|
66
73
|
* Matches when at least one ancestor slot carries the given tag pair.
|
|
@@ -68,7 +75,7 @@ export function whenParentTagged(criterion) {
|
|
|
68
75
|
* @since 0.3.16-canary.0
|
|
69
76
|
*/
|
|
70
77
|
export function whenAnyAncestorTagged(criterion) {
|
|
71
|
-
return (constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.tags.includes(criterion));
|
|
78
|
+
return requiringReservedNamesAmong((constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.tags.includes(criterion)), [criterion], "whenAnyAncestorTagged");
|
|
72
79
|
}
|
|
73
80
|
/**
|
|
74
81
|
* Matches when the direct parent slot carries **all** of the given tag pairs.
|
|
@@ -80,13 +87,13 @@ export function whenAnyAncestorTagged(criterion) {
|
|
|
80
87
|
export function whenParentTaggedAll(tags) {
|
|
81
88
|
assertHasCriteria(tags, "whenParentTaggedAll");
|
|
82
89
|
const wanted = tagKeyMaskOf(tags);
|
|
83
|
-
return (constraintContext) => {
|
|
90
|
+
return requiringReservedNamesAmong((constraintContext) => {
|
|
84
91
|
const { parent } = constraintContext;
|
|
85
92
|
if (parent === undefined || !coversTagKeys(parent.slot.keyMask, wanted)) {
|
|
86
93
|
return false;
|
|
87
94
|
}
|
|
88
95
|
return tags.every((criterion) => parent.slot.tags.includes(criterion));
|
|
89
|
-
};
|
|
96
|
+
}, tags, "whenParentTaggedAll");
|
|
90
97
|
}
|
|
91
98
|
/**
|
|
92
99
|
* Matches when at least one ancestor slot carries **all** of the given tag pairs.
|
|
@@ -98,7 +105,21 @@ export function whenParentTaggedAll(tags) {
|
|
|
98
105
|
export function whenAnyAncestorTaggedAll(tags) {
|
|
99
106
|
assertHasCriteria(tags, "whenAnyAncestorTaggedAll");
|
|
100
107
|
const wanted = tagKeyMaskOf(tags);
|
|
101
|
-
return (constraintContext) => constraintContext.ancestors.some((frame) => coversTagKeys(frame.slot.keyMask, wanted) && tags.every((criterion) => frame.slot.tags.includes(criterion)));
|
|
108
|
+
return requiringReservedNamesAmong((constraintContext) => constraintContext.ancestors.some((frame) => coversTagKeys(frame.slot.keyMask, wanted) && tags.every((criterion) => frame.slot.tags.includes(criterion))), tags, "whenAnyAncestorTaggedAll");
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Records a requirement for every reserved-key criterion in a list, so a name spelled through the tag
|
|
112
|
+
* lane is validated exactly as one spelled through `whenNamed`.
|
|
113
|
+
*/
|
|
114
|
+
function requiringReservedNamesAmong(predicate, tags, helperName) {
|
|
115
|
+
let requirements;
|
|
116
|
+
for (const criterion of tags) {
|
|
117
|
+
if (criterion.key === slotName) {
|
|
118
|
+
requirements ??= [];
|
|
119
|
+
requirements.push({ tokenName: undefined, name: criterion.value, helperName });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return requirements === undefined ? predicate : requiringAncestorSlotNames(predicate, requirements);
|
|
102
123
|
}
|
|
103
124
|
/**
|
|
104
125
|
* Refuses a criteria list with nothing in it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/di",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Lightweight dependency injection primitives for Codefast",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codefast",
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
"inversion-of-control",
|
|
10
10
|
"typescript"
|
|
11
11
|
],
|
|
12
|
+
"homepage": "https://github.com/codefastlabs/codefast/tree/main/packages/di#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/codefastlabs/codefast/issues"
|
|
15
|
+
},
|
|
12
16
|
"license": "MIT",
|
|
13
|
-
"author": "Vuong Phan <mr.thevuong@gmail.com>",
|
|
17
|
+
"author": "Vuong Phan <mr.thevuong@gmail.com> (https://github.com/thevuong)",
|
|
14
18
|
"repository": {
|
|
15
19
|
"type": "git",
|
|
16
20
|
"url": "git+https://github.com/codefastlabs/codefast.git",
|
|
@@ -28,20 +32,6 @@
|
|
|
28
32
|
"module": "./dist/index.js",
|
|
29
33
|
"types": "./dist/index.d.ts",
|
|
30
34
|
"imports": {
|
|
31
|
-
"#/tests/*": [
|
|
32
|
-
"./tests/*",
|
|
33
|
-
"./tests/*.ts",
|
|
34
|
-
"./tests/*.tsx",
|
|
35
|
-
"./tests/*/index.ts",
|
|
36
|
-
"./tests/*/index.tsx"
|
|
37
|
-
],
|
|
38
|
-
"#/examples/*": [
|
|
39
|
-
"./examples/*",
|
|
40
|
-
"./examples/*.ts",
|
|
41
|
-
"./examples/*.tsx",
|
|
42
|
-
"./examples/*/index.ts",
|
|
43
|
-
"./examples/*/index.tsx"
|
|
44
|
-
],
|
|
45
35
|
"#/*": {
|
|
46
36
|
"types": "./dist/*.d.ts",
|
|
47
37
|
"default": "./dist/*.js"
|
|
@@ -92,6 +82,10 @@
|
|
|
92
82
|
"types": "./dist/core/registry.d.ts",
|
|
93
83
|
"import": "./dist/core/registry.js"
|
|
94
84
|
},
|
|
85
|
+
"./core/state-epoch": {
|
|
86
|
+
"types": "./dist/core/state-epoch.d.ts",
|
|
87
|
+
"import": "./dist/core/state-epoch.js"
|
|
88
|
+
},
|
|
95
89
|
"./core/tag": {
|
|
96
90
|
"types": "./dist/core/tag.d.ts",
|
|
97
91
|
"import": "./dist/core/tag.js"
|
|
@@ -208,6 +202,10 @@
|
|
|
208
202
|
"types": "./dist/resolution/plan/instantiation-plan.d.ts",
|
|
209
203
|
"import": "./dist/resolution/plan/instantiation-plan.js"
|
|
210
204
|
},
|
|
205
|
+
"./resolution/plan/plan-codegen": {
|
|
206
|
+
"types": "./dist/resolution/plan/plan-codegen.d.ts",
|
|
207
|
+
"import": "./dist/resolution/plan/plan-codegen.js"
|
|
208
|
+
},
|
|
211
209
|
"./resolution/resolver": {
|
|
212
210
|
"types": "./dist/resolution/resolver.d.ts",
|
|
213
211
|
"import": "./dist/resolution/resolver.js"
|
|
@@ -225,31 +223,7 @@
|
|
|
225
223
|
"publishConfig": {
|
|
226
224
|
"access": "public"
|
|
227
225
|
},
|
|
228
|
-
"devDependencies": {
|
|
229
|
-
"@babel/core": "^8.0.1",
|
|
230
|
-
"@babel/plugin-proposal-decorators": "8.0.2",
|
|
231
|
-
"@rolldown/plugin-babel": "^0.2.3",
|
|
232
|
-
"@types/node": "^26.4.1",
|
|
233
|
-
"@vitest/coverage-v8": "^5.0.0",
|
|
234
|
-
"tsx": "^4.23.13",
|
|
235
|
-
"typescript": "^7.0.2",
|
|
236
|
-
"vitest": "^5.0.0",
|
|
237
|
-
"@codefast/typescript-config": "0.9.0"
|
|
238
|
-
},
|
|
239
226
|
"engines": {
|
|
240
227
|
"node": ">=24.0.0"
|
|
241
|
-
},
|
|
242
|
-
"scripts": {
|
|
243
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
244
|
-
"check-types": "tsc --noEmit",
|
|
245
|
-
"clean": "rm -rf dist",
|
|
246
|
-
"examples": "node examples/run.mjs",
|
|
247
|
-
"test": "vitest run",
|
|
248
|
-
"test:coverage": "vitest run --coverage",
|
|
249
|
-
"test:e2e": "vitest run tests/e2e",
|
|
250
|
-
"test:integration": "vitest run tests/integration",
|
|
251
|
-
"test:type": "vitest run tests/types",
|
|
252
|
-
"test:unit": "vitest run tests/unit",
|
|
253
|
-
"test:watch": "vitest"
|
|
254
228
|
}
|
|
255
229
|
}
|