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
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # alloy-di
2
+
3
+ `alloy-di` is a compile-time dependency injection toolkit for Vite. It scans your TypeScript during build, generates a static container, and ships a tiny runtime so you get dependency injection without reflection overhead.
4
+
5
+ ## Highlights
6
+
7
+ - **Build-time graph** – services, scopes, and dependencies are resolved while bundling, so runtime work stays minimal.
8
+ - **First-class lazy loading** – use `Lazy()` or provider-based lazy registrations to keep optional features in separate chunks.
9
+ - **Framework agnostic** – works anywhere Vite runs: React, Vue, Svelte, SSR, libraries, and plain TS apps.
10
+ - **Type safe** – generates `serviceIdentifiers` and manifest declarations for precise inference.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add -D alloy-di
16
+ ```
17
+
18
+ ## 30‑second setup
19
+
20
+ 1. **Add the Vite plugin**
21
+
22
+ ```ts
23
+ import { defineConfig } from "vite";
24
+ import alloy from "alloy-di/vite";
25
+
26
+ export default defineConfig({
27
+ plugins: [alloy()],
28
+ });
29
+ ```
30
+
31
+ 2. **Annotate services**
32
+
33
+ ```ts
34
+ import { Injectable, Singleton, deps } from "alloy-di/runtime";
35
+
36
+ @Singleton()
37
+ export class ServiceA {}
38
+
39
+ @Injectable(deps(ServiceA))
40
+ export class AppService {
41
+ constructor(private readonly serviceA: ServiceA) {}
42
+ }
43
+ ```
44
+
45
+ 3. **Resolve from the virtual container**
46
+
47
+ ```ts
48
+ import container, { serviceIdentifiers } from "virtual:alloy-container";
49
+
50
+ const app = await container.get(serviceIdentifiers.AppService);
51
+ ```
52
+
53
+ Need manifests, providers, or testing utilities? See the docs site for complete guides.
54
+
55
+ ## Documentation
56
+
57
+ - **Website**: https://alloy-di.dev (generated from `/docs`)
58
+ - **Develop locally**: `pnpm docs:dev`
59
+ - **Build static site**: `pnpm docs:build`
60
+
61
+ The site covers getting started, plugin options, manifest authoring, lazy loading, testing helpers, and architecture deep dives.
62
+
63
+ ## Examples in this repo
64
+
65
+ - `packages/examples/app` – React + Vite app consuming decorated services, manifests, and providers.
66
+ - `packages/examples/library-internal` – monorepo library that emits `alloy.manifest.mjs` via the Rolldown plugin.
67
+ - `packages/examples/library-external` – plain classes registered through providers.
68
+
69
+ Clone the repo, run `pnpm install`, then `pnpm --filter @alloy-di/example-app dev` to explore.
@@ -0,0 +1,117 @@
1
+ import { Constructor, Newable, Token } from "./types.js";
2
+ import { ServiceIdentifier } from "./service-identifiers.js";
3
+
4
+ //#region src/lib/container.d.ts
5
+
6
+ /**
7
+ * Runtime dependency injection container used by generated modules and tests.
8
+ *
9
+ * It stores metadata discovered at build time, resolves constructor dependencies,
10
+ * performs singleton caching, and supports token-based value providers.
11
+ */
12
+ declare class Container {
13
+ private singletons;
14
+ private pendingSingletons;
15
+ private instanceOverrides;
16
+ private metadataCache;
17
+ private valueProviders;
18
+ private factoryWarningCache;
19
+ /**
20
+ * Resolve (and construct) the requested service.
21
+ *
22
+ * @param target - Class constructor that was decorated with `@Injectable`/`@Singleton`.
23
+ * @returns A promise that resolves to the instantiated service.
24
+ */
25
+ get<T>(target: Newable<T>): Promise<T>;
26
+ get<T>(identifier: ServiceIdentifier<T>): Promise<T>;
27
+ /**
28
+ * Provide a concrete instance override for a class constructor.
29
+ * Used by test utilities to inject mocks/stubs without altering global metadata.
30
+ */
31
+ overrideInstance<T>(target: Newable<T>, instance: T): void;
32
+ /**
33
+ * Retrieve the stable identifier associated with a constructor.
34
+ * Consumers can cache this and later call {@link getByIdentifier}.
35
+ */
36
+ getIdentifier<T>(target: Constructor): ServiceIdentifier<T>;
37
+ /**
38
+ * Resolve a service using its stable identifier.
39
+ * Identifiers remain safe across minification and code splitting.
40
+ */
41
+ getByIdentifier<T = unknown>(identifier: ServiceIdentifier<T>): Promise<T>;
42
+ /**
43
+ * Register a concrete value for an injection token at runtime.
44
+ *
45
+ * @param token - The token created via `createToken`.
46
+ * @param value - The value that should be injected when the token is requested.
47
+ */
48
+ provideValue<T>(token: Token<T>, value: T): void;
49
+ /**
50
+ * Retrieve a provided value for a token from this container.
51
+ * Throws if no provider is registered for the token.
52
+ */
53
+ getToken<T>(token: Token<T>): T;
54
+ private getByConstructor;
55
+ private maybeWarnFactoryLazyConstructorUsage;
56
+ /**
57
+ * Resolve a constructor, managing singleton lifetimes and detecting circular dependencies.
58
+ * This is the core resolution logic that orchestrates caching, coalescing, and instantiation.
59
+ *
60
+ * @param target - Service constructor to resolve
61
+ * @param resolutionStack - Chain of services currently being resolved (for cycle detection)
62
+ * @returns Promise resolving to the service instance
63
+ * @throws Error if a circular dependency is detected
64
+ */
65
+ private resolve;
66
+ /**
67
+ * Resolve a singleton service with caching and in-flight creation coalescing.
68
+ */
69
+ private resolveSingleton;
70
+ /**
71
+ * Instantiate a class by resolving and injecting all declared dependencies.
72
+ * Handles factory-lazy services by importing the real class before instantiation.
73
+ *
74
+ * @param target - Service constructor (may be a stub class if factory provided)
75
+ * @param dependencies - Array of dependency items to resolve and inject
76
+ * @param resolutionStack - Current resolution chain
77
+ * @param factory - Optional lazy factory to import the real class
78
+ * @returns Promise resolving to the instantiated service
79
+ */
80
+ private createInstance;
81
+ /**
82
+ * Resolve a single dependency entry, handling lazies, tokens, and constructors.
83
+ * This is called for each parameter in a service's dependency array.
84
+ *
85
+ * @param param - Dependency item (can be Lazy, Token, or Constructor)
86
+ * @param target - Service being constructed (for error messages)
87
+ * @param resolutionStack - Current resolution chain
88
+ * @returns Promise resolving to the dependency instance
89
+ * @throws Error if dependency type is invalid
90
+ */
91
+ private resolveParam;
92
+ /**
93
+ * Execute a lazy importer with optional retry/backoff semantics.
94
+ * Implements exponential backoff for transient network failures.
95
+ *
96
+ * @param lazyDep - Lazy dependency wrapper with importer function and retry config
97
+ * @param target - Service being resolved (for error messages)
98
+ * @param resolutionStack - Current resolution chain (for cycle detection and error context)
99
+ * @returns The imported class constructor
100
+ * @throws Error if all retry attempts exhausted or import returns non-constructor
101
+ */
102
+ private importWithRetry;
103
+ /**
104
+ * Resolve a token dependency via registered value providers.
105
+ */
106
+ private resolveTokenLike;
107
+ /**
108
+ * Format a readable representation of the resolution stack for error messages.
109
+ */
110
+ private formatStackPath;
111
+ /**
112
+ * Retrieve (and memoize) the DI metadata for a service from the registry.
113
+ */
114
+ private getServiceMetadata;
115
+ }
116
+ //#endregion
117
+ export { Container };
@@ -0,0 +1,266 @@
1
+ import { ServiceScope } from "./scope.js";
2
+ import { isConstructor, isToken } from "./types.js";
3
+ import { isLazy } from "./lazy.js";
4
+ import { dependenciesRegistry } from "./decorators.js";
5
+ import { DependencyResolutionError } from "./dependency-error.js";
6
+ import { getConstructorByIdentifier, getServiceIdentifier } from "./service-identifiers.js";
7
+ import { isDevEnvironment } from "./env-detection.js";
8
+
9
+ //#region src/lib/container.ts
10
+ function classifyDependency(value) {
11
+ if (isLazy(value)) return {
12
+ kind: "lazy",
13
+ lazy: value
14
+ };
15
+ if (isToken(value)) return {
16
+ kind: "token",
17
+ token: value
18
+ };
19
+ if (isConstructor(value)) return {
20
+ kind: "constructor",
21
+ ctor: value
22
+ };
23
+ return null;
24
+ }
25
+ function hasFactory(metadata) {
26
+ return Boolean(metadata?.factory);
27
+ }
28
+ function isProviderPlaceholder(target) {
29
+ return Boolean(typeof target === "function" && "__alloyLazy" in target && target.__alloyLazy === true);
30
+ }
31
+ function formatFactoryLazyWarning(target) {
32
+ return `[alloy] container.get(${target.name || "<anonymous>"}) resolved a factory-lazy service via constructor. Use container.get(${target.name ? `serviceIdentifiers.${target.name}` : "serviceIdentifiers.<Service>"}) or cache const id = ${target.name ? `container.getIdentifier(${target.name})` : `container.getIdentifier(<Service>)`}; container.get(id) to preserve lazy loading.`;
33
+ }
34
+ /**
35
+ * Runtime dependency injection container used by generated modules and tests.
36
+ *
37
+ * It stores metadata discovered at build time, resolves constructor dependencies,
38
+ * performs singleton caching, and supports token-based value providers.
39
+ */
40
+ var Container = class {
41
+ singletons = /* @__PURE__ */ new Map();
42
+ pendingSingletons = /* @__PURE__ */ new Map();
43
+ instanceOverrides = /* @__PURE__ */ new Map();
44
+ metadataCache = /* @__PURE__ */ new Map();
45
+ valueProviders = /* @__PURE__ */ new Map();
46
+ factoryWarningCache = /* @__PURE__ */ new WeakSet();
47
+ async get(targetOrIdentifier) {
48
+ if (typeof targetOrIdentifier === "symbol") return this.getByIdentifier(targetOrIdentifier);
49
+ return this.getByConstructor(targetOrIdentifier);
50
+ }
51
+ /**
52
+ * Provide a concrete instance override for a class constructor.
53
+ * Used by test utilities to inject mocks/stubs without altering global metadata.
54
+ */
55
+ overrideInstance(target, instance) {
56
+ this.instanceOverrides.set(target, instance);
57
+ this.singletons.set(target, instance);
58
+ }
59
+ /**
60
+ * Retrieve the stable identifier associated with a constructor.
61
+ * Consumers can cache this and later call {@link getByIdentifier}.
62
+ */
63
+ getIdentifier(target) {
64
+ return getServiceIdentifier(target);
65
+ }
66
+ /**
67
+ * Resolve a service using its stable identifier.
68
+ * Identifiers remain safe across minification and code splitting.
69
+ */
70
+ async getByIdentifier(identifier) {
71
+ const ctor = getConstructorByIdentifier(identifier);
72
+ if (!ctor) throw new Error(`No service registered for identifier ${identifier.description ?? identifier.toString()}`);
73
+ return this.getByConstructor(ctor, { skipFactoryWarning: true });
74
+ }
75
+ /**
76
+ * Register a concrete value for an injection token at runtime.
77
+ *
78
+ * @param token - The token created via `createToken`.
79
+ * @param value - The value that should be injected when the token is requested.
80
+ */
81
+ provideValue(token, value) {
82
+ this.valueProviders.set(token.id, value);
83
+ }
84
+ /**
85
+ * Retrieve a provided value for a token from this container.
86
+ * Throws if no provider is registered for the token.
87
+ */
88
+ getToken(token) {
89
+ if (!this.valueProviders.has(token.id)) throw new Error(`No provider registered for token ${token.description ?? String(token.id)}`);
90
+ return this.valueProviders.get(token.id);
91
+ }
92
+ async getByConstructor(target, options) {
93
+ if (!options?.skipFactoryWarning) this.maybeWarnFactoryLazyConstructorUsage(target);
94
+ return this.resolve(target, []);
95
+ }
96
+ maybeWarnFactoryLazyConstructorUsage(target) {
97
+ if (!isDevEnvironment()) return;
98
+ if (!hasFactory(this.metadataCache.get(target) ?? this.getServiceMetadata(target)) || this.factoryWarningCache.has(target) || isProviderPlaceholder(target)) return;
99
+ this.factoryWarningCache.add(target);
100
+ if (typeof console !== "undefined" && typeof console.warn === "function") console.warn(formatFactoryLazyWarning(target));
101
+ }
102
+ /**
103
+ * Resolve a constructor, managing singleton lifetimes and detecting circular dependencies.
104
+ * This is the core resolution logic that orchestrates caching, coalescing, and instantiation.
105
+ *
106
+ * @param target - Service constructor to resolve
107
+ * @param resolutionStack - Chain of services currently being resolved (for cycle detection)
108
+ * @returns Promise resolving to the service instance
109
+ * @throws Error if a circular dependency is detected
110
+ */
111
+ async resolve(target, resolutionStack) {
112
+ const overridden = this.instanceOverrides.get(target);
113
+ if (overridden) return overridden;
114
+ if (resolutionStack.includes(target)) throw new DependencyResolutionError(`Circular dependency detected: ${[...resolutionStack.map((t) => t.name), target.name].join(" -> ")}`, {
115
+ target,
116
+ resolutionStack,
117
+ failedDependency: target
118
+ });
119
+ const metadata = this.getServiceMetadata(target);
120
+ const nextStack = [...resolutionStack, target];
121
+ if (metadata.scope === ServiceScope.SINGLETON) return this.resolveSingleton(target, metadata, nextStack);
122
+ return this.createInstance(target, metadata.dependencies, nextStack, metadata.factory);
123
+ }
124
+ /**
125
+ * Resolve a singleton service with caching and in-flight creation coalescing.
126
+ */
127
+ async resolveSingleton(target, metadata, resolutionStack) {
128
+ const cached = this.singletons.get(target);
129
+ if (cached) return cached;
130
+ const pending = this.pendingSingletons.get(target);
131
+ if (pending) return await pending;
132
+ const creation = this.createInstance(target, metadata.dependencies, resolutionStack, metadata.factory).then((instance) => {
133
+ this.singletons.set(target, instance);
134
+ return instance;
135
+ });
136
+ this.pendingSingletons.set(target, creation);
137
+ try {
138
+ return await creation;
139
+ } finally {
140
+ this.pendingSingletons.delete(target);
141
+ }
142
+ }
143
+ /**
144
+ * Instantiate a class by resolving and injecting all declared dependencies.
145
+ * Handles factory-lazy services by importing the real class before instantiation.
146
+ *
147
+ * @param target - Service constructor (may be a stub class if factory provided)
148
+ * @param dependencies - Array of dependency items to resolve and inject
149
+ * @param resolutionStack - Current resolution chain
150
+ * @param factory - Optional lazy factory to import the real class
151
+ * @returns Promise resolving to the instantiated service
152
+ */
153
+ async createInstance(target, dependencies, resolutionStack, factory) {
154
+ const ctor = factory ? await this.importWithRetry(factory, target, resolutionStack) : target;
155
+ return new ctor(...await Promise.all(dependencies.map((param) => this.resolveParam(param, ctor, resolutionStack))));
156
+ }
157
+ /**
158
+ * Resolve a single dependency entry, handling lazies, tokens, and constructors.
159
+ * This is called for each parameter in a service's dependency array.
160
+ *
161
+ * @param param - Dependency item (can be Lazy, Token, or Constructor)
162
+ * @param target - Service being constructed (for error messages)
163
+ * @param resolutionStack - Current resolution chain
164
+ * @returns Promise resolving to the dependency instance
165
+ * @throws Error if dependency type is invalid
166
+ */
167
+ async resolveParam(param, target, resolutionStack) {
168
+ const classification = classifyDependency(param);
169
+ if (!classification) {
170
+ const stackPath = this.formatStackPath(target, resolutionStack);
171
+ throw new DependencyResolutionError(`Invalid dependency type while resolving ${target.name}. Resolution stack: ${stackPath}. Received type: ${typeof param}`, {
172
+ target,
173
+ resolutionStack,
174
+ failedDependency: param
175
+ });
176
+ }
177
+ switch (classification.kind) {
178
+ case "lazy": {
179
+ const depClass = await this.importWithRetry(classification.lazy, target, resolutionStack);
180
+ return this.resolve(depClass, resolutionStack);
181
+ }
182
+ case "token": return this.resolveTokenLike(classification.token, target, resolutionStack);
183
+ case "constructor": return this.resolve(classification.ctor, resolutionStack);
184
+ }
185
+ return classification;
186
+ }
187
+ /**
188
+ * Execute a lazy importer with optional retry/backoff semantics.
189
+ * Implements exponential backoff for transient network failures.
190
+ *
191
+ * @param lazyDep - Lazy dependency wrapper with importer function and retry config
192
+ * @param target - Service being resolved (for error messages)
193
+ * @param resolutionStack - Current resolution chain (for cycle detection and error context)
194
+ * @returns The imported class constructor
195
+ * @throws Error if all retry attempts exhausted or import returns non-constructor
196
+ */
197
+ async importWithRetry(lazyDep, target, resolutionStack) {
198
+ const runImport = async () => await lazyDep.importer();
199
+ const retries = lazyDep.retry?.retries ?? 0;
200
+ const baseDelay = lazyDep.retry?.backoffMs ?? 0;
201
+ const factor = lazyDep.retry?.factor ?? 2;
202
+ let attempt = 0;
203
+ while (true) try {
204
+ const module = await runImport();
205
+ const depClass = typeof module === "object" && module !== null && "default" in module ? module.default : module;
206
+ if (!isConstructor(depClass)) {
207
+ const stackPath = this.formatStackPath(target, resolutionStack);
208
+ throw new DependencyResolutionError(`Lazy importer did not return a class for dependency while resolving ${target.name}. Resolution stack: ${stackPath}. Received type: ${typeof depClass}`, {
209
+ target,
210
+ resolutionStack,
211
+ failedDependency: depClass
212
+ });
213
+ }
214
+ return depClass;
215
+ } catch (err) {
216
+ if (attempt >= retries) {
217
+ const stackPath = this.formatStackPath(target, resolutionStack);
218
+ throw new DependencyResolutionError(`Failed to import lazy dependency while resolving ${target.name}. Resolution stack: ${stackPath}. Original error: ${err instanceof Error ? err.message : String(err)}`, {
219
+ target,
220
+ resolutionStack,
221
+ failedDependency: lazyDep,
222
+ cause: err
223
+ });
224
+ }
225
+ const delay = baseDelay * Math.pow(factor, attempt);
226
+ if (delay > 0) await new Promise((r) => setTimeout(r, delay));
227
+ attempt++;
228
+ }
229
+ }
230
+ /**
231
+ * Resolve a token dependency via registered value providers.
232
+ */
233
+ resolveTokenLike(tok, target, resolutionStack) {
234
+ if (this.valueProviders.has(tok.id)) return this.valueProviders.get(tok.id);
235
+ const stackPath = this.formatStackPath(target, resolutionStack);
236
+ throw new DependencyResolutionError(`No provider registered for token ${tok.description ?? String(tok.id)} while resolving ${target.name}. Resolution stack: ${stackPath}`, {
237
+ target,
238
+ resolutionStack,
239
+ failedDependency: tok
240
+ });
241
+ }
242
+ /**
243
+ * Format a readable representation of the resolution stack for error messages.
244
+ */
245
+ formatStackPath(target, resolutionStack) {
246
+ return [...resolutionStack.map((t) => t.name), target.name].join(" -> ");
247
+ }
248
+ /**
249
+ * Retrieve (and memoize) the DI metadata for a service from the registry.
250
+ */
251
+ getServiceMetadata(target) {
252
+ const cached = this.metadataCache.get(target);
253
+ if (cached) return cached;
254
+ const registryEntry = dependenciesRegistry.get(target);
255
+ const metadata = {
256
+ scope: registryEntry?.scope ?? ServiceScope.TRANSIENT,
257
+ dependencies: (registryEntry?.dependencies ?? (() => []))(),
258
+ factory: registryEntry?.factory
259
+ };
260
+ this.metadataCache.set(target, metadata);
261
+ return metadata;
262
+ }
263
+ };
264
+
265
+ //#endregion
266
+ export { Container };
@@ -0,0 +1,177 @@
1
+ import { Newable, Token } from "./types.js";
2
+ import { Lazy } from "./lazy.js";
3
+ import { ServiceScope } from "./scope.js";
4
+
5
+ //#region src/lib/decorators.d.ts
6
+
7
+ /**
8
+ * Resolve a declared dependency to the constructor parameter type expected by the service.
9
+ *
10
+ * - If the dependency is a `Lazy<T>`, this resolves to `T` (the eagerly-constructed type).
11
+ * - If the dependency is a `Newable<T>` (a class constructor), this resolves to `T`.
12
+ * - If the dependency is a `Token<T>`, this resolves to the provided value type `T`.
13
+ * - Otherwise, resolves to `never`.
14
+ *
15
+ * This is used to map the `dependencies` tuple into the service constructor parameter list.
16
+ *
17
+ * @typeParam D - A single declared dependency item.
18
+ */
19
+ type ResolveDep<D> = D extends Lazy<infer L> ? L : D extends Newable<infer I> ? I : D extends Token<infer V> ? V : never;
20
+ /**
21
+ * Tuple-map a declared dependencies list to the constructor parameter types.
22
+ *
23
+ * Given a tuple of constructors and/or `Lazy<...>` wrappers, produces a tuple of the
24
+ * instance types the class constructor should accept.
25
+ *
26
+ * Example:
27
+ * - `[Logger, Metrics]` -> `[Logger, Metrics]` (instances)
28
+ * - `[Lazy(() => Logger)]` -> `[Logger]`
29
+ *
30
+ * @typeParam TDeps - A readonly tuple of declared dependencies.
31
+ */
32
+ type DepInstances<TDeps extends readonly unknown[]> = { [K in keyof TDeps]: ResolveDep<TDeps[K]> };
33
+ /**
34
+ * Allowed shapes for the `dependencies` option.
35
+ *
36
+ * - A readonly tuple/array of constructors and/or `Lazy<...>` wrappers.
37
+ * - Or a function returning such a tuple (recommended to break circular refs in the same file).
38
+ */
39
+ type DependencyItem = Newable<unknown> | Lazy<unknown> | Token<unknown>;
40
+ /**
41
+ * Strongly-typed decorator function signature used by overloads when dependencies are known.
42
+ *
43
+ * @typeParam TDeps - A readonly tuple of declared dependencies.
44
+ * @internal
45
+ */
46
+ type TypedClassDecorator<TDeps extends readonly DependencyItem[]> = (target: new (...args: DepInstances<TDeps>) => unknown) => void;
47
+ /**
48
+ * Global registry for service metadata used by the runtime container.
49
+ *
50
+ * Keys are service constructors; values include the configured scope and a
51
+ * normalized dependency function that returns the declared dependencies as a readonly tuple.
52
+ *
53
+ * The Vite plugin populates this from source decorators at build time, but you can also
54
+ * register programmatically via the decorators in tests or non-plugin setups.
55
+ *
56
+ * @internal
57
+ */
58
+ declare const dependenciesRegistry: Map<Newable<unknown>, {
59
+ dependencies?: () => readonly (Newable<unknown> | Lazy<unknown> | Token<unknown>)[];
60
+ scope?: ServiceScope;
61
+ factory?: Lazy<Newable<unknown>>;
62
+ }>;
63
+ /**
64
+ * Class decorator for declaring a DI-managed service and its dependencies.
65
+ *
66
+ * Overloads preserve tuple inference for both array and function dependency forms, enabling
67
+ * strict alignment between the declared dependencies and the class constructor parameter list.
68
+ *
69
+ * Notes:
70
+ * - Order matters: dependencies map positionally to constructor parameters.
71
+ * - For circular dependencies in the same file, use the function form: `@Injectable(() => [B])`.
72
+ * - When using `Lazy(...)`, the constructor expects the resolved type, not `Lazy<T>`.
73
+ * - Due to TypeScript limitations, decorator position may not always surface target mismatches;
74
+ * use {@link assertDeps} for zero-cost compile-time assertions when needed.
75
+ *
76
+ * @typeParam TDeps - A readonly tuple of declared dependencies.
77
+ * @param depsOrScope - Optional dependency declaration followed by an optional scope string.
78
+ *
79
+ * @example Transient with a direct dependency
80
+ * ```ts
81
+ * @Injectable(deps(Logger))
82
+ * class AppService {
83
+ * constructor(private logger: Logger) {}
84
+ * }
85
+ * ```
86
+ *
87
+ * @example Singleton shorthand via scope
88
+ * ```ts
89
+ * @Injectable('singleton')
90
+ * class AppState {}
91
+ * ```
92
+ *
93
+ * @example Circular dependency in the same file
94
+ * ```ts
95
+ * @Injectable(() => [CircularB])
96
+ * class CircularA { constructor(private b: CircularB) {} }
97
+ * ```
98
+ *
99
+ * @example Lazy dependency resolves to the underlying type
100
+ * ```ts
101
+ * @Injectable([Lazy(() => Promise.resolve(Logger))])
102
+ * class NeedsLogger { constructor(private logger: Logger) {} }
103
+ * ```
104
+ */
105
+ declare function Injectable(): ClassDecorator;
106
+ declare function Injectable(scope: ServiceScope): ClassDecorator;
107
+ declare function Injectable<const TDeps extends readonly DependencyItem[]>(dependencies: () => TDeps, scope?: ServiceScope): TypedClassDecorator<TDeps>;
108
+ declare function Injectable<const TDeps extends readonly DependencyItem[]>(dependencies: TDeps, scope?: ServiceScope): TypedClassDecorator<TDeps>;
109
+ /**
110
+ * Shorthand decorator for singleton services.
111
+ *
112
+ * Equivalent to `@Injectable(dependencies?, 'singleton')` with the same strict typing
113
+ * and overload behaviors as {@link Injectable}.
114
+ *
115
+ * @typeParam TDeps - A readonly tuple of declared dependencies.
116
+ * @param options - Singleton configuration and dependencies (array or function form).
117
+ *
118
+ * @example No dependencies
119
+ * ```ts
120
+ * @Singleton()
121
+ * class GlobalLogger {}
122
+ * ```
123
+ *
124
+ * @example With dependencies (array form)
125
+ * ```ts
126
+ * @Singleton(deps(Config))
127
+ * class Metrics { constructor(private cfg: Config) {} }
128
+ * ```
129
+ */
130
+ declare function Singleton(): ClassDecorator;
131
+ declare function Singleton<const TDeps extends readonly DependencyItem[]>(dependencies: () => TDeps): TypedClassDecorator<TDeps>;
132
+ declare function Singleton<const TDeps extends readonly DependencyItem[]>(dependencies: TDeps): TypedClassDecorator<TDeps>;
133
+ /**
134
+ * Declare dependencies as a strongly-typed readonly tuple without `as const`.
135
+ *
136
+ * This helper preserves tuple inference for strict constructor checking while keeping callsites
137
+ * concise. It returns a function so it can be used directly as `dependencies` in the decorator.
138
+ *
139
+ * @typeParam T - A readonly tuple of constructors and/or `Lazy<...>` wrappers.
140
+ * @param items - Variadic dependency list.
141
+ * @returns A function returning the same tuple (suitable for `dependencies`).
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * @Injectable(deps(Logger, Metrics))
146
+ * class AppService { constructor(l: Logger, m: Metrics) {} }
147
+ * ```
148
+ */
149
+ declare function deps<T extends readonly (Newable<unknown> | Lazy<unknown> | Token<unknown>)[]>(...items: T): () => T;
150
+ /**
151
+ * Compile-time assertion: ensure constructor parameters match the resolved dependency tuple.
152
+ *
153
+ * This function has zero runtime cost and simply returns the class unchanged. It exists solely
154
+ * to force a TypeScript evaluation that surfaces mismatches (number, order, or type), including
155
+ * cases where decorator position alone may not report errors due to compiler limitations.
156
+ *
157
+ * @typeParam TDeps - Declared dependency tuple.
158
+ * @typeParam TClass - Class type with a constructor that must accept `DepInstances<TDeps>`.
159
+ * @param depsFn - A function returning the declared dependency tuple (e.g., from {@link deps}).
160
+ * @param klass - The class constructor to validate.
161
+ * @returns The same class constructor, unchanged.
162
+ *
163
+ * @example Negative test in code
164
+ * ```ts
165
+ * class Dep {}
166
+ * class Wrong {}
167
+ *
168
+ * @Injectable(deps(Dep))
169
+ * class UsesWrong { constructor(_: Wrong) {} }
170
+ *
171
+ * // @ts-expect-error mismatch detected at compile time
172
+ * assertDeps(deps(Dep), UsesWrong);
173
+ * ```
174
+ */
175
+ declare function assertDeps<TDeps extends readonly (Newable<unknown> | Lazy<unknown> | Token<unknown>)[], TClass extends new (...args: DepInstances<TDeps>) => unknown>(depsFn: () => TDeps, klass: TClass): TClass;
176
+ //#endregion
177
+ export { Injectable, Singleton, assertDeps, dependenciesRegistry, deps };