@codefast/di 0.3.13-canary.4

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 (50) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +21 -0
  3. package/README.md +572 -0
  4. package/dist/binding-select.d.mts +22 -0
  5. package/dist/binding-select.mjs +50 -0
  6. package/dist/binding.d.mts +219 -0
  7. package/dist/binding.mjs +240 -0
  8. package/dist/constraints.d.mts +18 -0
  9. package/dist/constraints.mjs +24 -0
  10. package/dist/container.d.mts +82 -0
  11. package/dist/container.mjs +406 -0
  12. package/dist/decorators/inject.d.mts +24 -0
  13. package/dist/decorators/inject.mjs +69 -0
  14. package/dist/decorators/injectable.d.mts +40 -0
  15. package/dist/decorators/injectable.mjs +62 -0
  16. package/dist/decorators/lifecycle-decorators.d.mts +13 -0
  17. package/dist/decorators/lifecycle-decorators.mjs +34 -0
  18. package/dist/dependency-graph.d.mts +35 -0
  19. package/dist/dependency-graph.mjs +126 -0
  20. package/dist/environment.d.mts +14 -0
  21. package/dist/environment.mjs +20 -0
  22. package/dist/errors.d.mts +100 -0
  23. package/dist/errors.mjs +152 -0
  24. package/dist/index.d.mts +10 -0
  25. package/dist/index.mjs +8 -0
  26. package/dist/inspector.d.mts +76 -0
  27. package/dist/inspector.mjs +247 -0
  28. package/dist/lifecycle.d.mts +34 -0
  29. package/dist/lifecycle.mjs +83 -0
  30. package/dist/metadata/metadata-keys.d.mts +17 -0
  31. package/dist/metadata/metadata-keys.mjs +19 -0
  32. package/dist/metadata/metadata-types.d.mts +55 -0
  33. package/dist/metadata/metadata-types.mjs +1 -0
  34. package/dist/metadata/param-registry.d.mts +16 -0
  35. package/dist/metadata/param-registry.mjs +25 -0
  36. package/dist/metadata/symbol-metadata-reader.d.mts +15 -0
  37. package/dist/metadata/symbol-metadata-reader.mjs +32 -0
  38. package/dist/module.d.mts +60 -0
  39. package/dist/module.mjs +57 -0
  40. package/dist/registry.d.mts +38 -0
  41. package/dist/registry.mjs +65 -0
  42. package/dist/resolver.d.mts +102 -0
  43. package/dist/resolver.mjs +361 -0
  44. package/dist/scope-validation.d.mts +20 -0
  45. package/dist/scope-validation.mjs +34 -0
  46. package/dist/scope.d.mts +80 -0
  47. package/dist/scope.mjs +185 -0
  48. package/dist/token.d.mts +20 -0
  49. package/dist/token.mjs +9 -0
  50. package/package.json +157 -0
