alloy-di 0.1.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.
Files changed (44) hide show
  1. package/README.md +69 -0
  2. package/dist/lib/container.d.ts +117 -0
  3. package/dist/lib/container.js +266 -0
  4. package/dist/lib/decorators.d.ts +177 -0
  5. package/dist/lib/decorators.js +126 -0
  6. package/dist/lib/dependency-error.js +31 -0
  7. package/dist/lib/env-detection.js +44 -0
  8. package/dist/lib/lazy.d.ts +39 -0
  9. package/dist/lib/lazy.js +18 -0
  10. package/dist/lib/providers.d.ts +112 -0
  11. package/dist/lib/providers.js +166 -0
  12. package/dist/lib/scope.d.ts +8 -0
  13. package/dist/lib/scope.js +8 -0
  14. package/dist/lib/service-identifiers.d.ts +34 -0
  15. package/dist/lib/service-identifiers.js +54 -0
  16. package/dist/lib/testing/mocking.d.ts +14 -0
  17. package/dist/lib/testing/mocking.js +109 -0
  18. package/dist/lib/testing/registry.js +19 -0
  19. package/dist/lib/types.d.ts +22 -0
  20. package/dist/lib/types.js +21 -0
  21. package/dist/plugins/core/codegen.js +288 -0
  22. package/dist/plugins/core/decorators.js +90 -0
  23. package/dist/plugins/core/discovery-store.js +78 -0
  24. package/dist/plugins/core/identifier-resolver.js +22 -0
  25. package/dist/plugins/core/lazy.js +98 -0
  26. package/dist/plugins/core/scanner.js +93 -0
  27. package/dist/plugins/core/types.d.ts +44 -0
  28. package/dist/plugins/core/utils.js +45 -0
  29. package/dist/plugins/rollup-plugin/build-utils.js +49 -0
  30. package/dist/plugins/rollup-plugin/index.d.ts +30 -0
  31. package/dist/plugins/rollup-plugin/index.js +225 -0
  32. package/dist/plugins/vite-plugin/index.d.ts +25 -0
  33. package/dist/plugins/vite-plugin/index.js +154 -0
  34. package/dist/plugins/vite-plugin/manifest-utils.js +213 -0
  35. package/dist/rollup.d.ts +2 -0
  36. package/dist/rollup.js +7 -0
  37. package/dist/runtime.d.ts +7 -0
  38. package/dist/runtime.js +8 -0
  39. package/dist/test.d.ts +50 -0
  40. package/dist/test.js +67 -0
  41. package/dist/tsconfig.tsbuildinfo +1 -0
  42. package/dist/vite.d.ts +2 -0
  43. package/dist/vite.js +7 -0
  44. package/package.json +69 -0
