@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/binding.d.mts +24 -24
  3. package/dist/constraints.d.mts +3 -3
  4. package/dist/container.mjs +82 -150
  5. package/dist/decorators/inject.d.mts +3 -2
  6. package/dist/decorators/inject.mjs +13 -40
  7. package/dist/decorators/injectable.mjs +4 -12
  8. package/dist/decorators/lifecycle-decorators.mjs +11 -72
  9. package/dist/dependency-graph.mjs +6 -4
  10. package/dist/graph-adapters/cytoscape.d.mts +12 -12
  11. package/dist/graph-adapters/cytoscape.mjs +7 -7
  12. package/dist/graph-adapters/reactflow.d.mts +15 -15
  13. package/dist/graph-adapters/reactflow.mjs +6 -9
  14. package/dist/index.d.mts +3 -3
  15. package/dist/inspector.d.mts +3 -2
  16. package/dist/inspector.mjs +7 -10
  17. package/dist/lifecycle.mjs +2 -12
  18. package/dist/metadata/metadata-keys.d.mts +8 -33
  19. package/dist/metadata/metadata-keys.mjs +8 -21
  20. package/dist/metadata/metadata-types.d.mts +5 -0
  21. package/dist/metadata/symbol-metadata-reader.d.mts +1 -0
  22. package/dist/metadata/symbol-metadata-reader.mjs +11 -33
  23. package/dist/registry.mjs +3 -22
  24. package/dist/resolve-options.d.mts +2 -2
  25. package/dist/resolve-options.mjs +10 -8
  26. package/dist/resolver.d.mts +5 -0
  27. package/dist/resolver.mjs +15 -21
  28. package/dist/types.d.mts +10 -4
  29. package/package.json +39 -13
  30. package/src/binding-scope.ts +26 -0
  31. package/src/binding-select.ts +167 -0
  32. package/src/binding.ts +281 -0
  33. package/src/constraints.ts +149 -0
  34. package/src/constructor-type.ts +19 -0
  35. package/src/container.ts +1213 -0
  36. package/src/decorators/inject.ts +233 -0
  37. package/src/decorators/injectable.ts +85 -0
  38. package/src/decorators/lifecycle-decorators.ts +55 -0
  39. package/src/dependency-graph.ts +116 -0
  40. package/src/environment.ts +232 -0
  41. package/src/errors.ts +262 -0
  42. package/src/graph-adapters/cytoscape.ts +64 -0
  43. package/src/graph-adapters/dot.ts +22 -0
  44. package/src/graph-adapters/reactflow.ts +58 -0
  45. package/src/index.ts +101 -0
  46. package/src/inspector.ts +133 -0
  47. package/src/lifecycle.ts +238 -0
  48. package/src/metadata/metadata-keys.ts +25 -0
  49. package/src/metadata/metadata-reader-token.ts +8 -0
  50. package/src/metadata/metadata-types.ts +53 -0
  51. package/src/metadata/symbol-metadata-reader.ts +57 -0
  52. package/src/module.ts +93 -0
  53. package/src/registry.ts +241 -0
  54. package/src/resolve-options.ts +42 -0
  55. package/src/resolver.ts +1837 -0
  56. package/src/scope.ts +77 -0
  57. package/src/token.ts +40 -0
  58. package/src/types.ts +145 -0
  59. package/dist/graph-adapters/types.d.mts +0 -2
  60. package/dist/graph-adapters/types.mjs +0 -1
