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,154 @@
1
+ import { createClassKey, normalizeImportPath, walkSync } from "../core/utils.js";
2
+ import { IdentifierResolver } from "../core/identifier-resolver.js";
3
+ import { generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition } from "../core/codegen.js";
4
+ import { createDiscoveryStore } from "../core/discovery-store.js";
5
+ import { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest } from "./manifest-utils.js";
6
+ import path from "node:path";
7
+ import fs from "node:fs";
8
+
9
+ //#region src/plugins/vite-plugin/index.ts
10
+ function toLazyServiceKey(identifier) {
11
+ const description = identifier.description;
12
+ if (!description || !description.startsWith("alloy:")) throw new Error("[alloy] lazyServices entries must be serviceIdentifiers exported by Alloy manifests.");
13
+ return description;
14
+ }
15
+ /**
16
+ * Creates the Alloy Vite plugin that statically discovers injectable classes
17
+ * and exposes them through a virtual container module at build time.
18
+ */
19
+ function alloy(options = {}) {
20
+ const virtualModuleId = "virtual:alloy-container";
21
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
22
+ const configuredProviderEntries = Array.from(options.providers ?? []);
23
+ const providerModuleRefs = [];
24
+ let resolvedRoot = process.cwd();
25
+ let packageName = "UNKNOWN_PACKAGE";
26
+ const lazyServiceKeys = new Set((options.lazyServices ?? []).map(toLazyServiceKey));
27
+ const discovery = createDiscoveryStore();
28
+ const discoveredClasses = /* @__PURE__ */ new Map();
29
+ const lazyReferencedClassKeys = /* @__PURE__ */ new Set();
30
+ const processUpdate = (id, code) => {
31
+ const { metas, lazyClassKeys, previousMetas, previousLazyClassKeys } = discovery.updateFile(id, code);
32
+ if (previousMetas) for (const meta of previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
33
+ for (const meta of metas) discoveredClasses.set(createClassKey(meta.filePath, meta.className), meta);
34
+ if (previousLazyClassKeys) for (const key of previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
35
+ if (lazyClassKeys.size) for (const key of lazyClassKeys) lazyReferencedClassKeys.add(key);
36
+ };
37
+ return {
38
+ name: "vite-plugin-alloy",
39
+ configResolved(config) {
40
+ resolvedRoot = config.root ?? process.cwd();
41
+ try {
42
+ const pkgPath = path.resolve(resolvedRoot, "package.json");
43
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
44
+ if (typeof pkg.name === "string") packageName = pkg.name;
45
+ } catch {}
46
+ providerModuleRefs.length = 0;
47
+ for (const entry of configuredProviderEntries) {
48
+ const absPath = path.isAbsolute(entry) ? entry : path.resolve(resolvedRoot, entry);
49
+ providerModuleRefs.push({
50
+ absPath,
51
+ importPath: normalizeImportPath(absPath)
52
+ });
53
+ }
54
+ },
55
+ resolveId(id) {
56
+ if (id === virtualModuleId) return resolvedVirtualModuleId;
57
+ },
58
+ transform(code, id) {
59
+ if (!/\.(tsx?|ts)$/i.test(id) || id.endsWith(".d.ts")) return null;
60
+ if (id.includes("node_modules")) return null;
61
+ processUpdate(id, code);
62
+ return {
63
+ code,
64
+ map: null
65
+ };
66
+ },
67
+ handleHotUpdate(ctx) {
68
+ const file = ctx.file;
69
+ if (!ctx.modules.length) {
70
+ const removed = discovery.removeFile(file);
71
+ if (removed.previousMetas) for (const meta of removed.previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
72
+ if (removed.previousLazyClassKeys) for (const key of removed.previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
73
+ }
74
+ return ctx.modules;
75
+ },
76
+ buildStart() {
77
+ discovery.clear();
78
+ discoveredClasses.clear();
79
+ lazyReferencedClassKeys.clear();
80
+ for (const ref of providerModuleRefs) this.addWatchFile(ref.absPath);
81
+ const files = walkSync(path.join(resolvedRoot, "src"));
82
+ for (const file of files) if (/\.(tsx?|ts)$/i.test(file) && !file.endsWith(".d.ts")) try {
83
+ processUpdate(file, fs.readFileSync(file, "utf-8"));
84
+ } catch {}
85
+ },
86
+ async load(id) {
87
+ if (id !== resolvedVirtualModuleId) return;
88
+ const metas = Array.from(discoveredClasses.values());
89
+ for (const meta of metas) {
90
+ const normalizedMetaPath = normalizeImportPath(meta.filePath);
91
+ const trimmedNormalizedMetaPath = normalizedMetaPath.replace(/^\/+/g, "");
92
+ const looksRootRelative = normalizedMetaPath === "/src" || normalizedMetaPath.startsWith("/src/");
93
+ let relPath = path.relative(resolvedRoot, meta.filePath);
94
+ if (path.sep === "\\") relPath = relPath.split(path.sep).join("/");
95
+ if (looksRootRelative || !relPath || relPath.startsWith("..") || relPath.startsWith("\\")) relPath = trimmedNormalizedMetaPath || normalizedMetaPath.replace(/^\/+/g, "");
96
+ meta.identifierKey = `alloy:${packageName}/${relPath}#${meta.className}`;
97
+ }
98
+ const manifestData = await readManifests(options.manifests ?? []);
99
+ const manifestServices = manifestData.services;
100
+ const loadedManifests = manifestData.loadedManifests;
101
+ if (metas.length && manifestServices.length) {
102
+ const duplicates = findDuplicateManifestServices(metas, manifestServices);
103
+ if (duplicates.length) {
104
+ const details = duplicates.map((d) => `- ${d.exportName}: local [${d.localPaths.join(", ")}] vs manifest '${d.manifestImport}'`).join("\n");
105
+ throw new Error([
106
+ "[alloy] Duplicate service registrations detected.",
107
+ details,
108
+ "Resolve by removing one source (local or manifest) to avoid ambiguous DI keys."
109
+ ].join("\n"));
110
+ }
111
+ }
112
+ const combinedMetas = [...metas, ...manifestServices.map((svc) => ({
113
+ className: svc.exportName,
114
+ filePath: svc.importPath,
115
+ metadata: {
116
+ scope: svc.scope,
117
+ dependencies: []
118
+ }
119
+ }))];
120
+ const resolver = new IdentifierResolver(combinedMetas);
121
+ const metasByName = groupMetasByName(combinedMetas);
122
+ for (const svc of manifestServices) metas.push(toMetaFromManifest(svc, metasByName, resolver, lazyReferencedClassKeys));
123
+ const providerImports = Array.from(new Set([...providerModuleRefs.map((ref) => ref.importPath), ...manifestData.providers]));
124
+ reconcileLazySet(metas, lazyReferencedClassKeys, collectEagerReferencedNames(metas));
125
+ augmentFactoryLazyServices(metas, lazyServiceKeys);
126
+ const code = generateContainerModule(metas, new Set(lazyReferencedClassKeys), providerImports);
127
+ const dtsDir = path.resolve(resolvedRoot, options.containerDeclarationDir ?? "./src");
128
+ const dtsContent = generateContainerTypeDefinition(metas, (filePath) => {
129
+ if (path.isAbsolute(filePath)) {
130
+ let rel = path.relative(dtsDir, filePath);
131
+ rel = rel.split(path.sep).join(path.posix.sep);
132
+ if (!rel.startsWith(".")) rel = "./" + rel;
133
+ return rel;
134
+ }
135
+ return filePath;
136
+ });
137
+ if (!fs.existsSync(dtsDir)) fs.mkdirSync(dtsDir, { recursive: true });
138
+ const dtsPath = path.join(dtsDir, "alloy-container.d.ts");
139
+ fs.writeFileSync(dtsPath, dtsContent);
140
+ if (loadedManifests && loadedManifests.length > 0) {
141
+ const manifestsDts = generateManifestTypeDefinition(loadedManifests.map((m) => ({
142
+ packageName: m.packageName,
143
+ services: m.services
144
+ })));
145
+ const manifestsDtsPath = path.join(dtsDir, "alloy-manifests.d.ts");
146
+ fs.writeFileSync(manifestsDtsPath, manifestsDts);
147
+ }
148
+ return code;
149
+ }
150
+ };
151
+ }
152
+
153
+ //#endregion
154
+ export { alloy };
@@ -0,0 +1,213 @@
1
+ import { createClassKey, createSymbolKey, normalizeImportPath } from "../core/utils.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/plugins/vite-plugin/manifest-utils.ts
5
+ /**
6
+ * Reads a list of manifest objects and returns aggregated service + provider module specifiers.
7
+ *
8
+ * @param inputs Direct manifest objects.
9
+ * @returns Aggregated arrays of service descriptors and provider specifiers.
10
+ */
11
+ async function readManifests(inputs) {
12
+ const services = [];
13
+ const providers = [];
14
+ const loadedManifests = [];
15
+ const manifestSchema = z.object({
16
+ schemaVersion: z.number().optional(),
17
+ packageName: z.string(),
18
+ services: z.array(z.object({
19
+ importPath: z.string(),
20
+ exportName: z.string(),
21
+ symbolKey: z.string(),
22
+ scope: z.enum(["singleton", "transient"]),
23
+ deps: z.array(z.string()).default([]),
24
+ tokenDeps: z.array(z.object({
25
+ exportName: z.string(),
26
+ importPath: z.string()
27
+ })).default([]),
28
+ lazyDeps: z.array(z.object({
29
+ importPath: z.string(),
30
+ exportName: z.string(),
31
+ retry: z.object({
32
+ retries: z.number(),
33
+ backoffMs: z.number().optional(),
34
+ factor: z.number().optional()
35
+ }).optional()
36
+ })).default([])
37
+ })).default([]),
38
+ providers: z.array(z.string()).default([])
39
+ });
40
+ for (const manifest of inputs) {
41
+ const parsed = manifestSchema.safeParse(manifest);
42
+ if (!parsed.success) continue;
43
+ loadedManifests.push({
44
+ packageName: parsed.data.packageName,
45
+ services: parsed.data.services,
46
+ providers: parsed.data.providers
47
+ });
48
+ for (const svc of parsed.data.services) services.push(svc);
49
+ for (const p of parsed.data.providers) providers.push(p);
50
+ }
51
+ return Promise.resolve({
52
+ services,
53
+ providers,
54
+ loadedManifests
55
+ });
56
+ }
57
+ /**
58
+ * Groups metas by class name to support resolving dependencies that reference classes with collisions.
59
+ *
60
+ * @param metas Metas to index.
61
+ * @returns Map className -> list of metas sharing that name.
62
+ */
63
+ function groupMetasByName(metas) {
64
+ const byName = /* @__PURE__ */ new Map();
65
+ for (const m of metas) {
66
+ const list = byName.get(m.className) ?? [];
67
+ list.push(m);
68
+ byName.set(m.className, list);
69
+ }
70
+ return byName;
71
+ }
72
+ /**
73
+ * Chooses the best meta for a dependency name when there are naming collisions.
74
+ * Preference order:
75
+ * 1. Exact unique match
76
+ * 2. First meta from same package scope (prefix before first slash)
77
+ * 3. Fallback to first candidate
78
+ */
79
+ function selectMetaForDep(metasByName, depName, currentImportPath) {
80
+ const candidates = metasByName.get(depName);
81
+ if (!candidates || candidates.length === 0) return;
82
+ if (candidates.length === 1) return candidates[0];
83
+ const scopePrefix = currentImportPath.split("/")[0];
84
+ return candidates.find((c) => c.filePath.startsWith(scopePrefix)) ?? candidates[0];
85
+ }
86
+ /**
87
+ * Converts a manifest service descriptor into a DiscoveredMeta ready for container codegen.
88
+ * Handles:
89
+ * - Eager dependency identifier resolution (with aliasing)
90
+ * - Token dependencies inclusion
91
+ * - Generation of Lazy(...) expressions for lazyDeps (with optional retry config)
92
+ * - Recording lazy dependency keys into provided lazySet
93
+ *
94
+ * @param svc Manifest descriptor
95
+ * @param metasByName Grouped metas for duplicate resolution
96
+ * @param resolver Collision-aware identifier resolver
97
+ * @param lazySet Target set tracking lazy-only class keys
98
+ */
99
+ function toMetaFromManifest(svc, metasByName, resolver, lazySet) {
100
+ const deps = [];
101
+ const referencedImports = [];
102
+ for (const depName of svc.deps ?? []) {
103
+ const targetMeta = selectMetaForDep(metasByName, depName, svc.importPath);
104
+ if (targetMeta) {
105
+ const expression = resolver.resolve(targetMeta.className, targetMeta.filePath);
106
+ deps.push({
107
+ expression,
108
+ referencedIdentifiers: [expression],
109
+ isLazy: false
110
+ });
111
+ } else deps.push({
112
+ expression: depName,
113
+ referencedIdentifiers: [depName],
114
+ isLazy: false
115
+ });
116
+ }
117
+ if (Array.isArray(svc.tokenDeps)) for (const tok of svc.tokenDeps) {
118
+ deps.push({
119
+ expression: tok.exportName,
120
+ referencedIdentifiers: [tok.exportName],
121
+ isLazy: false
122
+ });
123
+ referencedImports.push({
124
+ name: tok.exportName,
125
+ path: tok.importPath,
126
+ originalName: tok.exportName
127
+ });
128
+ }
129
+ for (const lazy of svc.lazyDeps ?? []) {
130
+ const importer = `() => import('${lazy.importPath}').then(m => m.${lazy.exportName})`;
131
+ let expr;
132
+ if (lazy.retry) {
133
+ const opts = [`retries: ${lazy.retry.retries}`];
134
+ if (typeof lazy.retry.backoffMs === "number") opts.push(`backoffMs: ${lazy.retry.backoffMs}`);
135
+ if (typeof lazy.retry.factor === "number") opts.push(`factor: ${lazy.retry.factor}`);
136
+ expr = `Lazy(${importer}, { ${opts.join(", ")} })`;
137
+ } else expr = `Lazy(${importer})`;
138
+ deps.push({
139
+ expression: expr,
140
+ referencedIdentifiers: [],
141
+ isLazy: true
142
+ });
143
+ lazySet.add(createClassKey(lazy.importPath, lazy.exportName));
144
+ }
145
+ const metadata = {
146
+ scope: svc.scope,
147
+ dependencies: deps
148
+ };
149
+ return {
150
+ className: svc.exportName,
151
+ filePath: svc.importPath,
152
+ identifierKey: svc.symbolKey,
153
+ metadata,
154
+ referencedImports
155
+ };
156
+ }
157
+ /**
158
+ * Extracts class names referenced eagerly (not wrapped in Lazy) from meta metadata blocks.
159
+ * This allows distinguishing services that must remain in static import set.
160
+ *
161
+ * @param metas All metas to scan.
162
+ * @returns Set of class names with at least one eager reference.
163
+ */
164
+ function collectEagerReferencedNames(metas) {
165
+ const eager = /* @__PURE__ */ new Set();
166
+ for (const meta of metas) for (const dep of meta.metadata.dependencies) if (!dep.isLazy) for (const id of dep.referencedIdentifiers) eager.add(id);
167
+ return eager;
168
+ }
169
+ /**
170
+ * Removes any service key from lazySet if the service is also referenced eagerly.
171
+ * A service is only lazy-only if ALL references are lazy.
172
+ */
173
+ function reconcileLazySet(metas, lazySet, eagerNames) {
174
+ for (const meta of metas) if (eagerNames.has(meta.className)) {
175
+ const key = createClassKey(meta.filePath, meta.className);
176
+ lazySet.delete(key);
177
+ }
178
+ }
179
+ /**
180
+ * Adds a factory-lazy wrapper to services configured via plugin option `lazyServices`.
181
+ * Injects a `factory: Lazy(() => import(...))` property into the metadata.
182
+ * Safely skips metas already containing a factory property.
183
+ */
184
+ function augmentFactoryLazyServices(metas, lazyServiceKeys) {
185
+ for (const m of metas) {
186
+ const identifierKey = m.identifierKey ?? createSymbolKey(m.filePath, m.className);
187
+ if (!lazyServiceKeys.has(identifierKey)) continue;
188
+ if (m.metadata.factory) continue;
189
+ const rawPath = m.filePath.startsWith("/@") ? m.filePath.slice(1) : m.filePath;
190
+ const importPath = !/^(\.|\/|[A-Za-z]:\\)/.test(rawPath) ? rawPath : normalizeImportPath(m.filePath);
191
+ const factoryExpr = `Lazy(() => import('${importPath.startsWith("/@") ? importPath.slice(1) : importPath}').then(m => m.${m.className}))`;
192
+ m.metadata.factory = {
193
+ expression: factoryExpr,
194
+ referencedIdentifiers: [],
195
+ isLazy: true
196
+ };
197
+ }
198
+ }
199
+ /**
200
+ * Detects duplicate service registrations between locally discovered metas and ingested manifests.
201
+ * Returns structured info for error reporting.
202
+ */
203
+ function findDuplicateManifestServices(localMetas, manifestServices) {
204
+ const discoveredNames = new Set(localMetas.map((m) => m.className));
205
+ return manifestServices.filter((svc) => discoveredNames.has(svc.exportName)).map((d) => ({
206
+ exportName: d.exportName,
207
+ localPaths: localMetas.filter((m) => m.className === d.exportName).map((m) => normalizeImportPath(m.filePath)),
208
+ manifestImport: d.importPath
209
+ }));
210
+ }
211
+
212
+ //#endregion
213
+ export { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest };
@@ -0,0 +1,2 @@
1
+ import { alloy } from "./plugins/rollup-plugin/index.js";
2
+ export { alloy, alloy as default };
package/dist/rollup.js ADDED
@@ -0,0 +1,7 @@
1
+ import { alloy } from "./plugins/rollup-plugin/index.js";
2
+
3
+ //#region src/rollup.ts
4
+ var rollup_default = alloy;
5
+
6
+ //#endregion
7
+ export { alloy, rollup_default as default };
@@ -0,0 +1,7 @@
1
+ import { Newable, Token, createToken } from "./lib/types.js";
2
+ import { ServiceIdentifier, clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier } from "./lib/service-identifiers.js";
3
+ import { Container } from "./lib/container.js";
4
+ import { LAZY_IDENTIFIER, Lazy } from "./lib/lazy.js";
5
+ import { Injectable, Singleton, assertDeps, dependenciesRegistry, deps } from "./lib/decorators.js";
6
+ import { ProviderDefinitions, applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle } from "./lib/providers.js";
7
+ export { Container, Injectable, LAZY_IDENTIFIER, Lazy, type Lazy as LazyInterface, type Newable, type ProviderDefinitions, type ServiceIdentifier, Singleton, type Token, applyProviders, asClass, asLazyClass, asValue, assertDeps, clearServiceIdentifierRegistry, createToken, defineProviders, dependenciesRegistry, deps, getConstructorByIdentifier, getServiceIdentifier, lifecycle, registerServiceIdentifier };
@@ -0,0 +1,8 @@
1
+ import { createToken } from "./lib/types.js";
2
+ import { LAZY_IDENTIFIER, Lazy } from "./lib/lazy.js";
3
+ import { Injectable, Singleton, assertDeps, dependenciesRegistry, deps } from "./lib/decorators.js";
4
+ import { clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier } from "./lib/service-identifiers.js";
5
+ import { Container } from "./lib/container.js";
6
+ import { applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle } from "./lib/providers.js";
7
+
8
+ export { Container, Injectable, LAZY_IDENTIFIER, Lazy, Singleton, applyProviders, asClass, asLazyClass, asValue, assertDeps, clearServiceIdentifierRegistry, createToken, defineProviders, dependenciesRegistry, deps, getConstructorByIdentifier, getServiceIdentifier, lifecycle, registerServiceIdentifier };
package/dist/test.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Newable, Token, createToken } from "./lib/types.js";
2
+ import { ServiceIdentifier } from "./lib/service-identifiers.js";
3
+ import { Container } from "./lib/container.js";
4
+ import { ProviderDefinitions } from "./lib/providers.js";
5
+ import { MockOf } from "./lib/testing/mocking.js";
6
+ import { vi } from "vitest";
7
+
8
+ //#region src/test.d.ts
9
+ interface OverrideSpec {
10
+ /** Class constructor instance overrides */
11
+ instances?: Array<[Newable<unknown>, unknown]>;
12
+ /** Token value overrides */
13
+ tokens?: Array<[Token<unknown>, unknown]>;
14
+ }
15
+ interface CreateTestContainerOptions {
16
+ overrides?: OverrideSpec;
17
+ autoMock?: boolean;
18
+ target?: Newable<unknown>;
19
+ providers?: ProviderDefinitions | ProviderDefinitions[];
20
+ }
21
+ interface TestContainerHandle {
22
+ container: Container;
23
+ get<T>(target: Newable<T> | ServiceIdentifier<T>): Promise<T>;
24
+ getIdentifier?<T>(ctor: Newable<T>): ServiceIdentifier<T>;
25
+ /** Retrieve a token value via a synthetic classless access */
26
+ getToken<T>(token: Token<T>): T;
27
+ /** Provide a token value into the container */
28
+ provideToken?<T>(token: Token<T>, value: T): void;
29
+ /** Placeholder restore hook (future phases may implement overlay stacks). */
30
+ restore(): void;
31
+ /** Retrieve a single class mock (if autoMock enabled). */
32
+ getMock?<T>(ctor: Newable<T>): MockOf<T> | undefined;
33
+ /** Retrieve multiple class mocks preserving tuple order. */
34
+ getMocks?<T extends readonly Newable<unknown>[]>(ctors: T): { [K in keyof T]: T[K] extends Newable<infer I> ? MockOf<I> | undefined : never };
35
+ /** Convenience: get a specific method spy from a mock. */
36
+ spyOf?<T>(ctor: Newable<T>, method: Extract<keyof T, string>): ReturnType<typeof vi.fn> | undefined;
37
+ /** Convenience: reset all mock spies (calls vi.fn().mockReset()). */
38
+ clearMockSpies?(): void;
39
+ }
40
+ /**
41
+ * Create a test-focused container with manual overrides.
42
+ * - Does not perform auto-mocking (Phase 2 will expand this).
43
+ */
44
+ declare function createTestContainer(opts?: CreateTestContainerOptions | OverrideSpec): TestContainerHandle & {
45
+ getMock<T>(ctor: Newable<T>): MockOf<T> | undefined;
46
+ getMocks<T extends readonly Newable<unknown>[]>(ctors: T): { [K in keyof T]: T[K] extends Newable<infer I> ? MockOf<I> | undefined : never };
47
+ provideToken<T>(token: Token<T>, value: T): void;
48
+ };
49
+ //#endregion
50
+ export { CreateTestContainerOptions, type MockOf, OverrideSpec, TestContainerHandle, createTestContainer, createToken };
package/dist/test.js ADDED
@@ -0,0 +1,67 @@
1
+ import { createToken } from "./lib/types.js";
2
+ import { Container } from "./lib/container.js";
3
+ import { applyProviders } from "./lib/providers.js";
4
+ import { restoreRegistry, snapshotRegistry } from "./lib/testing/registry.js";
5
+ import { applyAutoMocks } from "./lib/testing/mocking.js";
6
+
7
+ //#region src/test.ts
8
+ /**
9
+ * Create a test-focused container with manual overrides.
10
+ * - Does not perform auto-mocking (Phase 2 will expand this).
11
+ */
12
+ function createTestContainer(opts) {
13
+ const isLegacy = !!opts && !("autoMock" in opts) && !("target" in opts) && !("overrides" in opts);
14
+ const normalizedOpts = isLegacy ? { overrides: opts } : opts ?? {};
15
+ const overrides = normalizedOpts.overrides ?? (isLegacy ? opts : void 0);
16
+ const container = new Container();
17
+ const snapshot = snapshotRegistry();
18
+ if (normalizedOpts.providers) applyProviders(container, normalizedOpts.providers);
19
+ for (const [tok, value] of overrides?.tokens ?? []) container.provideValue(tok, value);
20
+ const overriddenCtors = new Set(overrides?.instances?.map(([c]) => c) ?? []);
21
+ for (const [ctor, instance] of overrides?.instances ?? []) container.overrideInstance(ctor, instance);
22
+ let mocks;
23
+ let lazyPatches;
24
+ if (normalizedOpts.autoMock && normalizedOpts.target) {
25
+ const auto = applyAutoMocks({
26
+ target: normalizedOpts.target,
27
+ container,
28
+ overridesCtors: overriddenCtors
29
+ });
30
+ mocks = auto.mocks;
31
+ lazyPatches = auto.lazyPatches;
32
+ }
33
+ return {
34
+ container,
35
+ get: (target) => typeof target === "symbol" ? container.getByIdentifier(target) : container.get(target),
36
+ getIdentifier: (ctor) => container.getIdentifier(ctor),
37
+ getToken: (token) => {
38
+ return container.getToken(token);
39
+ },
40
+ provideToken: (token, value) => {
41
+ container.provideValue(token, value);
42
+ },
43
+ getMock: (ctor) => {
44
+ return mocks?.get(ctor) ?? void 0;
45
+ },
46
+ getMocks: (ctors) => {
47
+ return ctors.map((c) => mocks?.get(c));
48
+ },
49
+ spyOf: (ctor, method) => {
50
+ return (mocks?.get(ctor))?.spies[method];
51
+ },
52
+ clearMockSpies: () => {
53
+ if (!mocks) return;
54
+ for (const [, m] of mocks) {
55
+ const spies = m.spies;
56
+ for (const key of Object.keys(spies)) spies[key].mockReset();
57
+ }
58
+ },
59
+ restore: () => {
60
+ for (const patch of lazyPatches ?? []) patch.lazy.importer = patch.originalImporter;
61
+ restoreRegistry(snapshot);
62
+ }
63
+ };
64
+ }
65
+
66
+ //#endregion
67
+ export { createTestContainer, createToken };
@@ -0,0 +1 @@
1
+ {"root":["../src/alloy-container.d.ts","../src/entry-points.test.ts","../src/rollup.ts","../src/runtime.ts","../src/test.ts","../src/vite.ts","../src/lib/container.identifiers.test.ts","../src/lib/container.internals.test.ts","../src/lib/container.test.ts","../src/lib/container.testing-features.test.ts","../src/lib/container.ts","../src/lib/decorators.runtime.test.ts","../src/lib/decorators.test-d.ts","../src/lib/decorators.ts","../src/lib/dependency-error.ts","../src/lib/env-detection.test.ts","../src/lib/env-detection.ts","../src/lib/lazy-retry.test.ts","../src/lib/lazy.ts","../src/lib/providers.test.ts","../src/lib/providers.ts","../src/lib/scope.ts","../src/lib/service-identifiers.test.ts","../src/lib/service-identifiers.ts","../src/lib/tokens.test.ts","../src/lib/types.ts","../src/lib/testing/mocking.test.ts","../src/lib/testing/mocking.ts","../src/lib/testing/registry.test.ts","../src/lib/testing/registry.ts","../src/plugins/core/codegen.test.ts","../src/plugins/core/codegen.ts","../src/plugins/core/decorators.helpers.test.ts","../src/plugins/core/decorators.ts","../src/plugins/core/discovery-store.ts","../src/plugins/core/identifier-resolver.test.ts","../src/plugins/core/identifier-resolver.ts","../src/plugins/core/lazy-utils.ts","../src/plugins/core/lazy.helpers.test.ts","../src/plugins/core/lazy.ts","../src/plugins/core/scanner.test.ts","../src/plugins/core/scanner.ts","../src/plugins/core/types.ts","../src/plugins/core/utils.ts","../src/plugins/rollup-plugin/build-utils.ts","../src/plugins/rollup-plugin/index.ts","../src/plugins/rollup-plugin/rollup-plugin.test.ts","../src/plugins/vite-plugin/codegen.specifier.test.ts","../src/plugins/vite-plugin/duplicate-registrations.test.ts","../src/plugins/vite-plugin/fixture-subpaths.test.ts","../src/plugins/vite-plugin/index.ts","../src/plugins/vite-plugin/lazy-services.test.ts","../src/plugins/vite-plugin/lifecycle-and-hmr.test.ts","../src/plugins/vite-plugin/manifest-utils.ts","../src/plugins/vite-plugin/manifest-utils.validation.test.ts","../src/plugins/vite-plugin/module-generation.test.ts","../src/plugins/vite-plugin/transform-guards.test.ts"],"version":"5.9.3"}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { AlloyPluginOptions, alloy } from "./plugins/vite-plugin/index.js";
2
+ export { type AlloyPluginOptions, alloy, alloy as default };
package/dist/vite.js ADDED
@@ -0,0 +1,7 @@
1
+ import { alloy } from "./plugins/vite-plugin/index.js";
2
+
3
+ //#region src/vite.ts
4
+ var vite_default = alloy;
5
+
6
+ //#endregion
7
+ export { alloy, vite_default as default };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "alloy-di",
3
+ "version": "0.1.0",
4
+ "description": "A compile-time dependency injection plugin for Vite",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "rimraf dist && rolldown -c",
10
+ "dev": "rolldown -c rolldown.config.ts --watch",
11
+ "format": "prettier -u --write ./",
12
+ "format:check": "prettier -u --check ./",
13
+ "lint": "oxlint --fix --type-aware --config ./.oxlintrc.json .",
14
+ "lint:check": "oxlint --type-aware --config ./.oxlintrc.json .",
15
+ "test": "vitest --run",
16
+ "test:cover": "vitest --run --coverage",
17
+ "test:watch": "vitest",
18
+ "typecheck": "tsc -b . --noEmit"
19
+ },
20
+ "exports": {
21
+ "./runtime": {
22
+ "types": "./dist/runtime.d.ts",
23
+ "import": "./dist/runtime.js"
24
+ },
25
+ "./vite": {
26
+ "types": "./dist/vite.d.ts",
27
+ "import": "./dist/vite.js"
28
+ },
29
+ "./rollup": {
30
+ "types": "./dist/rollup.d.ts",
31
+ "import": "./dist/rollup.js"
32
+ },
33
+ "./test": {
34
+ "types": "./dist/test.d.ts",
35
+ "import": "./dist/test.js"
36
+ }
37
+ },
38
+ "keywords": [
39
+ "vite",
40
+ "vite-plugin",
41
+ "di",
42
+ "dependency-injection",
43
+ "typescript"
44
+ ],
45
+ "author": "Mikael Selander",
46
+ "license": "MIT",
47
+ "files": [
48
+ "dist",
49
+ "README.md"
50
+ ],
51
+ "devDependencies": {
52
+ "@rollup/plugin-typescript": "^12.3.0",
53
+ "rimraf": "^6.1.0",
54
+ "rolldown": "1.0.0-beta.52",
55
+ "rolldown-plugin-dts": "^0.18.1"
56
+ },
57
+ "dependencies": {
58
+ "tslib": "^2.8.1",
59
+ "zod": "^4.1.13"
60
+ },
61
+ "peerDependencies": {
62
+ "vitest": ">=4.0.14 <5.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "vitest": {
66
+ "optional": true
67
+ }
68
+ }
69
+ }