@@ -0,0 +1,126 @@
1
+ import { ServiceScope } from "./scope.js";
2
+
3
+ //#region src/lib/decorators.ts
4
+ /**
5
+ * Global registry for service metadata used by the runtime container.
6
+ *
7
+ * Keys are service constructors; values include the configured scope and a
8
+ * normalized dependency function that returns the declared dependencies as a readonly tuple.
9
+ *
10
+ * The Vite plugin populates this from source decorators at build time, but you can also
11
+ * register programmatically via the decorators in tests or non-plugin setups.
12
+ *
13
+ * @internal
14
+ */
15
+ const dependenciesRegistry = /* @__PURE__ */ new Map();
16
+ /**
17
+ * Create the underlying decorator implementation for both `@Injectable` and `@Singleton`.
18
+ *
19
+ * - Normalizes `dependencies` to a function so the container can evaluate lazily/memoize.
20
+ * - Registers the class metadata in {@link dependenciesRegistry}.
21
+ * - Preserves strict tuple inference through the call-site overloads of the public decorators.
22
+ *
23
+ * @param scope - The requested lifetime: `singleton` or `transient`.
24
+ * @param dependencies - Optional dependency list (array or factory returning an array).
25
+ * @returns A class decorator that records the metadata into the registry.
26
+ * @internal
27
+ */
28
+ function createDecoratorWithDeps(scope, depsOpt) {
29
+ if (typeof depsOpt === "function") {
30
+ const depsFn$1 = depsOpt;
31
+ return (target) => {
32
+ dependenciesRegistry.set(target, {
33
+ scope,
34
+ dependencies: depsFn$1
35
+ });
36
+ };
37
+ }
38
+ const deps$1 = depsOpt;
39
+ const depsFn = () => deps$1;
40
+ return (target) => {
41
+ dependenciesRegistry.set(target, {
42
+ scope,
43
+ dependencies: depsFn
44
+ });
45
+ };
46
+ }
47
+ /**
48
+ * Register scope metadata for services that declare no dependencies.
49
+ *
50
+ * @param scope - Lifetime associated with the decorated service.
51
+ * @returns A class decorator that records only the scope in {@link dependenciesRegistry}.
52
+ * @internal
53
+ */
54
+ function createDecoratorWithoutDeps(scope) {
55
+ return (target) => {
56
+ dependenciesRegistry.set(target, { scope });
57
+ };
58
+ }
59
+ /**
60
+ * Determine whether a decorator argument represents dependency metadata.
61
+ *
62
+ * @param value - The argument supplied to `@Injectable`/`@Singleton`.
63
+ * @returns True if the argument is an array or factory of dependencies.
64
+ * @internal
65
+ */
66
+ function isDependenciesArg(value) {
67
+ return typeof value === "function" || Array.isArray(value);
68
+ }
69
+ function Injectable(depsOrScope, scopeOverride) {
70
+ if (isDependenciesArg(depsOrScope)) return createDecoratorWithDeps(scopeOverride ?? ServiceScope.TRANSIENT, depsOrScope);
71
+ return createDecoratorWithoutDeps((typeof depsOrScope === "string" ? depsOrScope : void 0) ?? scopeOverride ?? ServiceScope.TRANSIENT);
72
+ }
73
+ function Singleton(dependencies) {
74
+ if (isDependenciesArg(dependencies)) return createDecoratorWithDeps(ServiceScope.SINGLETON, dependencies);
75
+ return createDecoratorWithoutDeps(ServiceScope.SINGLETON);
76
+ }
77
+ /**
78
+ * Declare dependencies as a strongly-typed readonly tuple without `as const`.
79
+ *
80
+ * This helper preserves tuple inference for strict constructor checking while keeping callsites
81
+ * concise. It returns a function so it can be used directly as `dependencies` in the decorator.
82
+ *
83
+ * @typeParam T - A readonly tuple of constructors and/or `Lazy<...>` wrappers.
84
+ * @param items - Variadic dependency list.
85
+ * @returns A function returning the same tuple (suitable for `dependencies`).
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * @Injectable(deps(Logger, Metrics))
90
+ * class AppService { constructor(l: Logger, m: Metrics) {} }
91
+ * ```
92
+ */
93
+ function deps(...items) {
94
+ return () => items;
95
+ }
96
+ /**
97
+ * Compile-time assertion: ensure constructor parameters match the resolved dependency tuple.
98
+ *
99
+ * This function has zero runtime cost and simply returns the class unchanged. It exists solely
100
+ * to force a TypeScript evaluation that surfaces mismatches (number, order, or type), including
101
+ * cases where decorator position alone may not report errors due to compiler limitations.
102
+ *
103
+ * @typeParam TDeps - Declared dependency tuple.
104
+ * @typeParam TClass - Class type with a constructor that must accept `DepInstances<TDeps>`.
105
+ * @param depsFn - A function returning the declared dependency tuple (e.g., from {@link deps}).
106
+ * @param klass - The class constructor to validate.
107
+ * @returns The same class constructor, unchanged.
108
+ *
109
+ * @example Negative test in code
110
+ * ```ts
111
+ * class Dep {}
112
+ * class Wrong {}
113
+ *
114
+ * @Injectable(deps(Dep))
115
+ * class UsesWrong { constructor(_: Wrong) {} }
116
+ *
117
+ * // @ts-expect-error mismatch detected at compile time
118
+ * assertDeps(deps(Dep), UsesWrong);
119
+ * ```
120
+ */
121
+ function assertDeps(depsFn, klass) {
122
+ return klass;
123
+ }
124
+
125
+ //#endregion
126
+ export { Injectable, Singleton, assertDeps, dependenciesRegistry, deps };
@@ -0,0 +1,31 @@
1
+ import { isConstructor, isToken } from "./types.js";
2
+ import { isLazy } from "./lazy.js";
3
+
4
+ //#region src/lib/dependency-error.ts
5
+ function describeDependency(value) {
6
+ if (isConstructor(value)) return `constructor ${value.name || "<anonymous>"}`;
7
+ if (isToken(value)) return `token(${value.description ?? value.id.toString()})`;
8
+ if (isLazy(value)) return "Lazy(import)";
9
+ if (typeof value === "function") return value.name || "<anonymous function>";
10
+ return String(value);
11
+ }
12
+ var DependencyResolutionError = class extends Error {
13
+ target;
14
+ resolutionStack;
15
+ failedDependency;
16
+ constructor(message, params) {
17
+ super(message, params.cause ? { cause: params.cause } : void 0);
18
+ this.name = "DependencyResolutionError";
19
+ this.target = params.target;
20
+ this.resolutionStack = params.resolutionStack;
21
+ this.failedDependency = params.failedDependency;
22
+ }
23
+ toDetailedString() {
24
+ const stackPath = [...this.resolutionStack.map((c) => c.name), this.target.name].filter(Boolean).join(" -> ");
25
+ const dependencyInfo = this.failedDependency ? `\nFailed dependency: ${describeDependency(this.failedDependency)}` : "";
26
+ return `${this.message}\nResolution path: ${stackPath}${dependencyInfo}`;
27
+ }
28
+ };
29
+
30
+ //#endregion
31
+ export { DependencyResolutionError };
@@ -0,0 +1,44 @@
1
+ //#region src/lib/env-detection.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+ function readImportMetaEnvFromRuntime() {
6
+ if (typeof import.meta === "undefined") return;
7
+ const candidate = import.meta;
8
+ if (!isRecord(candidate) || !("env" in candidate)) return;
9
+ const envValue = candidate.env;
10
+ if (!isRecord(envValue)) return;
11
+ const env = {};
12
+ if (typeof envValue.MODE === "string") env.MODE = envValue.MODE;
13
+ if (typeof envValue.PROD === "boolean") env.PROD = envValue.PROD;
14
+ if (typeof envValue.NODE_ENV === "string") env.NODE_ENV = envValue.NODE_ENV;
15
+ return env;
16
+ }
17
+ function readProcessNodeEnv() {
18
+ if (typeof process === "undefined") return;
19
+ const nodeEnv = "development";
20
+ return typeof nodeEnv === "string" ? nodeEnv : void 0;
21
+ }
22
+ function getImportMetaEnv(overrides) {
23
+ if (overrides?.importMetaEnv === null) return;
24
+ if (overrides?.importMetaEnv) return overrides.importMetaEnv;
25
+ return readImportMetaEnvFromRuntime();
26
+ }
27
+ function getNodeEnv(overrides) {
28
+ if (typeof overrides?.nodeEnv === "string") return overrides.nodeEnv;
29
+ if (overrides?.nodeEnv === null) return;
30
+ return readProcessNodeEnv();
31
+ }
32
+ function isDevEnvironment(overrides) {
33
+ if (typeof overrides?.isDev === "boolean") return overrides.isDev;
34
+ const nodeEnv = getNodeEnv(overrides);
35
+ if (typeof nodeEnv === "string") return nodeEnv !== "production";
36
+ const importMetaEnv = getImportMetaEnv(overrides);
37
+ if (typeof importMetaEnv?.PROD === "boolean") return !importMetaEnv.PROD;
38
+ if (typeof importMetaEnv?.MODE === "string") return importMetaEnv.MODE !== "production";
39
+ if (typeof importMetaEnv?.NODE_ENV === "string") return importMetaEnv.NODE_ENV !== "production";
40
+ return true;
41
+ }
42
+
43
+ //#endregion
44
+ export { isDevEnvironment };
@@ -0,0 +1,39 @@
1
+ import { Newable } from "./types.js";
2
+
3
+ //#region src/lib/lazy.d.ts
4
+ declare const LAZY_IDENTIFIER: unique symbol;
5
+ type Importer<T> = () => Promise<{
6
+ default: Newable<T>;
7
+ } | Newable<T>>;
8
+ interface Lazy<T> {
9
+ [LAZY_IDENTIFIER]: true;
10
+ importer: Importer<T>;
11
+ retry?: {
12
+ retries: number;
13
+ backoffMs?: number;
14
+ factor?: number;
15
+ };
16
+ }
17
+ /**
18
+ * Helper type to extract the instance type from a Newable constructor.
19
+ */
20
+ type ExtractInstanceType<T> = T extends Newable<infer I> ? I : never;
21
+ /**
22
+ * Infers the type T from an Importer function's return type.
23
+ * Handles both default exports and named exports.
24
+ */
25
+ type InferLazyType<F> = F extends (() => Promise<infer R>) ? R extends {
26
+ default: infer D;
27
+ } ? ExtractInstanceType<D> : ExtractInstanceType<R> : never;
28
+ /**
29
+ * Wraps a dynamic import of a service to mark it for lazy loading.
30
+ * When called without a type parameter, the type is automatically inferred.
31
+ * When called with a type parameter, that type is used explicitly.
32
+ * @param importer A function that returns a dynamic import, e.g., `() => import('./my.service').then(m => m.MyService)`
33
+ */
34
+ declare function Lazy<F extends () => Promise<Newable<any> | {
35
+ default: Newable<any>;
36
+ }>>(importer: F, retry?: Lazy<any>["retry"]): Lazy<InferLazyType<F>>;
37
+ declare function Lazy<T>(importer: Importer<T>, retry?: Lazy<T>["retry"]): Lazy<T>;
38
+ //#endregion
39
+ export { LAZY_IDENTIFIER, Lazy };
@@ -0,0 +1,18 @@
1
+ //#region src/lib/lazy.ts
2
+ const LAZY_IDENTIFIER = Symbol("lazy");
3
+ /**
4
+ * Narrow an unknown value to a `Lazy` wrapper based on the hidden identifier symbol.
5
+ */
6
+ function isLazy(value) {
7
+ return typeof value === "object" && value !== null && LAZY_IDENTIFIER in value;
8
+ }
9
+ function Lazy(importer, retry) {
10
+ return {
11
+ [LAZY_IDENTIFIER]: true,
12
+ importer,
13
+ retry
14
+ };
15
+ }
16
+
17
+ //#endregion
18
+ export { LAZY_IDENTIFIER, Lazy, isLazy };
@@ -0,0 +1,112 @@
1
+ import { Newable, Token } from "./types.js";
2
+ import { Container } from "./container.js";
3
+ import { Lazy } from "./lazy.js";
4
+ import { ServiceScope } from "./scope.js";
5
+
6
+ //#region src/lib/providers.d.ts
7
+
8
+ /**
9
+ * Explicit lifecycle choices for providers. Mirrors decorator scopes but externalized.
10
+ */
11
+ type ProviderLifecycle = ServiceScope;
12
+ /**
13
+ * A single dependency item can be:
14
+ * - A constructor (class) registered or discoverable by the container.
15
+ * - A Lazy<T> wrapper for deferred resolution of another service.
16
+ * - A Token<T> representing a value provider.
17
+ */
18
+ type DependencyItem = Newable<unknown> | Lazy<unknown> | Token<unknown>;
19
+ /** Readonly list of dependency items describing a constructor's injection points. */
20
+ type DependencyList = readonly DependencyItem[];
21
+ /**
22
+ * Dependencies can be provided directly or via a thunk to support ordering / circular cases.
23
+ */
24
+ type DependenciesOption = DependencyList | (() => DependencyList);
25
+ /**
26
+ * Hidden symbol used to attach lazy provider metadata to its placeholder constructor.
27
+ */
28
+ declare const LAZY_PROVIDER_DESCRIPTOR: unique symbol;
29
+ /**
30
+ * Placeholder constructor type for a lazily imported class.
31
+ * It masquerades as `Newable<T>` for dependency declaration and typing, while hosting
32
+ * a private descriptor used during provider application.
33
+ */
34
+ type LazyPlaceholder<T = unknown> = Newable<T> & {
35
+ [LAZY_PROVIDER_DESCRIPTOR]: LazyServiceProviderDescriptor<T>;
36
+ };
37
+ /** Descriptor for a token-value binding. */
38
+ interface ValueProviderDescriptor<T = unknown> {
39
+ kind: "value";
40
+ token: Token<T>;
41
+ value: T;
42
+ }
43
+ /** Descriptor for a concrete class provider with lifecycle and optional dependencies. */
44
+ interface ServiceProviderDescriptor<T extends Newable<unknown> = Newable<unknown>> {
45
+ kind: "service";
46
+ useClass: T;
47
+ lifecycle: ProviderLifecycle;
48
+ deps?: DependenciesOption;
49
+ }
50
+ /**
51
+ * Internal descriptor capturing lazy service metadata.
52
+ * Not exposed directly to consumers; attached via symbol on the placeholder.
53
+ */
54
+ interface LazyServiceProviderDescriptor<T = unknown> {
55
+ placeholder: LazyPlaceholder<T>;
56
+ factory: Lazy<Newable<T>>;
57
+ lifecycle: ProviderLifecycle;
58
+ deps?: DependenciesOption;
59
+ }
60
+ /** Aggregate provider definitions for batch application. */
61
+ interface ProviderDefinitions {
62
+ values?: ValueProviderDescriptor[];
63
+ services?: ServiceProviderDescriptor[];
64
+ lazyServices?: LazyPlaceholder[];
65
+ }
66
+ /**
67
+ * Identity helper for provider definition blocks.
68
+ * Enables better type inference in user code without changing runtime behavior.
69
+ */
70
+ declare function defineProviders(defs: ProviderDefinitions): ProviderDefinitions;
71
+ /** Create a value provider for a given token. */
72
+ declare function asValue<T>(token: Token<T>, value: T): ValueProviderDescriptor<T>;
73
+ /**
74
+ * Create a class (service) provider with explicit lifecycle and optional dependencies.
75
+ */
76
+ declare function asClass<T extends Newable<unknown>>(useClass: T, options: {
77
+ lifecycle: ProviderLifecycle;
78
+ deps?: DependenciesOption;
79
+ }): ServiceProviderDescriptor<T>;
80
+ /**
81
+ * Convenience lifecycle helpers for fluent provider creation.
82
+ */
83
+ declare const lifecycle: {
84
+ readonly singleton: () => ProviderLifecycle;
85
+ readonly transient: () => ProviderLifecycle;
86
+ };
87
+ /**
88
+ * Define a lazily imported class provider.
89
+ *
90
+ * Creates a placeholder constructor that stands in for the eventual imported class.
91
+ * Dependencies can be declared immediately against this placeholder. When the container
92
+ * resolves the service, it will invoke the attached lazy factory to perform the dynamic import.
93
+ *
94
+ * @param importer Dynamic import function returning the service constructor.
95
+ * @param options.lifecycle Lifecycle scope (singleton or transient).
96
+ * @param options.deps Optional dependencies (array or thunk) for the service.
97
+ * @param options.label Optional display name overriding the placeholder's constructor name.
98
+ */
99
+ declare function asLazyClass<T, const TDeps extends DependenciesOption>(importer: () => Promise<Newable<T>>, options: {
100
+ lifecycle: ProviderLifecycle;
101
+ deps?: TDeps;
102
+ label?: string;
103
+ }): LazyPlaceholder<T>;
104
+ /**
105
+ * Apply one or more provider definition blocks to a container.
106
+ *
107
+ * For each service / lazy service, a registration entry is written into `dependenciesRegistry`.
108
+ * Value providers are passed directly to the container for immediate binding.
109
+ */
110
+ declare function applyProviders(container: Container, definitions: ProviderDefinitions | ProviderDefinitions[]): void;
111
+ //#endregion
112
+ export { ProviderDefinitions, applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle };
@@ -0,0 +1,166 @@
1
+ import { ServiceScope } from "./scope.js";
2
+ import { isConstructor } from "./types.js";
3
+ import { Lazy } from "./lazy.js";
4
+ import { dependenciesRegistry } from "./decorators.js";
5
+
6
+ //#region src/lib/providers.ts
7
+ /**
8
+ * Hidden symbol used to attach lazy provider metadata to its placeholder constructor.
9
+ */
10
+ const LAZY_PROVIDER_DESCRIPTOR = Symbol("alloy.lazy-provider-descriptor");
11
+ /**
12
+ * Identity helper for provider definition blocks.
13
+ * Enables better type inference in user code without changing runtime behavior.
14
+ */
15
+ function defineProviders(defs) {
16
+ return defs;
17
+ }
18
+ /** Create a value provider for a given token. */
19
+ function asValue(token, value) {
20
+ return {
21
+ kind: "value",
22
+ token,
23
+ value
24
+ };
25
+ }
26
+ /**
27
+ * Create a class (service) provider with explicit lifecycle and optional dependencies.
28
+ */
29
+ function asClass(useClass, options) {
30
+ return {
31
+ kind: "service",
32
+ useClass,
33
+ lifecycle: options.lifecycle,
34
+ deps: options.deps
35
+ };
36
+ }
37
+ /**
38
+ * Convenience lifecycle helpers for fluent provider creation.
39
+ */
40
+ const lifecycle = {
41
+ singleton() {
42
+ return ServiceScope.SINGLETON;
43
+ },
44
+ transient() {
45
+ return ServiceScope.TRANSIENT;
46
+ }
47
+ };
48
+ /**
49
+ * Define a lazily imported class provider.
50
+ *
51
+ * Creates a placeholder constructor that stands in for the eventual imported class.
52
+ * Dependencies can be declared immediately against this placeholder. When the container
53
+ * resolves the service, it will invoke the attached lazy factory to perform the dynamic import.
54
+ *
55
+ * @param importer Dynamic import function returning the service constructor.
56
+ * @param options.lifecycle Lifecycle scope (singleton or transient).
57
+ * @param options.deps Optional dependencies (array or thunk) for the service.
58
+ * @param options.label Optional display name overriding the placeholder's constructor name.
59
+ */
60
+ function asLazyClass(importer, options) {
61
+ class AlloyLazyProvider {
62
+ static __alloyLazy = true;
63
+ constructor() {
64
+ throw new Error("Lazy provider placeholders cannot be instantiated directly. Use container.get instead.");
65
+ }
66
+ }
67
+ const placeholder = AlloyLazyProvider;
68
+ if (options.label) Object.defineProperty(placeholder, "name", { value: options.label });
69
+ const factory = Lazy(async () => {
70
+ return await importer();
71
+ });
72
+ Object.defineProperty(placeholder, LAZY_PROVIDER_DESCRIPTOR, { value: {
73
+ placeholder,
74
+ factory,
75
+ lifecycle: options.lifecycle,
76
+ deps: options.deps
77
+ } });
78
+ return placeholder;
79
+ }
80
+ /**
81
+ * Normalizes dependencies option into a consistent thunk form.
82
+ */
83
+ function normalizeDependencies(option) {
84
+ if (!option) return () => [];
85
+ if (typeof option === "function") return option;
86
+ return () => option;
87
+ }
88
+ function detectProviderCycles(entries) {
89
+ if (entries.length === 0) return;
90
+ const providerMap = /* @__PURE__ */ new Map();
91
+ for (const entry of entries) providerMap.set(entry.ctor, entry.deps);
92
+ if (providerMap.size <= 1) return;
93
+ const adjacency = /* @__PURE__ */ new Map();
94
+ for (const [ctor, depsOption] of providerMap.entries()) {
95
+ if (typeof depsOption === "function") {
96
+ adjacency.set(ctor, []);
97
+ continue;
98
+ }
99
+ const deps = depsOption ?? [];
100
+ const neighbors = [];
101
+ for (const dep of deps) if (isConstructor(dep) && providerMap.has(dep)) neighbors.push(dep);
102
+ adjacency.set(ctor, neighbors);
103
+ }
104
+ const visiting = /* @__PURE__ */ new Set();
105
+ const visited = /* @__PURE__ */ new Set();
106
+ const path = [];
107
+ const dfs = (node) => {
108
+ if (visiting.has(node)) {
109
+ const cycleStart = path.indexOf(node);
110
+ const cyclePath = [...path.slice(cycleStart), node].map((ctor) => ctor.name || "<anonymous>").join(" -> ");
111
+ throw new Error(`[alloy] Circular provider dependency detected: ${cyclePath}`);
112
+ }
113
+ if (visited.has(node)) return;
114
+ visiting.add(node);
115
+ path.push(node);
116
+ for (const dep of adjacency.get(node) ?? []) dfs(dep);
117
+ path.pop();
118
+ visiting.delete(node);
119
+ visited.add(node);
120
+ };
121
+ for (const node of providerMap.keys()) dfs(node);
122
+ }
123
+ /**
124
+ * Apply one or more provider definition blocks to a container.
125
+ *
126
+ * For each service / lazy service, a registration entry is written into `dependenciesRegistry`.
127
+ * Value providers are passed directly to the container for immediate binding.
128
+ */
129
+ function applyProviders(container, definitions) {
130
+ const list = Array.isArray(definitions) ? definitions : [definitions];
131
+ const planEntries = [];
132
+ for (const definition of list) {
133
+ for (const service of definition.services ?? []) planEntries.push({
134
+ ctor: service.useClass,
135
+ deps: service.deps
136
+ });
137
+ for (const placeholder of definition.lazyServices ?? []) {
138
+ const descriptor = placeholder[LAZY_PROVIDER_DESCRIPTOR];
139
+ if (!descriptor) continue;
140
+ planEntries.push({
141
+ ctor: descriptor.placeholder,
142
+ deps: descriptor.deps
143
+ });
144
+ }
145
+ }
146
+ detectProviderCycles(planEntries);
147
+ for (const definition of list) {
148
+ for (const valueProvider of definition.values ?? []) container.provideValue(valueProvider.token, valueProvider.value);
149
+ const registerService = (ctor, lifecycle$1, deps, factory) => {
150
+ dependenciesRegistry.set(ctor, {
151
+ scope: lifecycle$1,
152
+ dependencies: normalizeDependencies(deps),
153
+ factory
154
+ });
155
+ };
156
+ for (const service of definition.services ?? []) registerService(service.useClass, service.lifecycle, service.deps);
157
+ for (const placeholder of definition.lazyServices ?? []) {
158
+ const descriptor = placeholder[LAZY_PROVIDER_DESCRIPTOR];
159
+ if (!descriptor) continue;
160
+ registerService(descriptor.placeholder, descriptor.lifecycle, descriptor.deps, descriptor.factory);
161
+ }
162
+ }
163
+ }
164
+
165
+ //#endregion
166
+ export { applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle };
@@ -0,0 +1,8 @@
1
+ //#region src/lib/scope.d.ts
2
+ declare const ServiceScope: {
3
+ readonly SINGLETON: "singleton";
4
+ readonly TRANSIENT: "transient";
5
+ };
6
+ type ServiceScope = (typeof ServiceScope)[keyof typeof ServiceScope];
7
+ //#endregion
8
+ export { ServiceScope };
@@ -0,0 +1,8 @@
1
+ //#region src/lib/scope.ts
2
+ const ServiceScope = {
3
+ SINGLETON: "singleton",
4
+ TRANSIENT: "transient"
5
+ };
6
+
7
+ //#endregion
8
+ export { ServiceScope };
@@ -0,0 +1,34 @@
1
+ import { Constructor } from "./types.js";
2
+
3
+ //#region src/lib/service-identifiers.d.ts
4
+
5
+ /**
6
+ * Symbol-based identifier used to resolve services in a minifier-safe way.
7
+ * These identifiers never collide with minified constructor names and can be
8
+ * shared between runtime and generated modules.
9
+ */
10
+ type ServiceIdentifier<T = unknown> = symbol & {
11
+ readonly __serviceIdentifierBrand: unique symbol;
12
+ readonly __type?: T;
13
+ };
14
+ /**
15
+ * Associates a constructor with a stable identifier. When an explicit identifier
16
+ * is provided (e.g., by generated metadata), it becomes canonical for the
17
+ * constructor. Attempting to reuse an identifier with a different constructor
18
+ * throws to surface manifest/config mismatches early.
19
+ */
20
+ declare function registerServiceIdentifier<T>(ctor: Constructor, explicitIdentifier?: ServiceIdentifier<T>): ServiceIdentifier<T>;
21
+ /**
22
+ * Retrieves the identifier for a constructor, creating one lazily when absent.
23
+ */
24
+ declare function getServiceIdentifier<T>(ctor: Constructor): ServiceIdentifier<T>;
25
+ /**
26
+ * Reverse lookup used by the container to go from identifier -> constructor.
27
+ */
28
+ declare function getConstructorByIdentifier(identifier: ServiceIdentifier): Constructor | undefined;
29
+ /**
30
+ * Removes all identifier associations. Useful for tests and tooling hooks that need a clean slate.
31
+ */
32
+ declare function clearServiceIdentifierRegistry(): void;
33
+ //#endregion
34
+ export { ServiceIdentifier, clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier };
@@ -0,0 +1,54 @@
1
+ //#region src/lib/service-identifiers.ts
2
+ let ctorToIdentifier = /* @__PURE__ */ new WeakMap();
3
+ const identifierToCtor = /* @__PURE__ */ new Map();
4
+ function formatIdentifierDescription(ctor) {
5
+ const name = ctor?.name?.trim();
6
+ return name ? `Service(${name})` : "Service(<anonymous>)";
7
+ }
8
+ function createServiceIdentifier(ctor) {
9
+ return Symbol(formatIdentifierDescription(ctor));
10
+ }
11
+ /**
12
+ * Associates a constructor with a stable identifier. When an explicit identifier
13
+ * is provided (e.g., by generated metadata), it becomes canonical for the
14
+ * constructor. Attempting to reuse an identifier with a different constructor
15
+ * throws to surface manifest/config mismatches early.
16
+ */
17
+ function registerServiceIdentifier(ctor, explicitIdentifier) {
18
+ const current = ctorToIdentifier.get(ctor);
19
+ if (current) {
20
+ if (explicitIdentifier && explicitIdentifier !== current) {
21
+ const owner = identifierToCtor.get(explicitIdentifier);
22
+ if (owner && owner !== ctor) throw new Error("Attempted to reassign an existing ServiceIdentifier to a different constructor.");
23
+ }
24
+ return current;
25
+ }
26
+ const identifier = explicitIdentifier ?? createServiceIdentifier(ctor);
27
+ const existingOwner = identifierToCtor.get(identifier);
28
+ if (existingOwner && existingOwner !== ctor) throw new Error("ServiceIdentifier is already associated with a different constructor.");
29
+ ctorToIdentifier.set(ctor, identifier);
30
+ identifierToCtor.set(identifier, ctor);
31
+ return identifier;
32
+ }
33
+ /**
34
+ * Retrieves the identifier for a constructor, creating one lazily when absent.
35
+ */
36
+ function getServiceIdentifier(ctor) {
37
+ return registerServiceIdentifier(ctor);
38
+ }
39
+ /**
40
+ * Reverse lookup used by the container to go from identifier -> constructor.
41
+ */
42
+ function getConstructorByIdentifier(identifier) {
43
+ return identifierToCtor.get(identifier);
44
+ }
45
+ /**
46
+ * Removes all identifier associations. Useful for tests and tooling hooks that need a clean slate.
47
+ */
48
+ function clearServiceIdentifierRegistry() {
49
+ ctorToIdentifier = /* @__PURE__ */ new WeakMap();
50
+ identifierToCtor.clear();
51
+ }
52
+
53
+ //#endregion
54
+ export { clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier };
@@ -0,0 +1,14 @@
1
+ import { Newable } from "../types.js";
2
+ import { vi } from "vitest";
3
+
4
+ //#region src/lib/testing/mocking.d.ts
5
+ type MethodKeys<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => any) ? K : never }[keyof T];
6
+ /** Typed mock shape returned for class auto-mocking. */
7
+ type MockOf<T> = Partial<T> & {
8
+ /** Map of method name -> vi spy function */
9
+ spies: Record<Extract<MethodKeys<T>, string>, ReturnType<typeof vi.fn>>;
10
+ /** Original constructor reference for introspection */
11
+ __target: Newable<T>;
12
+ };
13
+ //#endregion
14
+ export { MockOf };