package/src/binding.ts ADDED
@@ -0,0 +1,281 @@
1
+ import type {
2
+ ActivationHandler,
3
+ BindingIdentifier,
4
+ BindingScope,
5
+ BindingTag,
6
+ Constructor,
7
+ DeactivationHandler,
8
+ DependencyKey,
9
+ ResolutionContext,
10
+ TokenValue,
11
+ ConstraintContext,
12
+ } from "#/types";
13
+ import type { Token } from "#/token";
14
+ import type { InjectionDescriptor } from "#/decorators/inject";
15
+
16
+ // ── BindingSlot ───────────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * @since 0.3.16-canary.0
20
+ */
21
+ export interface BindingSlot {
22
+ readonly name: string | undefined;
23
+ readonly tags: ReadonlyArray<BindingTag>;
24
+ }
25
+
26
+ /**
27
+ * @since 0.3.16-canary.0
28
+ */
29
+ export function bindingSlotEquals(left: BindingSlot, right: BindingSlot): boolean {
30
+ if (left.name !== right.name) {
31
+ return false;
32
+ }
33
+ if (left.tags.length !== right.tags.length) {
34
+ return false;
35
+ }
36
+ for (const [tagKey, tagValue] of left.tags) {
37
+ if (
38
+ !right.tags.some(
39
+ ([otherKey, otherValue]) => otherKey === tagKey && Object.is(otherValue, tagValue),
40
+ )
41
+ ) {
42
+ return false;
43
+ }
44
+ }
45
+ return true;
46
+ }
47
+
48
+ /**
49
+ * @since 0.3.16-canary.0
50
+ */
51
+ export const DEFAULT_BINDING_SLOT = { name: undefined, tags: [] } satisfies BindingSlot;
52
+
53
+ /**
54
+ * @since 0.3.16-canary.0
55
+ */
56
+ export function bindingSlotToString(slot: BindingSlot): string {
57
+ if (slot.name === undefined && slot.tags.length === 0) {
58
+ return "default";
59
+ }
60
+ const parts: Array<string> = [];
61
+ if (slot.name !== undefined) {
62
+ parts.push(`name:${slot.name}`);
63
+ }
64
+ for (const [tagKey, tagValue] of slot.tags) {
65
+ parts.push(`tag:${tagKey}=${String(tagValue)}`);
66
+ }
67
+ return parts.join(",");
68
+ }
69
+
70
+ // ── BindingBase ───────────────────────────────────────────────────────────────
71
+
72
+ interface BindingBase<Value> {
73
+ readonly id: BindingIdentifier;
74
+ readonly token: Token<Value> | Constructor<Value>;
75
+ readonly slot: BindingSlot;
76
+ readonly predicate?: (ctx: ConstraintContext) => boolean;
77
+ }
78
+
79
+ type BindingBaseKeys = keyof BindingBase<unknown>;
80
+
81
+ // ── Binding kinds ─────────────────────────────────────────────────────────────
82
+
83
+ /**
84
+ * @since 0.3.16-canary.0
85
+ */
86
+ export interface ClassBinding<Value> extends BindingBase<Value> {
87
+ readonly kind: "class";
88
+ readonly target: Constructor<Value>;
89
+ readonly scope: BindingScope;
90
+ readonly onActivation?: ActivationHandler<Value>;
91
+ readonly onDeactivation?: DeactivationHandler<Value>;
92
+ }
93
+
94
+ /**
95
+ * @since 0.3.16-canary.0
96
+ */
97
+ export interface DynamicBinding<Value> extends BindingBase<Value> {
98
+ readonly kind: "dynamic";
99
+ readonly factory: (ctx: ResolutionContext) => Value;
100
+ readonly scope: BindingScope;
101
+ readonly onActivation?: ActivationHandler<Value>;
102
+ readonly onDeactivation?: DeactivationHandler<Value>;
103
+ }
104
+
105
+ /**
106
+ * @since 0.3.16-canary.0
107
+ */
108
+ export interface DynamicAsyncBinding<Value> extends BindingBase<Value> {
109
+ readonly kind: "dynamic-async";
110
+ readonly factory: (ctx: ResolutionContext) => Promise<Value>;
111
+ readonly scope: BindingScope;
112
+ readonly onActivation?: ActivationHandler<Value>;
113
+ readonly onDeactivation?: DeactivationHandler<Value>;
114
+ }
115
+
116
+ /**
117
+ * @since 0.3.16-canary.0
118
+ */
119
+ export interface ResolvedBinding<Value> extends BindingBase<Value> {
120
+ readonly kind: "resolved";
121
+ readonly factory: (...args: Array<unknown>) => Value;
122
+ readonly deps: ReadonlyArray<InjectionDescriptor>;
123
+ readonly scope: BindingScope;
124
+ readonly onActivation?: ActivationHandler<Value>;
125
+ readonly onDeactivation?: DeactivationHandler<Value>;
126
+ }
127
+
128
+ /**
129
+ * @since 0.3.16-canary.0
130
+ */
131
+ export interface ResolvedAsyncBinding<Value> extends BindingBase<Value> {
132
+ readonly kind: "resolved-async";
133
+ readonly factory: (...args: Array<unknown>) => Promise<Value>;
134
+ readonly deps: ReadonlyArray<InjectionDescriptor>;
135
+ readonly scope: BindingScope;
136
+ readonly onActivation?: ActivationHandler<Value>;
137
+ readonly onDeactivation?: DeactivationHandler<Value>;
138
+ }
139
+
140
+ /**
141
+ * @since 0.3.16-canary.0
142
+ */
143
+ export interface ConstantBinding<Value> extends BindingBase<Value> {
144
+ readonly kind: "constant";
145
+ readonly value: Value;
146
+ readonly scope: "singleton";
147
+ readonly onActivation?: ActivationHandler<Value>;
148
+ readonly onDeactivation?: DeactivationHandler<Value>;
149
+ }
150
+
151
+ /**
152
+ * @since 0.3.16-canary.0
153
+ */
154
+ export interface AliasBinding<Value> extends BindingBase<Value> {
155
+ readonly kind: "alias";
156
+ readonly target: Token<Value> | Constructor<Value>;
157
+ }
158
+
159
+ /**
160
+ * @since 0.3.16-canary.0
161
+ */
162
+ export type Binding<Value = unknown> =
163
+ | ClassBinding<Value>
164
+ | DynamicBinding<Value>
165
+ | DynamicAsyncBinding<Value>
166
+ | ResolvedBinding<Value>
167
+ | ResolvedAsyncBinding<Value>
168
+ | ConstantBinding<Value>
169
+ | AliasBinding<Value>;
170
+
171
+ /**
172
+ * Builder-only payload before `id`, `token`, `slot`, and `predicate` are applied.
173
+ *
174
+ * @since 0.3.16-canary.0
175
+ */
176
+ export type PartialBinding<Value> =
177
+ | Omit<ClassBinding<Value>, BindingBaseKeys>
178
+ | Omit<DynamicBinding<Value>, BindingBaseKeys>
179
+ | Omit<DynamicAsyncBinding<Value>, BindingBaseKeys>
180
+ | Omit<ResolvedBinding<Value>, BindingBaseKeys>
181
+ | Omit<ResolvedAsyncBinding<Value>, BindingBaseKeys>
182
+ | Omit<ConstantBinding<Value>, BindingBaseKeys>
183
+ | Omit<AliasBinding<Value>, BindingBaseKeys>;
184
+
185
+ // ── ID generation ─────────────────────────────────────────────────────────────
186
+
187
+ let _idCounter = 0;
188
+ /**
189
+ * @since 0.3.16-canary.0
190
+ */
191
+ export function generateBindingId(): BindingIdentifier {
192
+ return String(++_idCounter) as BindingIdentifier;
193
+ }
194
+
195
+ // ── Builder interfaces ────────────────────────────────────────────────────────
196
+
197
+ /**
198
+ * Common slot-constraint + id methods shared by all concrete binding builders.
199
+ *
200
+ * @since 0.3.16-canary.0
201
+ */
202
+ export interface SlotConstrainedBuilder {
203
+ when(predicate: (ctx: ConstraintContext) => boolean): this;
204
+ whenNamed(name: string): this;
205
+ whenTagged(tag: string, value: unknown): this;
206
+ whenDefault(): this;
207
+ id(): BindingIdentifier;
208
+ }
209
+
210
+ /**
211
+ * @since 0.3.16-canary.0
212
+ */
213
+ export interface BindToBuilder<Value> {
214
+ to(type: Constructor<Value>): BindingBuilder<Value>;
215
+ toSelf(): BindingBuilder<Value>;
216
+ toConstantValue(value: Value): ConstantBindingBuilder<Value>;
217
+ toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
218
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
219
+ toResolved<const Deps extends ReadonlyArray<DependencyKey>>(
220
+ factory: (...args: { [K in keyof Deps]: TokenValue<NoInfer<Deps>[K]> }) => Value,
221
+ deps: Deps,
222
+ ): BindingBuilder<Value>;
223
+ toResolvedAsync<const Deps extends ReadonlyArray<DependencyKey>>(
224
+ factory: (...args: { [K in keyof Deps]: TokenValue<NoInfer<Deps>[K]> }) => Promise<Value>,
225
+ deps: Deps,
226
+ ): BindingBuilder<Value>;
227
+ toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
228
+ }
229
+
230
+ /**
231
+ * @since 0.3.16-canary.0
232
+ */
233
+ export interface BindingBuilder<Value> extends SlotConstrainedBuilder {
234
+ singleton(): SingletonBindingBuilder<Value>;
235
+ transient(): TransientBindingBuilder<Value>;
236
+ scoped(): ScopedBindingBuilder<Value>;
237
+ }
238
+
239
+ /**
240
+ * @since 0.3.16-canary.0
241
+ */
242
+ export interface ConstantBindingBuilder<Value> extends SlotConstrainedBuilder {
243
+ onActivation(fn: ActivationHandler<Value>): SingletonLifecycleBuilder<Value>;
244
+ onDeactivation(fn: DeactivationHandler<Value>): SingletonLifecycleBuilder<Value>;
245
+ }
246
+
247
+ /**
248
+ * @since 0.3.16-canary.0
249
+ */
250
+ export interface AliasBindingBuilder extends SlotConstrainedBuilder {}
251
+
252
+ /**
253
+ * @since 0.3.16-canary.0
254
+ */
255
+ export interface SingletonBindingBuilder<Value> {
256
+ onActivation(fn: ActivationHandler<Value>): this;
257
+ onDeactivation(fn: DeactivationHandler<Value>): this;
258
+ id(): BindingIdentifier;
259
+ }
260
+
261
+ /**
262
+ * @since 0.3.16-canary.0
263
+ */
264
+ export interface TransientBindingBuilder<Value> {
265
+ onActivation(fn: ActivationHandler<Value>): this;
266
+ id(): BindingIdentifier;
267
+ }
268
+
269
+ /**
270
+ * @since 0.3.16-canary.0
271
+ */
272
+ export interface ScopedBindingBuilder<Value> extends TransientBindingBuilder<Value> {}
273
+
274
+ /**
275
+ * @since 0.3.16-canary.0
276
+ */
277
+ export interface SingletonLifecycleBuilder<Value> {
278
+ onActivation(fn: ActivationHandler<Value>): this;
279
+ onDeactivation(fn: DeactivationHandler<Value>): this;
280
+ id(): BindingIdentifier;
281
+ }
@@ -0,0 +1,149 @@
1
+ import type { BindingTag, ConstraintContext, Constructor } from "#/types";
2
+ import type { Token } from "#/token";
3
+ import { tokenName } from "#/token";
4
+
5
+ function tokenNameOf(token: Token<unknown> | Constructor): string {
6
+ return tokenName(token);
7
+ }
8
+
9
+ /**
10
+ * @since 0.3.16-canary.0
11
+ */
12
+ export function whenParentIs(
13
+ token: Token<unknown> | Constructor,
14
+ ): (constraintContext: ConstraintContext) => boolean {
15
+ const tokenDisplayName = tokenNameOf(token);
16
+ return (constraintContext) =>
17
+ constraintContext.parent !== undefined &&
18
+ constraintContext.parent.tokenName === tokenDisplayName;
19
+ }
20
+
21
+ /**
22
+ * @since 0.3.16-canary.0
23
+ */
24
+ export function whenNoParentIs(
25
+ token: Token<unknown> | Constructor,
26
+ ): (constraintContext: ConstraintContext) => boolean {
27
+ const tokenDisplayName = tokenNameOf(token);
28
+ return (constraintContext) =>
29
+ constraintContext.parent === undefined ||
30
+ constraintContext.parent.tokenName !== tokenDisplayName;
31
+ }
32
+
33
+ /**
34
+ * @since 0.3.16-canary.0
35
+ */
36
+ export function whenAnyAncestorIs(
37
+ token: Token<unknown> | Constructor,
38
+ ): (constraintContext: ConstraintContext) => boolean {
39
+ const tokenDisplayName = tokenNameOf(token);
40
+ return (constraintContext) =>
41
+ constraintContext.ancestors.some(
42
+ (ancestorFrame) => ancestorFrame.tokenName === tokenDisplayName,
43
+ );
44
+ }
45
+
46
+ /**
47
+ * @since 0.3.16-canary.0
48
+ */
49
+ export function whenNoAncestorIs(
50
+ token: Token<unknown> | Constructor,
51
+ ): (constraintContext: ConstraintContext) => boolean {
52
+ const tokenDisplayName = tokenNameOf(token);
53
+ return (constraintContext) =>
54
+ constraintContext.ancestors.every(
55
+ (ancestorFrame) => ancestorFrame.tokenName !== tokenDisplayName,
56
+ );
57
+ }
58
+
59
+ /**
60
+ * @since 0.3.16-canary.0
61
+ */
62
+ export function whenParentNamed(name: string): (constraintContext: ConstraintContext) => boolean {
63
+ return (constraintContext) =>
64
+ constraintContext.parent !== undefined && constraintContext.parent.slot.name === name;
65
+ }
66
+
67
+ /**
68
+ * @since 0.3.16-canary.0
69
+ */
70
+ export function whenAnyAncestorNamed(
71
+ name: string,
72
+ ): (constraintContext: ConstraintContext) => boolean {
73
+ return (constraintContext) =>
74
+ constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.name === name);
75
+ }
76
+
77
+ /**
78
+ * @since 0.3.16-canary.0
79
+ */
80
+ export function whenParentTagged(
81
+ tag: string,
82
+ value: unknown,
83
+ ): (constraintContext: ConstraintContext) => boolean {
84
+ return (constraintContext) =>
85
+ constraintContext.parent !== undefined &&
86
+ constraintContext.parent.slot.tags.some(
87
+ ([tagKey, tagValue]) => tagKey === tag && Object.is(tagValue, value),
88
+ );
89
+ }
90
+
91
+ /**
92
+ * @since 0.3.16-canary.0
93
+ */
94
+ export function whenAnyAncestorTagged(
95
+ tag: string,
96
+ value: unknown,
97
+ ): (constraintContext: ConstraintContext) => boolean {
98
+ return (constraintContext) =>
99
+ constraintContext.ancestors.some((ancestorFrame) =>
100
+ ancestorFrame.slot.tags.some(
101
+ ([tagKey, tagValue]) => tagKey === tag && Object.is(tagValue, value),
102
+ ),
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Matches when the direct parent slot carries **all** of the given tag pairs.
108
+ * Equivalent to AND-composing multiple `whenParentTagged` calls but evaluates
109
+ * in a single predicate invocation — no intermediate closure allocations.
110
+ *
111
+ * @since 0.3.16-canary.1
112
+ */
113
+ export function whenParentTaggedAll(
114
+ tags: ReadonlyArray<BindingTag>,
115
+ ): (constraintContext: ConstraintContext) => boolean {
116
+ return (constraintContext) => {
117
+ const { parent } = constraintContext;
118
+ if (parent === undefined) {
119
+ return false;
120
+ }
121
+ const { tags: parentTags } = parent.slot;
122
+ return tags.every(([tagKey, tagValue]) =>
123
+ parentTags.some(
124
+ ([otherKey, otherValue]) => otherKey === tagKey && Object.is(otherValue, tagValue),
125
+ ),
126
+ );
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Matches when at least one ancestor slot carries **all** of the given tag pairs.
132
+ * Equivalent to AND-composing multiple `whenAnyAncestorTagged` calls but evaluates
133
+ * in a single predicate invocation — no intermediate closure allocations.
134
+ *
135
+ * @since 0.3.16-canary.1
136
+ */
137
+ export function whenAnyAncestorTaggedAll(
138
+ tags: ReadonlyArray<BindingTag>,
139
+ ): (constraintContext: ConstraintContext) => boolean {
140
+ return (constraintContext) =>
141
+ constraintContext.ancestors.some((frame) => {
142
+ const { tags: frameTags } = frame.slot;
143
+ return tags.every(([tagKey, tagValue]) =>
144
+ frameTags.some(
145
+ ([otherKey, otherValue]) => otherKey === tagKey && Object.is(otherValue, tagValue),
146
+ ),
147
+ );
148
+ });
149
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A class (newable) that produces `Value`. Rest parameters are `never[]` so
3
+ * real classes with typed constructors remain assignable under
4
+ * `strictFunctionTypes` (unlike `unknown[]`, which is not assignable from
5
+ * narrower parameter types). Runtime construction still uses the real shape;
6
+ * this alias is the DI “class token” surface only.
7
+ *
8
+ * @since 0.3.16-canary.0
9
+ */
10
+ export type Constructor<Value = unknown> = new (...args: Array<never>) => Value;
11
+
12
+ /**
13
+ * Class constructor as invoked by the resolver after metadata-driven
14
+ * resolution of `unknown[]` dependencies — separate from {@link Constructor},
15
+ * which is the public assignable class token.
16
+ *
17
+ * @since 0.3.16-canary.0
18
+ */
19
+ export type ConstructorInvocation = new (...args: Array<unknown>) => unknown;