@@ -0,0 +1,50 @@
1
+ import { InternalError, NoMatchingBindingError, TokenNotBoundError } from "./errors.mjs";
2
+ //#region src/binding-select.ts
3
+ /** Returns a human-readable label for a token or constructor (used in error messages and graph output). */
4
+ function registryKeyLabel(key) {
5
+ if (typeof key === "function") return key.name.length > 0 ? key.name : "(anonymous class)";
6
+ return key.name.trim().length > 0 ? key.name : "(anonymous token)";
7
+ }
8
+ /**
9
+ * Applies resolve hints and optional constraint predicates to a binding list.
10
+ */
11
+ function filterMatchingBindings(bindings, hint, constraintCtx) {
12
+ let candidates = [...bindings];
13
+ if (hint?.name !== void 0) candidates = candidates.filter((binding) => binding.bindingName === hint.name);
14
+ if (hint?.tag !== void 0) {
15
+ const [tagKey, tagValue] = hint.tag;
16
+ candidates = candidates.filter((binding) => Object.is(binding.tags.get(tagKey), tagValue));
17
+ }
18
+ if (constraintCtx !== void 0) candidates = candidates.filter((binding) => binding.constraint === void 0 || binding.constraint(constraintCtx));
19
+ return candidates;
20
+ }
21
+ /**
22
+ * Picks the binding that would be used for resolution with the given hint (same rules as {@link DependencyResolver}).
23
+ * When `constraintCtx` is set, bindings with a {@link BindingBuilder.when} predicate must pass it.
24
+ */
25
+ function selectBindingForRegistry(bindings, hint, tokenLabel, pathLabels, constraintCtx) {
26
+ if (bindings.length === 0) throw new TokenNotBoundError(tokenLabel, [...pathLabels]);
27
+ const candidates = filterMatchingBindings(bindings, hint, constraintCtx);
28
+ if (candidates.length === 1) {
29
+ const [only] = candidates;
30
+ if (only === void 0) throw new InternalError(`Internal: expected binding candidate for "${tokenLabel}" (resolution path: ${pathLabels.join(" -> ")})`);
31
+ return only;
32
+ }
33
+ if (candidates.length === 0) {
34
+ if (hint !== void 0 && (hint.name !== void 0 || hint.tag !== void 0)) throw new NoMatchingBindingError(tokenLabel, hint, [...pathLabels]);
35
+ throw new TokenNotBoundError(tokenLabel, [...pathLabels]);
36
+ }
37
+ throw new InternalError(`Ambiguous binding for "${tokenLabel}": ${String(candidates.length)} candidates matched after applying ResolveHint (resolution path: ${pathLabels.join(" -> ")})`);
38
+ }
39
+ /**
40
+ * Resolves the effective binding for a registry key using the default (no-hint) selection rules.
41
+ */
42
+ function selectDefaultBindingForKey(lookup, key, pathPrefix) {
43
+ const label = registryKeyLabel(key);
44
+ const nextPath = [...pathPrefix, label];
45
+ const bindings = lookup(key);
46
+ if (bindings === void 0 || bindings.length === 0) throw new TokenNotBoundError(label, nextPath);
47
+ return selectBindingForRegistry(bindings, void 0, label, nextPath, void 0);
48
+ }
49
+ //#endregion
50
+ export { filterMatchingBindings, registryKeyLabel, selectBindingForRegistry, selectDefaultBindingForKey };
@@ -0,0 +1,219 @@
1
+ import { Token, TokenValue } from "./token.mjs";
2
+ import { RegistryKey } from "./registry.mjs";
3
+
4
+ //#region src/binding.d.ts
5
+ declare const bindingIdentifierBrand: unique symbol;
6
+ /**
7
+ * Stable, opaque identifier for a single binding entry inside the registry.
8
+ */
9
+ type BindingIdentifier = string & {
10
+ readonly [bindingIdentifierBrand]: void;
11
+ };
12
+ /**
13
+ * Allocates a new opaque binding identifier (used when `.id(...)` is not supplied).
14
+ */
15
+ declare function createBindingIdentifier(): BindingIdentifier;
16
+ /**
17
+ * Runtime constructor token used as a registry key (no reflection metadata).
18
+ */
19
+ type Constructor<Value> = abstract new (...args: never[]) => Value;
20
+ /** Lifetime strategy for a resolved instance. */
21
+ type BindingScope = "singleton" | "transient" | "scoped";
22
+ /**
23
+ * Hint for disambiguating multi-bindings registered against the same token or constructor.
24
+ */
25
+ type ResolveHint = {
26
+ readonly name?: string;
27
+ readonly tag?: readonly [tag: string, value: unknown];
28
+ };
29
+ type ResolveOptions = ResolveHint;
30
+ /**
31
+ * Snapshot of a binding on the materialization stack (for {@link ConstraintContext}).
32
+ */
33
+ type ConstraintBindingKind = "constant" | "class" | "dynamic" | "async-dynamic" | "resolved" | "alias";
34
+ type ConstraintParentFrame = {
35
+ readonly registryKey: RegistryKey;
36
+ readonly bindingId: BindingIdentifier;
37
+ readonly bindingKind: ConstraintBindingKind;
38
+ readonly tags: ReadonlyMap<string, unknown>;
39
+ readonly scope: BindingScope;
40
+ };
41
+ /**
42
+ * @alias {@link ConstraintParentFrame} — frame on the materialization stack during resolution.
43
+ */
44
+ type MaterializationFrame = ConstraintParentFrame;
45
+ /**
46
+ * Context for {@link BindingBuilder.when} predicates: path, ancestor metadata, and the current resolve hint.
47
+ */
48
+ type ConstraintContext = {
49
+ readonly resolutionPath: readonly string[];
50
+ readonly materializationStack: readonly ConstraintParentFrame[];
51
+ readonly parent: ConstraintParentFrame | undefined;
52
+ readonly ancestors: readonly ConstraintParentFrame[];
53
+ readonly currentResolveHint: ResolveHint | undefined;
54
+ };
55
+ /**
56
+ * Context passed to factories and lifecycle hooks so nested dependencies resolve with the same path rules.
57
+ *
58
+ * `graph` exposes the current position in the dependency graph (resolution path, materialization
59
+ * stack, parent/ancestor frames) — used by {@link BindingBuilder.when} predicates to implement
60
+ * context-sensitive bindings such as `whenParentIs` or `whenAnyAncestorIs`.
61
+ */
62
+ type ResolutionContext = {
63
+ readonly resolve: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value;
64
+ readonly resolveAsync: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Promise<Value>;
65
+ readonly resolveOptional: <Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveOptions) => Value | undefined; /** Dependency-graph navigation context — path, materialization stack, parent/ancestor frames. */
66
+ readonly graph: ConstraintContext;
67
+ };
68
+ type BindingLifecycle = {
69
+ readonly bindingName?: string;
70
+ readonly tags: ReadonlyMap<string, unknown>;
71
+ readonly onActivation?: ActivationHandler<unknown>;
72
+ readonly onDeactivation?: DeactivationHandler<unknown>;
73
+ readonly constraint?: (ctx: ConstraintContext) => boolean;
74
+ };
75
+ /**
76
+ * Called after an instance is constructed (and after `@postConstruct`).
77
+ * The return value replaces the instance in the scope cache — use this to wrap with a proxy or
78
+ * apply post-processing. May be async; async handlers require `resolveAsync`.
79
+ */
80
+ type ActivationHandler<Value> = (ctx: ResolutionContext, instance: Value) => Value | Promise<Value>;
81
+ /**
82
+ * Called before a singleton/scoped instance is evicted from the scope cache.
83
+ * Runs after `@preDestroy`. May be async; async deactivation requires `disposeAsync` / `unloadAsync`.
84
+ */
85
+ type DeactivationHandler<Value> = (instance: Value) => void | Promise<void>;
86
+ type BindingBase = BindingLifecycle & {
87
+ readonly id: BindingIdentifier;
88
+ readonly scope: BindingScope; /** Set when the binding was registered from {@link Module} / {@link AsyncModule} setup. */
89
+ readonly moduleId?: string;
90
+ };
91
+ /** Binding backed by a pre-existing constant value; always singleton, no construction cost. */
92
+ type ConstantBinding<Value> = BindingBase & {
93
+ readonly kind: "constant";
94
+ readonly value: Value;
95
+ };
96
+ /** Binding that constructs `implementationClass` via the container's metadata-driven instantiation. */
97
+ type ClassBinding<Value> = BindingBase & {
98
+ readonly kind: "class";
99
+ readonly implementationClass: Constructor<Value>;
100
+ };
101
+ /** Binding backed by a synchronous factory that receives a {@link ResolutionContext}. */
102
+ type DynamicBinding<Value> = BindingBase & {
103
+ readonly kind: "dynamic";
104
+ readonly factory: (ctx: ResolutionContext) => Value;
105
+ };
106
+ /** Binding backed by an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
107
+ type AsyncDynamicBinding<Value> = BindingBase & {
108
+ readonly kind: "async-dynamic";
109
+ readonly factory: (ctx: ResolutionContext) => Promise<Value>;
110
+ };
111
+ /** Binding whose dependencies are declared statically and pre-resolved before the factory is called. */
112
+ type ResolvedBinding<Value> = BindingBase & {
113
+ readonly kind: "resolved";
114
+ readonly dependencyTokens: readonly (Token<unknown> | Constructor<unknown>)[];
115
+ readonly factory: (...args: unknown[]) => Value;
116
+ };
117
+ /** Binding that forwards resolution to `targetToken`; the container resolves whatever is bound there. */
118
+ type AliasBinding<Value> = BindingBase & {
119
+ readonly kind: "alias";
120
+ readonly targetToken: Token<Value>;
121
+ };
122
+ /**
123
+ * Discriminated union of all binding strategies the container can resolve.
124
+ */
125
+ type Binding<Value> = ConstantBinding<Value> | ClassBinding<Value> | DynamicBinding<Value> | AsyncDynamicBinding<Value> | ResolvedBinding<Value> | AliasBinding<Value>;
126
+ /**
127
+ * Callbacks injected by the owning container to sync each builder mutation into the registry.
128
+ * `register` fires once when a `to*(…)` strategy is selected; `update` fires on every
129
+ * subsequent chain call (`.singleton()`, `.onActivation()`, …).
130
+ */
131
+ type RegistryCallbacks<Value> = {
132
+ readonly register?: (binding: Binding<Value>) => void;
133
+ readonly update?: (binding: Binding<Value>) => void;
134
+ };
135
+ declare class BindingBuilder<Value> {
136
+ protected readonly bindingKey: Token<Value> | Constructor<Value>;
137
+ private strategy;
138
+ private scope;
139
+ private isScopeExplicit;
140
+ private explicitId;
141
+ private bindingName;
142
+ private readonly tags;
143
+ private readonly constraintPredicates;
144
+ private onActivationHandler;
145
+ private onDeactivationHandler;
146
+ private readonly moduleId;
147
+ private currentBinding;
148
+ private readonly callbacks;
149
+ constructor(bindingKey: Token<Value> | Constructor<Value>, moduleId?: string, callbacks?: RegistryCallbacks<Value>);
150
+ /** Binds the token to a concrete implementation class; the container constructs it on demand. */
151
+ to<C extends Constructor<Value>>(implementationClass: C): TransientBindingBuilder<Value>;
152
+ /** Binds the class key to itself — only valid when the key is a constructor. */
153
+ toSelf(): TransientBindingBuilder<Value>;
154
+ /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
155
+ toConstantValue<const ConcreteValue extends Value>(value: ConcreteValue): ConstantBindingBuilder<Value>;
156
+ /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
157
+ toDynamic(factory: (ctx: ResolutionContext) => Value): TransientBindingBuilder<Value>;
158
+ /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
159
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): TransientBindingBuilder<Value>;
160
+ /**
161
+ * Binds to a factory whose dependencies are declared explicitly in `deps` and pre-resolved by
162
+ * the container before the factory is called — no `ResolutionContext` needed inside the factory.
163
+ */
164
+ toResolved<Deps extends readonly (Token<unknown> | Constructor<unknown>)[]>(factory: (...args: { [Index in keyof Deps]: TokenValue<Deps[Index]> }) => Value, deps: Deps): TransientBindingBuilder<Value>;
165
+ /** Redirects resolution to `targetToken`; the container resolves whatever is bound there. */
166
+ toAlias(targetToken: Token<Value>): TransientBindingBuilder<Value>;
167
+ /** One instance per container; supports `onDeactivation`. */
168
+ singleton(): SingletonBindingBuilder<Value>;
169
+ /** New instance on every resolution (default scope). */
170
+ transient(): TransientBindingBuilder<Value>;
171
+ /** One instance per child container scope. */
172
+ scoped(): ScopedBindingBuilder<Value>;
173
+ /** Called with the resolved instance after construction; the return value replaces the instance. */
174
+ onActivation(handler: ActivationHandler<Value>): this;
175
+ /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
176
+ onDeactivation(handler: DeactivationHandler<Value>): this;
177
+ /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
178
+ whenNamed(name: string): this;
179
+ /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
180
+ whenTagged(tag: string, tagValue: unknown): this;
181
+ /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
182
+ when(constraint: (ctx: ConstraintContext) => boolean): this;
183
+ /**
184
+ * Returns the binding's stable ID, allocating one if needed.
185
+ * Passing an `identifier` pre-sets the ID before a `to*()` call — useful when modules need to
186
+ * reference a binding ID before it is registered.
187
+ */
188
+ id(): BindingIdentifier;
189
+ id(identifier: BindingIdentifier): BindingIdentifier;
190
+ private registerWithStrategy;
191
+ private refreshRegisteredBinding;
192
+ private assertScopeMutable;
193
+ private createBinding;
194
+ }
195
+ /**
196
+ * Builder returned after calling `.singleton()` — exposes `onDeactivation`.
197
+ */
198
+ type SingletonBindingBuilder<Value> = BindingBuilder<Value>;
199
+ /**
200
+ * Builder returned after calling `.transient()`, or any strategy method before a scope is set.
201
+ * Does NOT expose `onDeactivation` at the type level.
202
+ */
203
+ type TransientBindingBuilder<Value> = Omit<BindingBuilder<Value>, "onDeactivation">;
204
+ /**
205
+ * Builder returned after calling `.scoped()`.
206
+ * Does NOT expose `onDeactivation` at the type level.
207
+ */
208
+ type ScopedBindingBuilder<Value> = Omit<BindingBuilder<Value>, "onDeactivation">;
209
+ /**
210
+ * Builder returned after calling `.toConstantValue()`.
211
+ */
212
+ type ConstantBindingBuilder<Value> = Omit<SingletonBindingBuilder<Value>, "singleton" | "transient" | "scoped">;
213
+ /**
214
+ * Starts a fluent binding for the given token or constructor key.
215
+ */
216
+ declare function bind<Value>(key: Token<Value>): BindingBuilder<Value>;
217
+ declare function bind<Value>(key: Constructor<Value>): BindingBuilder<Value>;
218
+ //#endregion
219
+ export { ActivationHandler, AliasBinding, AsyncDynamicBinding, Binding, BindingBuilder, BindingIdentifier, BindingScope, ClassBinding, ConstantBinding, ConstantBindingBuilder, ConstraintBindingKind, ConstraintContext, ConstraintParentFrame, Constructor, DeactivationHandler, DynamicBinding, MaterializationFrame, ResolutionContext, ResolveHint, ResolveOptions, ResolvedBinding, ScopedBindingBuilder, SingletonBindingBuilder, TransientBindingBuilder, bind, createBindingIdentifier };
@@ -0,0 +1,240 @@
1
+ import { InternalError } from "./errors.mjs";
2
+ //#region src/binding.ts
3
+ /**
4
+ * Allocates a new opaque binding identifier (used when `.id(...)` is not supplied).
5
+ */
6
+ function createBindingIdentifier() {
7
+ return globalThis.crypto.randomUUID();
8
+ }
9
+ var BindingBuilder = class {
10
+ strategy = { type: "unset" };
11
+ scope = "transient";
12
+ isScopeExplicit = false;
13
+ explicitId;
14
+ bindingName;
15
+ tags = /* @__PURE__ */ new Map();
16
+ constraintPredicates = [];
17
+ onActivationHandler;
18
+ onDeactivationHandler;
19
+ moduleId;
20
+ currentBinding;
21
+ callbacks;
22
+ constructor(bindingKey, moduleId, callbacks) {
23
+ this.bindingKey = bindingKey;
24
+ this.moduleId = moduleId;
25
+ this.callbacks = callbacks ?? {};
26
+ }
27
+ /** Binds the token to a concrete implementation class; the container constructs it on demand. */
28
+ to(implementationClass) {
29
+ this.registerWithStrategy({
30
+ type: "class",
31
+ implementationClass
32
+ });
33
+ return this;
34
+ }
35
+ /** Binds the class key to itself — only valid when the key is a constructor. */
36
+ toSelf() {
37
+ if (typeof this.bindingKey !== "function") throw new InternalError("toSelf() requires the binding key to be a constructor; use bind(SomeClass) or call to(Class) instead.");
38
+ this.registerWithStrategy({
39
+ type: "class",
40
+ implementationClass: this.bindingKey
41
+ });
42
+ return this;
43
+ }
44
+ /** Binds the token to a pre-existing value; always resolved as singleton, no construction. */
45
+ toConstantValue(value) {
46
+ this.scope = "singleton";
47
+ this.registerWithStrategy({
48
+ type: "constant",
49
+ value
50
+ });
51
+ return this;
52
+ }
53
+ /** Binds to a synchronous factory; `ctx` provides nested resolution within the same path. */
54
+ toDynamic(factory) {
55
+ this.registerWithStrategy({
56
+ type: "dynamic",
57
+ factory
58
+ });
59
+ return this;
60
+ }
61
+ /** Binds to an async factory; must be resolved via `resolveAsync` / `resolveAllAsync`. */
62
+ toDynamicAsync(factory) {
63
+ this.registerWithStrategy({
64
+ type: "async-dynamic",
65
+ factory
66
+ });
67
+ return this;
68
+ }
69
+ /**
70
+ * Binds to a factory whose dependencies are declared explicitly in `deps` and pre-resolved by
71
+ * the container before the factory is called — no `ResolutionContext` needed inside the factory.
72
+ */
73
+ toResolved(factory, deps) {
74
+ this.registerWithStrategy({
75
+ type: "resolved",
76
+ factory,
77
+ dependencyTokens: deps
78
+ });
79
+ return this;
80
+ }
81
+ /** Redirects resolution to `targetToken`; the container resolves whatever is bound there. */
82
+ toAlias(targetToken) {
83
+ this.registerWithStrategy({
84
+ type: "alias",
85
+ targetToken
86
+ });
87
+ return this;
88
+ }
89
+ /** One instance per container; supports `onDeactivation`. */
90
+ singleton() {
91
+ this.assertScopeMutable();
92
+ this.scope = "singleton";
93
+ this.isScopeExplicit = true;
94
+ this.refreshRegisteredBinding();
95
+ return this;
96
+ }
97
+ /** New instance on every resolution (default scope). */
98
+ transient() {
99
+ this.assertScopeMutable();
100
+ this.scope = "transient";
101
+ this.isScopeExplicit = true;
102
+ this.refreshRegisteredBinding();
103
+ return this;
104
+ }
105
+ /** One instance per child container scope. */
106
+ scoped() {
107
+ this.assertScopeMutable();
108
+ this.scope = "scoped";
109
+ this.isScopeExplicit = true;
110
+ this.refreshRegisteredBinding();
111
+ return this;
112
+ }
113
+ /** Called with the resolved instance after construction; the return value replaces the instance. */
114
+ onActivation(handler) {
115
+ this.onActivationHandler = handler;
116
+ this.refreshRegisteredBinding();
117
+ return this;
118
+ }
119
+ /** @internal Keep public for runtime correctness; hidden from transient/scoped builders via type aliases. */
120
+ onDeactivation(handler) {
121
+ this.onDeactivationHandler = handler;
122
+ this.refreshRegisteredBinding();
123
+ return this;
124
+ }
125
+ /** This binding only resolves when the caller passes `{ name }` as the resolve hint. */
126
+ whenNamed(name) {
127
+ this.bindingName = name;
128
+ this.refreshRegisteredBinding();
129
+ return this;
130
+ }
131
+ /** This binding only resolves when the caller passes `{ tag: [tag, tagValue] }` as the resolve hint. */
132
+ whenTagged(tag, tagValue) {
133
+ this.tags.set(tag, tagValue);
134
+ this.refreshRegisteredBinding();
135
+ return this;
136
+ }
137
+ /** Adds a custom predicate; all predicates must pass for the binding to be selected. */
138
+ when(constraint) {
139
+ this.constraintPredicates.push(constraint);
140
+ this.refreshRegisteredBinding();
141
+ return this;
142
+ }
143
+ id(identifier) {
144
+ if (this.currentBinding !== void 0) {
145
+ if (identifier !== void 0 && identifier !== this.currentBinding.id) throw new InternalError("Cannot change binding identifier after registration.");
146
+ return this.currentBinding.id;
147
+ }
148
+ if (identifier !== void 0) {
149
+ this.explicitId = identifier;
150
+ return this.explicitId;
151
+ }
152
+ this.explicitId = this.explicitId ?? createBindingIdentifier();
153
+ return this.explicitId;
154
+ }
155
+ registerWithStrategy(next) {
156
+ if (this.strategy.type !== "unset") throw new InternalError("A binding strategy was already selected; only one to*(...) chain is allowed per builder.");
157
+ this.strategy = next;
158
+ const bindingId = this.id();
159
+ const binding = this.createBinding(bindingId, next);
160
+ this.currentBinding = binding;
161
+ this.callbacks.register?.(binding);
162
+ }
163
+ refreshRegisteredBinding() {
164
+ if (this.currentBinding === void 0 || this.strategy.type === "unset") return;
165
+ const next = this.createBinding(this.currentBinding.id, this.strategy);
166
+ this.currentBinding = next;
167
+ this.callbacks.update?.(next);
168
+ }
169
+ assertScopeMutable() {
170
+ if (this.strategy.type === "constant") throw new InternalError("Constant bindings are always singleton and do not support scope changes.");
171
+ }
172
+ createBinding(id, strategy) {
173
+ const constraint = this.constraintPredicates.length === 0 ? void 0 : (ctx) => this.constraintPredicates.every((predicate) => predicate(ctx));
174
+ const lifecycle = {
175
+ bindingName: this.bindingName,
176
+ tags: new Map(this.tags),
177
+ onActivation: this.onActivationHandler,
178
+ onDeactivation: this.onDeactivationHandler,
179
+ constraint
180
+ };
181
+ const moduleFields = this.moduleId === void 0 ? {} : { moduleId: this.moduleId };
182
+ switch (strategy.type) {
183
+ case "constant": return {
184
+ ...lifecycle,
185
+ ...moduleFields,
186
+ id,
187
+ scope: "singleton",
188
+ kind: "constant",
189
+ value: strategy.value
190
+ };
191
+ case "class": return {
192
+ ...lifecycle,
193
+ ...moduleFields,
194
+ id,
195
+ scope: this.scope,
196
+ kind: "class",
197
+ implementationClass: strategy.implementationClass
198
+ };
199
+ case "dynamic": return {
200
+ ...lifecycle,
201
+ ...moduleFields,
202
+ id,
203
+ scope: this.scope,
204
+ kind: "dynamic",
205
+ factory: strategy.factory
206
+ };
207
+ case "async-dynamic": return {
208
+ ...lifecycle,
209
+ ...moduleFields,
210
+ id,
211
+ scope: this.scope,
212
+ kind: "async-dynamic",
213
+ factory: strategy.factory
214
+ };
215
+ case "resolved": return {
216
+ ...lifecycle,
217
+ ...moduleFields,
218
+ id,
219
+ scope: this.scope,
220
+ kind: "resolved",
221
+ dependencyTokens: strategy.dependencyTokens,
222
+ factory: strategy.factory
223
+ };
224
+ case "alias": return {
225
+ ...lifecycle,
226
+ ...moduleFields,
227
+ id,
228
+ scope: this.scope,
229
+ kind: "alias",
230
+ targetToken: strategy.targetToken
231
+ };
232
+ default: return strategy;
233
+ }
234
+ }
235
+ };
236
+ function bind(key) {
237
+ return new BindingBuilder(key, void 0);
238
+ }
239
+ //#endregion
240
+ export { BindingBuilder, bind, createBindingIdentifier };
@@ -0,0 +1,18 @@
1
+ import { Token } from "./token.mjs";
2
+ import { ConstraintContext, Constructor } from "./binding.mjs";
3
+
4
+ //#region src/constraints.d.ts
5
+ /**
6
+ * Matches when the direct parent materialization was registered for `registryKey`.
7
+ */
8
+ declare function whenParentIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
9
+ /**
10
+ * Matches when any ancestor on the materialization stack was registered for `registryKey`.
11
+ */
12
+ declare function whenAnyAncestorIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
13
+ /**
14
+ * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
15
+ */
16
+ declare function whenTargetTagged(tag: string, tagValue: unknown): (ctx: ConstraintContext) => boolean;
17
+ //#endregion
18
+ export { whenAnyAncestorIs, whenParentIs, whenTargetTagged };
@@ -0,0 +1,24 @@
1
+ //#region src/constraints.ts
2
+ /**
3
+ * Matches when the direct parent materialization was registered for `registryKey`.
4
+ */
5
+ function whenParentIs(registryKey) {
6
+ return (ctx) => ctx.parent?.registryKey === registryKey;
7
+ }
8
+ /**
9
+ * Matches when any ancestor on the materialization stack was registered for `registryKey`.
10
+ */
11
+ function whenAnyAncestorIs(registryKey) {
12
+ return (ctx) => ctx.materializationStack.some((frame) => frame.registryKey === registryKey);
13
+ }
14
+ /**
15
+ * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
16
+ */
17
+ function whenTargetTagged(tag, tagValue) {
18
+ return (ctx) => {
19
+ if (ctx.parent === void 0) return false;
20
+ return Object.is(ctx.parent.tags.get(tag), tagValue);
21
+ };
22
+ }
23
+ //#endregion
24
+ export { whenAnyAncestorIs, whenParentIs, whenTargetTagged };
@@ -0,0 +1,82 @@
1
+ import { Token } from "./token.mjs";
2
+ import { RegistryKey } from "./registry.mjs";
3
+ import { Binding, BindingBuilder, BindingIdentifier, Constructor, ResolveHint, ResolveOptions } from "./binding.mjs";
4
+ import { ContainerGraphJson, ContainerSnapshot, DotGraphOptions } from "./inspector.mjs";
5
+ import { AsyncModule, Module } from "./module.mjs";
6
+
7
+ //#region src/container.d.ts
8
+ type ModuleLike = Module | AsyncModule;
9
+ /**
10
+ * Public contract for an IoC container (registry, modules, resolution, lifecycle).
11
+ * Construct instances with {@link Container.create} or {@link Container.fromModules}.
12
+ *
13
+ * Implements {@link AsyncDisposable} so `await using container = Container.create()` runs
14
+ * {@link Container.dispose} automatically at scope exit (TC39 Explicit Resource Management).
15
+ */
16
+ interface Container extends AsyncDisposable {
17
+ /** Starts a fluent binding builder for the given token or constructor. */
18
+ bind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
19
+ /** Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder. */
20
+ rebind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
21
+ /** Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation. */
22
+ unbind(tokenOrId: RegistryKey | BindingIdentifier): void;
23
+ /** Same as {@link unbind} but awaits async `onDeactivation` handlers before removing. */
24
+ unbindAsync(tokenOrId: RegistryKey | BindingIdentifier): Promise<void>;
25
+ /** Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`. */
26
+ has(token: RegistryKey, hint?: ResolveHint): boolean;
27
+ /** Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async. */
28
+ resolve<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value;
29
+ /** Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings. */
30
+ resolveAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value>;
31
+ /** Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async. */
32
+ resolveAll<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value[];
33
+ /** Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories. */
34
+ resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value[]>;
35
+ /** Resolves the token or returns `undefined` if no binding is registered (never throws on missing). */
36
+ resolveOptional<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value | undefined;
37
+ /** Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op. */
38
+ load(...modules: Module[]): void;
39
+ /** Registers bindings from sync and/or async modules, awaiting each async setup in sequence. */
40
+ loadAsync(...modules: ModuleLike[]): Promise<void>;
41
+ /** Removes all bindings contributed by the given modules; runs sync deactivation on released singletons. */
42
+ unload(...modules: ModuleLike[]): void;
43
+ /** Same as {@link unload} but awaits async `onDeactivation` handlers. */
44
+ unloadAsync(...modules: ModuleLike[]): Promise<void>;
45
+ /** Eagerly constructs every singleton binding so the first request is never cold. */
46
+ initializeAsync(): Promise<void>;
47
+ /** Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added. */
48
+ loadAutoRegistered(): number;
49
+ /** Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found. */
50
+ validate(): void;
51
+ /** Returns a debug snapshot of all registered bindings and their activation state. */
52
+ inspect(): ContainerSnapshot;
53
+ /** Renders the dependency graph as a Graphviz DOT string (default) or a typed JSON object. */
54
+ generateDependencyGraph(options?: DotGraphOptions & {
55
+ format?: "dot";
56
+ }): string;
57
+ generateDependencyGraph(options: DotGraphOptions & {
58
+ format: "json";
59
+ }): ContainerGraphJson;
60
+ /** Creates a child container that inherits bindings from this container without polluting its registry. */
61
+ createChild(): Container;
62
+ /** @throws Always — container disposal is async; use `await using` or `await container.dispose()`. */
63
+ [Symbol.dispose](): never;
64
+ /** Returns the raw binding list for a token without triggering resolution. `undefined` means no binding. */
65
+ lookupBindings(token: RegistryKey): readonly Binding<unknown>[] | undefined;
66
+ /** Runs all `onDeactivation` hooks on active singletons and releases all caches. */
67
+ dispose(): Promise<void>;
68
+ [Symbol.asyncDispose](): Promise<void>;
69
+ }
70
+ /**
71
+ * Factory functions for {@link Container} instances (interface + namespace merge).
72
+ */
73
+ declare namespace Container {
74
+ /** Creates an empty container with no bindings. */
75
+ function create(): Container;
76
+ /** Creates a container and immediately loads the given sync modules. */
77
+ function fromModules(...modules: Module[]): Container;
78
+ /** Creates a container and awaits loading of sync and/or async modules. */
79
+ function fromModulesAsync(...modules: (Module | AsyncModule)[]): Promise<Container>;
80
+ }
81
+ //#endregion
82
+ export { type BindingIdentifier, Container, type ContainerGraphJson, type ContainerSnapshot, type ResolveOptions };