alloy-di 1.2.2 → 1.3.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.
- package/dist/lib/container.d.ts +10 -2
- package/dist/lib/container.js +30 -17
- package/dist/lib/decorators.js +33 -0
- package/dist/lib/env-detection.d.ts +24 -0
- package/dist/lib/env-detection.js +22 -9
- package/dist/lib/service-identifiers.d.ts +5 -2
- package/dist/lib/service-identifiers.js +6 -2
- package/dist/plugins/core/codegen.js +195 -56
- package/dist/plugins/{vite-plugin → core}/container-loader.js +16 -13
- package/dist/plugins/{vite-plugin → core}/discovery-runtime.js +4 -14
- package/dist/plugins/core/discovery-store.js +15 -0
- package/dist/plugins/{vite-plugin → core}/manifest-utils.js +14 -4
- package/dist/plugins/core/scanner.js +31 -2
- package/dist/plugins/core/utils.js +46 -9
- package/dist/plugins/{vite-plugin → core}/visualization-utils.d.ts +1 -1
- package/dist/plugins/{vite-plugin → core}/visualization-utils.js +1 -1
- package/dist/plugins/{vite-plugin → core}/visualizer.d.ts +1 -1
- package/dist/plugins/{vite-plugin → core}/visualizer.js +24 -16
- package/dist/plugins/vite-plugin/index.d.ts +1 -1
- package/dist/plugins/vite-plugin/index.js +8 -4
- package/dist/plugins/vite-plugin/module-invalidation.js +13 -0
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +2 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/lib/container.d.ts
CHANGED
|
@@ -89,8 +89,12 @@ declare class Container {
|
|
|
89
89
|
*/
|
|
90
90
|
private resolveParam;
|
|
91
91
|
/**
|
|
92
|
-
* Execute a lazy importer with optional retry/backoff semantics
|
|
93
|
-
*
|
|
92
|
+
* Execute a lazy importer with optional retry/backoff semantics, then
|
|
93
|
+
* validate that the imported value is a constructor.
|
|
94
|
+
*
|
|
95
|
+
* Only the dynamic import itself is retried; a successful import that does
|
|
96
|
+
* not yield a constructor is deterministic and fails immediately without
|
|
97
|
+
* backoff or re-wrapping.
|
|
94
98
|
*
|
|
95
99
|
* @param lazyDep - Lazy dependency wrapper with importer function and retry config
|
|
96
100
|
* @param target - Service being resolved (for error messages)
|
|
@@ -99,6 +103,10 @@ declare class Container {
|
|
|
99
103
|
* @throws Error if all retry attempts exhausted or import returns non-constructor
|
|
100
104
|
*/
|
|
101
105
|
private importWithRetry;
|
|
106
|
+
/**
|
|
107
|
+
* Run a lazy importer, retrying failed imports with exponential backoff.
|
|
108
|
+
*/
|
|
109
|
+
private runImporterWithRetry;
|
|
102
110
|
/**
|
|
103
111
|
* Resolve a token dependency via registered value providers.
|
|
104
112
|
*/
|
package/dist/lib/container.js
CHANGED
|
@@ -184,8 +184,12 @@ var Container = class {
|
|
|
184
184
|
return classification;
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
|
-
* Execute a lazy importer with optional retry/backoff semantics
|
|
188
|
-
*
|
|
187
|
+
* Execute a lazy importer with optional retry/backoff semantics, then
|
|
188
|
+
* validate that the imported value is a constructor.
|
|
189
|
+
*
|
|
190
|
+
* Only the dynamic import itself is retried; a successful import that does
|
|
191
|
+
* not yield a constructor is deterministic and fails immediately without
|
|
192
|
+
* backoff or re-wrapping.
|
|
189
193
|
*
|
|
190
194
|
* @param lazyDep - Lazy dependency wrapper with importer function and retry config
|
|
191
195
|
* @param target - Service being resolved (for error messages)
|
|
@@ -194,23 +198,28 @@ var Container = class {
|
|
|
194
198
|
* @throws Error if all retry attempts exhausted or import returns non-constructor
|
|
195
199
|
*/
|
|
196
200
|
async importWithRetry(lazyDep, target, resolutionStack) {
|
|
197
|
-
const
|
|
201
|
+
const module = await this.runImporterWithRetry(lazyDep, target, resolutionStack);
|
|
202
|
+
const depClass = typeof module === "object" && module !== null && "default" in module ? module.default : module;
|
|
203
|
+
if (!isConstructor(depClass)) {
|
|
204
|
+
const stackPath = this.formatStackPath(target, resolutionStack);
|
|
205
|
+
throw new DependencyResolutionError(`Lazy importer did not return a class for dependency while resolving ${target.name}. Resolution stack: ${stackPath}. Received type: ${typeof depClass}`, {
|
|
206
|
+
target,
|
|
207
|
+
resolutionStack,
|
|
208
|
+
failedDependency: depClass
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return depClass;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Run a lazy importer, retrying failed imports with exponential backoff.
|
|
215
|
+
*/
|
|
216
|
+
async runImporterWithRetry(lazyDep, target, resolutionStack) {
|
|
198
217
|
const retries = lazyDep.retry?.retries ?? 0;
|
|
199
218
|
const baseDelay = lazyDep.retry?.backoffMs ?? 0;
|
|
200
219
|
const factor = lazyDep.retry?.factor ?? 2;
|
|
201
220
|
let attempt = 0;
|
|
202
221
|
while (true) try {
|
|
203
|
-
|
|
204
|
-
const depClass = typeof module === "object" && module !== null && "default" in module ? module.default : module;
|
|
205
|
-
if (!isConstructor(depClass)) {
|
|
206
|
-
const stackPath = this.formatStackPath(target, resolutionStack);
|
|
207
|
-
throw new DependencyResolutionError(`Lazy importer did not return a class for dependency while resolving ${target.name}. Resolution stack: ${stackPath}. Received type: ${typeof depClass}`, {
|
|
208
|
-
target,
|
|
209
|
-
resolutionStack,
|
|
210
|
-
failedDependency: depClass
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
return depClass;
|
|
222
|
+
return await lazyDep.importer();
|
|
214
223
|
} catch (err) {
|
|
215
224
|
if (attempt >= retries) {
|
|
216
225
|
const stackPath = this.formatStackPath(target, resolutionStack);
|
|
@@ -251,10 +260,14 @@ var Container = class {
|
|
|
251
260
|
const cached = this.metadataCache.get(target);
|
|
252
261
|
if (cached) return cached;
|
|
253
262
|
const registryEntry = dependenciesRegistry.get(target);
|
|
263
|
+
if (!registryEntry) return {
|
|
264
|
+
scope: ServiceScope.TRANSIENT,
|
|
265
|
+
dependencies: []
|
|
266
|
+
};
|
|
254
267
|
const metadata = {
|
|
255
|
-
scope: registryEntry
|
|
256
|
-
dependencies: (registryEntry
|
|
257
|
-
factory: registryEntry
|
|
268
|
+
scope: registryEntry.scope ?? ServiceScope.TRANSIENT,
|
|
269
|
+
dependencies: (registryEntry.dependencies ?? (() => []))(),
|
|
270
|
+
factory: registryEntry.factory
|
|
258
271
|
};
|
|
259
272
|
this.metadataCache.set(target, metadata);
|
|
260
273
|
return metadata;
|
package/dist/lib/decorators.js
CHANGED
|
@@ -65,11 +65,44 @@ function createDecoratorWithoutDeps(scope) {
|
|
|
65
65
|
function isDependenciesArg(value) {
|
|
66
66
|
return typeof value === "function" || Array.isArray(value);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Detect a class constructor passed where dependency metadata was expected.
|
|
70
|
+
*
|
|
71
|
+
* Class constructors have a non-writable `prototype`; dependency thunks
|
|
72
|
+
* (arrow functions or plain functions) either lack a `prototype` descriptor
|
|
73
|
+
* or have a writable one.
|
|
74
|
+
*
|
|
75
|
+
* @param value - The argument supplied to `@Injectable`/`@Singleton`.
|
|
76
|
+
* @returns True if the value is a class constructor.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
function isClassConstructor(value) {
|
|
80
|
+
if (typeof value !== "function") return false;
|
|
81
|
+
const prototypeDescriptor = Object.getOwnPropertyDescriptor(value, "prototype");
|
|
82
|
+
return prototypeDescriptor !== void 0 && !prototypeDescriptor.writable;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reject the bare-decorator misuse `@Injectable` / `@Singleton` (no parens).
|
|
86
|
+
*
|
|
87
|
+
* Applied bare, the decorator factory itself receives the class, mistakes it
|
|
88
|
+
* for a dependencies thunk, and returns a decorator function — which legacy
|
|
89
|
+
* decorator semantics then substitute for the class. Throwing here turns that
|
|
90
|
+
* silent class replacement into an immediate, actionable error.
|
|
91
|
+
*
|
|
92
|
+
* @param value - The first argument received by the decorator factory.
|
|
93
|
+
* @param decoratorName - Public decorator name for the error message.
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
function assertNotBareDecorator(value, decoratorName) {
|
|
97
|
+
if (isClassConstructor(value)) throw new TypeError(`@${decoratorName} must be called — use @${decoratorName}() instead of @${decoratorName}`);
|
|
98
|
+
}
|
|
68
99
|
function Injectable(depsOrScope, scopeOverride) {
|
|
100
|
+
assertNotBareDecorator(depsOrScope, "Injectable");
|
|
69
101
|
if (isDependenciesArg(depsOrScope)) return createDecoratorWithDeps(scopeOverride ?? ServiceScope.TRANSIENT, depsOrScope);
|
|
70
102
|
return createDecoratorWithoutDeps((typeof depsOrScope === "string" ? depsOrScope : void 0) ?? scopeOverride ?? ServiceScope.TRANSIENT);
|
|
71
103
|
}
|
|
72
104
|
function Singleton(dependencies) {
|
|
105
|
+
assertNotBareDecorator(dependencies, "Singleton");
|
|
73
106
|
if (isDependenciesArg(dependencies)) return createDecoratorWithDeps(ServiceScope.SINGLETON, dependencies);
|
|
74
107
|
return createDecoratorWithoutDeps(ServiceScope.SINGLETON);
|
|
75
108
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/lib/env-detection.d.ts
|
|
2
|
+
type ImportMetaEnvShape = {
|
|
3
|
+
MODE?: string;
|
|
4
|
+
PROD?: boolean;
|
|
5
|
+
NODE_ENV?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
type EnvDetectionOverrides = {
|
|
9
|
+
/**
|
|
10
|
+
* Explicit import.meta.env replacement. Use `null` to force "no env" behavior.
|
|
11
|
+
*/
|
|
12
|
+
importMetaEnv?: ImportMetaEnvShape | null;
|
|
13
|
+
/**
|
|
14
|
+
* Explicit NODE_ENV replacement. Use `null` to ignore process.env.
|
|
15
|
+
*/
|
|
16
|
+
nodeEnv?: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Short-circuit the entire detection logic with a predetermined boolean.
|
|
19
|
+
*/
|
|
20
|
+
isDev?: boolean;
|
|
21
|
+
};
|
|
22
|
+
declare function setEnvDetectionOverrides(overrides: EnvDetectionOverrides | undefined): void;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { EnvDetectionOverrides, setEnvDetectionOverrides };
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
function isRecord(value) {
|
|
3
3
|
return typeof value === "object" && value !== null;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Build-time-injected detection overrides.
|
|
7
|
+
*
|
|
8
|
+
* The Vite plugin emits a `setEnvDetectionOverrides({ isDev: ... })` call
|
|
9
|
+
* into the generated container module so detection in plugin-driven setups
|
|
10
|
+
* uses the bundler's authoritative mode instead of runtime sniffing.
|
|
11
|
+
*/
|
|
12
|
+
let injectedOverrides;
|
|
13
|
+
function setEnvDetectionOverrides(overrides) {
|
|
14
|
+
injectedOverrides = overrides;
|
|
15
|
+
}
|
|
5
16
|
function readImportMetaEnvFromRuntime() {
|
|
6
17
|
if (typeof import.meta === "undefined") return;
|
|
7
|
-
const
|
|
8
|
-
if (!isRecord(candidate) || !("env" in candidate)) return;
|
|
9
|
-
const envValue = candidate.env;
|
|
18
|
+
const envValue = import.meta.env;
|
|
10
19
|
if (!isRecord(envValue)) return;
|
|
11
20
|
const env = {};
|
|
12
21
|
if (typeof envValue.MODE === "string") env.MODE = envValue.MODE;
|
|
@@ -15,8 +24,11 @@ function readImportMetaEnvFromRuntime() {
|
|
|
15
24
|
return env;
|
|
16
25
|
}
|
|
17
26
|
function readProcessNodeEnv() {
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
try {
|
|
28
|
+
return "development";
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
20
32
|
}
|
|
21
33
|
function getImportMetaEnv(overrides) {
|
|
22
34
|
if (overrides?.importMetaEnv === null) return;
|
|
@@ -29,14 +41,15 @@ function getNodeEnv(overrides) {
|
|
|
29
41
|
return readProcessNodeEnv();
|
|
30
42
|
}
|
|
31
43
|
function isDevEnvironment(overrides) {
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
const effective = overrides ?? injectedOverrides;
|
|
45
|
+
if (typeof effective?.isDev === "boolean") return effective.isDev;
|
|
46
|
+
const nodeEnv = getNodeEnv(effective);
|
|
34
47
|
if (typeof nodeEnv === "string") return nodeEnv !== "production";
|
|
35
|
-
const importMetaEnv = getImportMetaEnv(
|
|
48
|
+
const importMetaEnv = getImportMetaEnv(effective);
|
|
36
49
|
if (typeof importMetaEnv?.PROD === "boolean") return !importMetaEnv.PROD;
|
|
37
50
|
if (typeof importMetaEnv?.MODE === "string") return importMetaEnv.MODE !== "production";
|
|
38
51
|
if (typeof importMetaEnv?.NODE_ENV === "string") return importMetaEnv.NODE_ENV !== "production";
|
|
39
52
|
return true;
|
|
40
53
|
}
|
|
41
54
|
//#endregion
|
|
42
|
-
export { isDevEnvironment };
|
|
55
|
+
export { isDevEnvironment, setEnvDetectionOverrides };
|
|
@@ -13,8 +13,11 @@ type ServiceIdentifier<T = unknown> = symbol & {
|
|
|
13
13
|
/**
|
|
14
14
|
* Associates a constructor with a stable identifier. When an explicit identifier
|
|
15
15
|
* is provided (e.g., by generated metadata), it becomes canonical for the
|
|
16
|
-
* constructor
|
|
17
|
-
*
|
|
16
|
+
* constructor when the constructor is first registered. If the constructor was
|
|
17
|
+
* already assigned an auto-generated identifier, later explicit identifiers are
|
|
18
|
+
* recorded as aliases so both symbols resolve to the same constructor.
|
|
19
|
+
* Attempting to reuse an identifier with a different constructor throws to
|
|
20
|
+
* surface manifest/config mismatches early.
|
|
18
21
|
*/
|
|
19
22
|
declare function registerServiceIdentifier<T>(ctor: Constructor, explicitIdentifier?: ServiceIdentifier<T>): ServiceIdentifier<T>;
|
|
20
23
|
/**
|
|
@@ -11,8 +11,11 @@ function createServiceIdentifier(ctor) {
|
|
|
11
11
|
/**
|
|
12
12
|
* Associates a constructor with a stable identifier. When an explicit identifier
|
|
13
13
|
* is provided (e.g., by generated metadata), it becomes canonical for the
|
|
14
|
-
* constructor
|
|
15
|
-
*
|
|
14
|
+
* constructor when the constructor is first registered. If the constructor was
|
|
15
|
+
* already assigned an auto-generated identifier, later explicit identifiers are
|
|
16
|
+
* recorded as aliases so both symbols resolve to the same constructor.
|
|
17
|
+
* Attempting to reuse an identifier with a different constructor throws to
|
|
18
|
+
* surface manifest/config mismatches early.
|
|
16
19
|
*/
|
|
17
20
|
function registerServiceIdentifier(ctor, explicitIdentifier) {
|
|
18
21
|
const current = ctorToIdentifier.get(ctor);
|
|
@@ -20,6 +23,7 @@ function registerServiceIdentifier(ctor, explicitIdentifier) {
|
|
|
20
23
|
if (explicitIdentifier && explicitIdentifier !== current) {
|
|
21
24
|
const owner = identifierToCtor.get(explicitIdentifier);
|
|
22
25
|
if (owner && owner !== ctor) throw new Error("Attempted to reassign an existing ServiceIdentifier to a different constructor.");
|
|
26
|
+
identifierToCtor.set(explicitIdentifier, ctor);
|
|
23
27
|
}
|
|
24
28
|
return current;
|
|
25
29
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createClassKey, createSymbolKey, hashString, normalizeImportPath } from "./utils.js";
|
|
2
2
|
import { IdentifierResolver } from "./identifier-resolver.js";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import ts from "typescript";
|
|
4
5
|
//#region src/plugins/core/codegen.ts
|
|
5
6
|
function escapeSingleQuotes(value) {
|
|
6
7
|
return value.replaceAll("'", "\\'");
|
|
@@ -18,49 +19,152 @@ function createSymbolDescription(meta) {
|
|
|
18
19
|
return createSymbolKey(meta.filePath, meta.className);
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
22
|
+
* Allocates module-local names from a single shared pool so the generated
|
|
23
|
+
* module's independent naming domains (runtime helpers, service imports,
|
|
24
|
+
* factory-lazy stubs, dependency imports, identifier consts, generated
|
|
25
|
+
* locals) can never collide.
|
|
26
|
+
*/
|
|
27
|
+
function createNamePool(reservedNames) {
|
|
28
|
+
const used = new Set(reservedNames);
|
|
29
|
+
return { claim(base) {
|
|
30
|
+
let candidate = base;
|
|
31
|
+
let suffix = 1;
|
|
32
|
+
while (used.has(candidate)) {
|
|
33
|
+
candidate = `${base}_${suffix}`;
|
|
34
|
+
suffix++;
|
|
35
|
+
}
|
|
36
|
+
used.add(candidate);
|
|
37
|
+
return candidate;
|
|
38
|
+
} };
|
|
39
|
+
}
|
|
40
|
+
function stripModuleExtension(importPath) {
|
|
41
|
+
return importPath.replace(/\.[cm]?[jt]sx?$/i, "");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Identity of an imported binding: the module (extension-insensitive, since
|
|
45
|
+
* references may resolve extensionless while service paths keep theirs) plus
|
|
46
|
+
* the export name.
|
|
47
|
+
*/
|
|
48
|
+
function createBindingKey(importPath, exportName) {
|
|
49
|
+
return `${stripModuleExtension(importPath)}::${exportName}`;
|
|
50
|
+
}
|
|
51
|
+
/** Resolves a referenced import's specifier to a normalized module path. */
|
|
52
|
+
function resolveReferencePath(ref, meta) {
|
|
53
|
+
return normalizeImportPath(ref.path.startsWith(".") ? path.resolve(path.dirname(meta.filePath), ref.path) : ref.path);
|
|
54
|
+
}
|
|
55
|
+
function resolveDependencyImport(ref, normalizedPath, pool, serviceBindings, runtimeImports) {
|
|
56
|
+
if (normalizedPath === "alloy-di/runtime" && ref.originalName && ref.name === ref.originalName && runtimeImports.has(ref.originalName)) return {
|
|
57
|
+
localName: ref.originalName,
|
|
58
|
+
importPath: normalizedPath,
|
|
59
|
+
originalName: ref.originalName,
|
|
60
|
+
reusesExistingBinding: true
|
|
61
|
+
};
|
|
62
|
+
const serviceLocalName = serviceBindings.get(createBindingKey(normalizedPath, ref.originalName ?? "default"));
|
|
63
|
+
if (serviceLocalName) return {
|
|
64
|
+
localName: serviceLocalName,
|
|
65
|
+
importPath: normalizedPath,
|
|
66
|
+
originalName: ref.originalName,
|
|
67
|
+
reusesExistingBinding: true
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
localName: pool.claim(ref.name),
|
|
71
|
+
importPath: normalizedPath,
|
|
72
|
+
originalName: ref.originalName
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
21
76
|
* Analyzes dependencies across all discovered services and resolves imports.
|
|
22
|
-
* Deduplicates imports
|
|
77
|
+
* Deduplicates imports by binding identity, reuses bindings the module
|
|
78
|
+
* already declares (runtime helpers, service imports, factory-lazy stubs),
|
|
79
|
+
* and claims fresh local names from the shared pool otherwise.
|
|
23
80
|
*/
|
|
24
|
-
function resolveDependencyImports(metas) {
|
|
81
|
+
function resolveDependencyImports(metas, pool, serviceBindings, runtimeImports) {
|
|
25
82
|
const importMap = /* @__PURE__ */ new Map();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (const meta of metas) {
|
|
33
|
-
if (!meta.referencedImports?.length) continue;
|
|
34
|
-
for (const ref of meta.referencedImports) {
|
|
35
|
-
if (ref.isTypeOnly) continue;
|
|
36
|
-
const normalizedPath = normalizeImportPath(ref.path.startsWith(".") ? path.resolve(path.dirname(meta.filePath), ref.path) : ref.path);
|
|
37
|
-
const key = `${normalizedPath}::${ref.originalName ?? "default"}`;
|
|
38
|
-
if (importMap.has(key)) continue;
|
|
39
|
-
importMap.set(key, {
|
|
40
|
-
localName: getUniqueName(ref.name),
|
|
41
|
-
importPath: normalizedPath,
|
|
42
|
-
originalName: ref.originalName
|
|
43
|
-
});
|
|
44
|
-
}
|
|
83
|
+
for (const meta of metas) for (const ref of meta.referencedImports ?? []) {
|
|
84
|
+
if (ref.isTypeOnly) continue;
|
|
85
|
+
const normalizedPath = resolveReferencePath(ref, meta);
|
|
86
|
+
const key = createBindingKey(normalizedPath, ref.originalName ?? "default");
|
|
87
|
+
if (importMap.has(key)) continue;
|
|
88
|
+
importMap.set(key, resolveDependencyImport(ref, normalizedPath, pool, serviceBindings, runtimeImports));
|
|
45
89
|
}
|
|
46
90
|
return {
|
|
47
91
|
dependencyImports: Array.from(importMap.values()),
|
|
48
92
|
importMap
|
|
49
93
|
};
|
|
50
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* True when an identifier occupies a name position rather than referencing a
|
|
97
|
+
* binding: property-access names (`ns.Api`), object keys — including method
|
|
98
|
+
* and accessor keys (`{ Api() {} }`, `{ get Api() {} }`) — class member
|
|
99
|
+
* names, qualified names, and destructuring property names.
|
|
100
|
+
*/
|
|
101
|
+
function isNonReferencePosition(node) {
|
|
102
|
+
const parent = node.parent;
|
|
103
|
+
if (ts.isPropertyAccessExpression(parent)) return parent.name === node;
|
|
104
|
+
if (ts.isQualifiedName(parent)) return parent.right === node;
|
|
105
|
+
if (ts.isBindingElement(parent)) return parent.propertyName === node;
|
|
106
|
+
if (ts.isPropertyAssignment(parent) || ts.isMethodDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isMethodSignature(parent) || ts.isPropertySignature(parent)) return parent.name === node;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Decide how to rewrite one identifier node, or skip it.
|
|
111
|
+
*
|
|
112
|
+
* Only binding references are rewritten: name positions and string/comment
|
|
113
|
+
* content keep their text. Shorthand properties expand
|
|
114
|
+
* (`{ Api }` -> `{ Api: Api_1 }`) so the key survives the rename.
|
|
115
|
+
*/
|
|
116
|
+
function createIdentifierEdit(node, source, replacement) {
|
|
117
|
+
const parent = node.parent;
|
|
118
|
+
if (isNonReferencePosition(node)) return;
|
|
119
|
+
const start = node.getStart(source);
|
|
120
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === node) return {
|
|
121
|
+
start,
|
|
122
|
+
end: node.end,
|
|
123
|
+
text: `${node.text}: ${replacement}`
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
start,
|
|
127
|
+
end: node.end,
|
|
128
|
+
text: replacement
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Rewrite identifier references inside a reconstructed dependency expression.
|
|
133
|
+
*
|
|
134
|
+
* The expression is parsed and identifier nodes are replaced by position, so
|
|
135
|
+
* `$`-prefixed names rewrite correctly and occurrences inside string literals
|
|
136
|
+
* (e.g. lazy `import('/src/Api')` specifiers) and comments are untouched —
|
|
137
|
+
* the previous `\b`-regex text replacement got both wrong.
|
|
138
|
+
*/
|
|
139
|
+
function rewriteReferencedIdentifiers(expression, referenced, rewriter) {
|
|
140
|
+
const wrapped = `(${expression});`;
|
|
141
|
+
const source = ts.createSourceFile("alloy-dependency-expression.ts", wrapped, ts.ScriptTarget.ESNext, true);
|
|
142
|
+
const edits = [];
|
|
143
|
+
const visit = (node) => {
|
|
144
|
+
if (ts.isIdentifier(node) && referenced.has(node.text)) {
|
|
145
|
+
const replacement = rewriter(node.text);
|
|
146
|
+
if (replacement && replacement !== node.text) {
|
|
147
|
+
const edit = createIdentifierEdit(node, source, replacement);
|
|
148
|
+
if (edit) edits.push(edit);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
ts.forEachChild(node, visit);
|
|
152
|
+
};
|
|
153
|
+
visit(source);
|
|
154
|
+
let result = wrapped;
|
|
155
|
+
for (const edit of edits.toSorted((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
156
|
+
return result.slice(1, -2);
|
|
157
|
+
}
|
|
51
158
|
function reconstructDependencyExpression(dep, rewriter, contextDir) {
|
|
52
159
|
let expr = dep.expression;
|
|
53
|
-
|
|
54
|
-
const replacement = rewriter(ident);
|
|
55
|
-
if (replacement && replacement !== ident) expr = expr.replaceAll(new RegExp(`\\b${ident}\\b`, "g"), replacement);
|
|
56
|
-
}
|
|
160
|
+
if (dep.referencedIdentifiers.length > 0) expr = rewriteReferencedIdentifiers(expr, new Set(dep.referencedIdentifiers), rewriter);
|
|
57
161
|
if (dep.isLazy) expr = expr.replaceAll(/import\s*\(\s*(['"])(.+?)\1\s*\)/g, (match, quote, importPath) => {
|
|
58
162
|
if (importPath.startsWith(".")) return `import(${quote}${normalizeImportPath(path.resolve(contextDir, importPath))}${quote})`;
|
|
59
163
|
return match;
|
|
60
164
|
});
|
|
61
165
|
return expr;
|
|
62
166
|
}
|
|
63
|
-
function reconstructOptionsText(meta, importMap) {
|
|
167
|
+
function reconstructOptionsText(meta, importMap, serviceRenames) {
|
|
64
168
|
const { scope, dependencies, factory } = meta.metadata;
|
|
65
169
|
const parts = [];
|
|
66
170
|
if (factory) {
|
|
@@ -73,12 +177,11 @@ function reconstructOptionsText(meta, importMap) {
|
|
|
73
177
|
return reconstructDependencyExpression(dep, (ident) => {
|
|
74
178
|
const ref = meta.referencedImports?.find((r) => r.name === ident && !r.isTypeOnly);
|
|
75
179
|
if (ref) {
|
|
76
|
-
const
|
|
77
|
-
const key = `${normalizeImportPath(ref.path.startsWith(".") ? path.resolve(dir, ref.path) : ref.path)}::${ref.originalName ?? "default"}`;
|
|
180
|
+
const key = createBindingKey(resolveReferencePath(ref, meta), ref.originalName ?? "default");
|
|
78
181
|
const resolved = importMap.get(key);
|
|
79
182
|
return resolved ? resolved.localName : ident;
|
|
80
183
|
}
|
|
81
|
-
return ident;
|
|
184
|
+
return serviceRenames.get(ident) ?? ident;
|
|
82
185
|
}, path.dirname(meta.filePath));
|
|
83
186
|
});
|
|
84
187
|
parts.push(`dependencies: () => [${depExprs.join(", ")}]`);
|
|
@@ -86,13 +189,38 @@ function reconstructOptionsText(meta, importMap) {
|
|
|
86
189
|
if (parts.length === 0) return "{}";
|
|
87
190
|
return `{ ${parts.join(", ")} }`;
|
|
88
191
|
}
|
|
89
|
-
function buildImportsAndRegistrations(metas, lazyReferencedClassKeys,
|
|
192
|
+
function buildImportsAndRegistrations(metas, lazyReferencedClassKeys, providerModulePaths, options) {
|
|
193
|
+
const hasProviderModules = providerModulePaths.length > 0;
|
|
90
194
|
const activeMetas = filterActiveMetas(metas, lazyReferencedClassKeys);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
195
|
+
const resolver = new IdentifierResolver(activeMetas);
|
|
196
|
+
const runtimeImports = computeRuntimeImports(activeMetas, hasProviderModules, options?.isDev !== void 0);
|
|
197
|
+
const pool = createNamePool([
|
|
198
|
+
...runtimeImports,
|
|
199
|
+
"registrations",
|
|
200
|
+
"container",
|
|
201
|
+
"providerDefinitions",
|
|
202
|
+
...providerModulePaths.map((_, idx) => `providers_${idx}`)
|
|
203
|
+
]);
|
|
204
|
+
const serviceNames = /* @__PURE__ */ new Map();
|
|
205
|
+
const serviceRenames = /* @__PURE__ */ new Map();
|
|
206
|
+
const serviceBindings = /* @__PURE__ */ new Map();
|
|
207
|
+
for (const meta of activeMetas) {
|
|
208
|
+
const baseName = resolver.resolve(meta.className, meta.filePath);
|
|
209
|
+
const name = pool.claim(baseName);
|
|
210
|
+
serviceNames.set(meta, name);
|
|
211
|
+
if (name !== baseName) serviceRenames.set(baseName, name);
|
|
212
|
+
serviceBindings.set(createBindingKey(getServiceImportPath(meta), meta.className), name);
|
|
213
|
+
}
|
|
214
|
+
const { dependencyImports, importMap } = resolveDependencyImports(activeMetas, pool, serviceBindings, runtimeImports);
|
|
215
|
+
const resolvedRegistrations = enrichRegistrations(activeMetas, {
|
|
216
|
+
resolver,
|
|
217
|
+
serviceNames,
|
|
218
|
+
serviceRenames,
|
|
219
|
+
importMap,
|
|
220
|
+
pool
|
|
221
|
+
});
|
|
94
222
|
const runtimeImportStatement = formatRuntimeImportStatement(runtimeImports);
|
|
95
|
-
const stubsBlock = createStubBlock(dependencyImports, resolvedRegistrations
|
|
223
|
+
const stubsBlock = createStubBlock(dependencyImports, resolvedRegistrations);
|
|
96
224
|
return {
|
|
97
225
|
runtimeImportStatement,
|
|
98
226
|
registrationsBlock: createRegistrationsBlock(buildRegistrationEntries(resolvedRegistrations)),
|
|
@@ -103,13 +231,14 @@ function buildImportsAndRegistrations(metas, lazyReferencedClassKeys, hasProvide
|
|
|
103
231
|
function filterActiveMetas(metas, lazyReferencedClassKeys) {
|
|
104
232
|
return metas.filter((meta) => !lazyReferencedClassKeys.has(createClassKey(meta.filePath, meta.className)));
|
|
105
233
|
}
|
|
106
|
-
function enrichRegistrations(activeMetas,
|
|
234
|
+
function enrichRegistrations(activeMetas, naming) {
|
|
235
|
+
const { resolver, serviceNames, serviceRenames, importMap, pool } = naming;
|
|
107
236
|
return activeMetas.map((meta) => {
|
|
108
|
-
const importName =
|
|
109
|
-
const identifierConst = `${importName}Identifier
|
|
237
|
+
const importName = serviceNames.get(meta) ?? meta.className;
|
|
238
|
+
const identifierConst = pool.claim(`${importName}Identifier`);
|
|
110
239
|
const exportKey = createIdentifierExportKey(meta, resolver);
|
|
111
240
|
const symbolDescription = meta.identifierKey ?? createSymbolDescription(meta);
|
|
112
|
-
const optionsText = reconstructOptionsText(meta, importMap);
|
|
241
|
+
const optionsText = reconstructOptionsText(meta, importMap, serviceRenames);
|
|
113
242
|
return {
|
|
114
243
|
...meta,
|
|
115
244
|
importName,
|
|
@@ -121,34 +250,30 @@ function enrichRegistrations(activeMetas, resolver, importMap) {
|
|
|
121
250
|
};
|
|
122
251
|
});
|
|
123
252
|
}
|
|
124
|
-
function computeRuntimeImports(
|
|
253
|
+
function computeRuntimeImports(activeMetas, hasProviderModules, hasEnvOverrides = false) {
|
|
125
254
|
const imports = new Set(["Container", "dependenciesRegistry"]);
|
|
126
|
-
|
|
255
|
+
if (hasEnvOverrides) imports.add("setEnvDetectionOverrides");
|
|
256
|
+
const needsLazyImport = activeMetas.some((m) => m.metadata.dependencies.some((d) => d.isLazy) || !!m.metadata.factory);
|
|
127
257
|
if (hasProviderModules) imports.add("applyProviders");
|
|
128
258
|
if (needsLazyImport) imports.add("Lazy");
|
|
129
|
-
if (
|
|
259
|
+
if (activeMetas.length) imports.add("registerServiceIdentifier");
|
|
130
260
|
return imports;
|
|
131
261
|
}
|
|
132
262
|
function formatRuntimeImportStatement(imports) {
|
|
133
263
|
return `\nimport { ${Array.from(imports).join(", ")} } from 'alloy-di/runtime';\n`;
|
|
134
264
|
}
|
|
135
|
-
function createStubBlock(dependencyImports, registrations
|
|
265
|
+
function createStubBlock(dependencyImports, registrations) {
|
|
136
266
|
const statements = [];
|
|
137
|
-
const importedNames = new Set(runtimeImports);
|
|
138
267
|
for (const dep of dependencyImports) {
|
|
139
|
-
if (dep.
|
|
140
|
-
if (importedNames.has(dep.localName)) continue;
|
|
268
|
+
if (dep.reusesExistingBinding) continue;
|
|
141
269
|
statements.push(createDependencyImportStatement(dep));
|
|
142
|
-
importedNames.add(dep.localName);
|
|
143
270
|
}
|
|
144
271
|
for (const meta of registrations) {
|
|
145
272
|
if (meta.isFactoryLazy) {
|
|
146
273
|
statements.push(`class ${meta.importName} {}`);
|
|
147
274
|
continue;
|
|
148
275
|
}
|
|
149
|
-
if (importedNames.has(meta.importName)) continue;
|
|
150
276
|
statements.push(createServiceImportStatement(meta));
|
|
151
|
-
importedNames.add(meta.importName);
|
|
152
277
|
}
|
|
153
278
|
return statements.length ? `${statements.join("\n")}\n` : "";
|
|
154
279
|
}
|
|
@@ -158,8 +283,11 @@ function createDependencyImportStatement(dep) {
|
|
|
158
283
|
if (dep.originalName && dep.originalName !== dep.localName) return `import { ${dep.originalName} as ${dep.localName} } from '${dep.importPath}';`;
|
|
159
284
|
return `import { ${dep.localName} } from '${dep.importPath}';`;
|
|
160
285
|
}
|
|
286
|
+
function getServiceImportPath(meta) {
|
|
287
|
+
return !/^(\/|[A-Za-z]:\\|\.|~)/.test(meta.filePath) && !meta.filePath.includes("\\") ? meta.filePath : normalizeImportPath(meta.filePath);
|
|
288
|
+
}
|
|
161
289
|
function createServiceImportStatement(meta) {
|
|
162
|
-
const importPath =
|
|
290
|
+
const importPath = getServiceImportPath(meta);
|
|
163
291
|
if (meta.importName === meta.className) return `import { ${meta.className} } from '${importPath}';`;
|
|
164
292
|
return `import { ${meta.className} as ${meta.importName} } from '${importPath}';`;
|
|
165
293
|
}
|
|
@@ -191,9 +319,10 @@ function createIdentifierExports(registrations) {
|
|
|
191
319
|
* @param lazyReferencedClassKeys - Set of service keys that are referenced ONLY lazily (and thus should not be imported/registered eagerly in this bundle).
|
|
192
320
|
* @param providerModulePaths - List of provider modules to import and apply.
|
|
193
321
|
*/
|
|
194
|
-
function generateContainerModule(metas, lazyReferencedClassKeys, providerModulePaths) {
|
|
322
|
+
function generateContainerModule(metas, lazyReferencedClassKeys, providerModulePaths, options) {
|
|
195
323
|
const hasProviderModules = providerModulePaths.length > 0;
|
|
196
|
-
const { runtimeImportStatement, registrationsBlock, stubsBlock, identifierExportBlock } = buildImportsAndRegistrations(metas, lazyReferencedClassKeys,
|
|
324
|
+
const { runtimeImportStatement, registrationsBlock, stubsBlock, identifierExportBlock } = buildImportsAndRegistrations(metas, lazyReferencedClassKeys, providerModulePaths, options);
|
|
325
|
+
const envOverridesBlock = options?.isDev === void 0 ? "" : `\nsetEnvDetectionOverrides({ isDev: ${options.isDev} });\n`;
|
|
197
326
|
let providerImportBlock = "";
|
|
198
327
|
let providerInvocationBlock = "";
|
|
199
328
|
if (hasProviderModules) {
|
|
@@ -202,7 +331,7 @@ function generateContainerModule(metas, lazyReferencedClassKeys, providerModuleP
|
|
|
202
331
|
providerInvocationBlock = `\nconst providerDefinitions = [${aliasNames.join(", ")}];\nfor (const definition of providerDefinitions) {\n applyProviders(container, definition);\n}\n`;
|
|
203
332
|
}
|
|
204
333
|
return `
|
|
205
|
-
${runtimeImportStatement}${stubsBlock}
|
|
334
|
+
${runtimeImportStatement}${envOverridesBlock}${stubsBlock}
|
|
206
335
|
${providerImportBlock}
|
|
207
336
|
${registrationsBlock}
|
|
208
337
|
|
|
@@ -215,6 +344,11 @@ ${providerInvocationBlock}${identifierExportBlock}
|
|
|
215
344
|
export default container;
|
|
216
345
|
`;
|
|
217
346
|
}
|
|
347
|
+
const GENERATED_FILE_NOTICE = "This file was auto-generated by Alloy. Manual changes will be overwritten.";
|
|
348
|
+
const GENERATED_FILE_HEADER = `/**
|
|
349
|
+
* ${GENERATED_FILE_NOTICE}
|
|
350
|
+
*/
|
|
351
|
+
`;
|
|
218
352
|
/**
|
|
219
353
|
* Generates the TypeScript declaration definition (`.d.ts`) for the virtual container module.
|
|
220
354
|
* It exports the `ServiceIdentifiers` interface matching the runtime exports.
|
|
@@ -224,17 +358,22 @@ export default container;
|
|
|
224
358
|
*/
|
|
225
359
|
function generateContainerTypeDefinition(metas, pathResolver) {
|
|
226
360
|
const resolver = new IdentifierResolver(metas);
|
|
361
|
+
const pool = createNamePool([
|
|
362
|
+
"Container",
|
|
363
|
+
"ServiceIdentifier",
|
|
364
|
+
"container"
|
|
365
|
+
]);
|
|
227
366
|
const imports = [];
|
|
228
367
|
const interfaceMembers = [];
|
|
229
368
|
for (const meta of metas) {
|
|
230
|
-
const importName = resolver.resolve(meta.className, meta.filePath);
|
|
369
|
+
const importName = pool.claim(resolver.resolve(meta.className, meta.filePath));
|
|
231
370
|
const importPath = pathResolver(meta.filePath);
|
|
232
371
|
if (importName === meta.className) imports.push(`import { ${meta.className} } from '${importPath}';`);
|
|
233
372
|
else imports.push(`import { ${meta.className} as ${importName} } from '${importPath}';`);
|
|
234
373
|
const exportKey = createIdentifierExportKey(meta, resolver);
|
|
235
374
|
interfaceMembers.push(`${exportKey}: ServiceIdentifier<${importName}>;`);
|
|
236
375
|
}
|
|
237
|
-
return
|
|
376
|
+
return `${GENERATED_FILE_HEADER}
|
|
238
377
|
declare module "virtual:alloy-container" {
|
|
239
378
|
import { Container, ServiceIdentifier } from "alloy-di/runtime";
|
|
240
379
|
${imports.length ? imports.join("\n") + "\n" : ""}
|
|
@@ -258,7 +397,7 @@ declare module "virtual:alloy-container" {
|
|
|
258
397
|
* @param manifests - List of loaded manifest info (packageName and services).
|
|
259
398
|
*/
|
|
260
399
|
function generateManifestTypeDefinition(manifests) {
|
|
261
|
-
return manifests.map((m) => {
|
|
400
|
+
return GENERATED_FILE_HEADER + manifests.map((m) => {
|
|
262
401
|
const serviceIdentifiers = m.services.map((s) => ` export const ${s.exportName}Identifier: ServiceIdentifier;`).join("\n");
|
|
263
402
|
return `
|
|
264
403
|
declare module "${m.packageName}/manifest" {
|
|
@@ -349,4 +488,4 @@ ${serviceIdentifiers}
|
|
|
349
488
|
}).join("\n");
|
|
350
489
|
}
|
|
351
490
|
//#endregion
|
|
352
|
-
export { generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition };
|
|
491
|
+
export { GENERATED_FILE_NOTICE, generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition };
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { normalizeImportPath } from "
|
|
2
|
-
import { IdentifierResolver } from "
|
|
3
|
-
import { generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition } from "
|
|
1
|
+
import { normalizeImportPath, writeFileIfChanged } from "./utils.js";
|
|
2
|
+
import { IdentifierResolver } from "./identifier-resolver.js";
|
|
3
|
+
import { GENERATED_FILE_NOTICE, generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition } from "./codegen.js";
|
|
4
4
|
import { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest } from "./manifest-utils.js";
|
|
5
5
|
import { generateMermaidDiagram } from "./visualizer.js";
|
|
6
6
|
import { ensureDirectoryForFile } from "./visualization-utils.js";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fs from "node:fs";
|
|
9
|
-
//#region src/plugins/
|
|
9
|
+
//#region src/plugins/core/container-loader.ts
|
|
10
10
|
async function loadVirtualContainerModule(options) {
|
|
11
|
-
const metas = options.localMetas
|
|
11
|
+
const metas = options.localMetas.map((meta) => ({
|
|
12
|
+
...meta,
|
|
13
|
+
metadata: { ...meta.metadata }
|
|
14
|
+
}));
|
|
15
|
+
const lazyClassKeys = new Set(options.lazyReferencedClassKeys);
|
|
12
16
|
assignIdentifierKeys(metas, options.packageName, options.resolvedRoot);
|
|
13
17
|
const manifestData = await readManifests(options.manifests);
|
|
14
18
|
const manifestServices = manifestData.services;
|
|
@@ -24,14 +28,13 @@ async function loadVirtualContainerModule(options) {
|
|
|
24
28
|
}))];
|
|
25
29
|
const resolver = new IdentifierResolver(combinedMetas);
|
|
26
30
|
const metasByName = groupMetasByName(combinedMetas);
|
|
27
|
-
for (const svc of manifestServices) metas.push(toMetaFromManifest(svc, metasByName, resolver,
|
|
31
|
+
for (const svc of manifestServices) metas.push(toMetaFromManifest(svc, metasByName, resolver, lazyClassKeys));
|
|
28
32
|
const providerImports = Array.from(new Set([...options.providerImportPaths, ...manifestData.providers]));
|
|
29
|
-
|
|
30
|
-
reconcileLazySet(metas, options.lazyReferencedClassKeys, eagerReferencedNames);
|
|
33
|
+
reconcileLazySet(metas, lazyClassKeys, collectEagerReferencedNames(metas));
|
|
31
34
|
augmentFactoryLazyServices(metas, options.lazyServiceKeys);
|
|
32
|
-
const code = generateContainerModule(metas,
|
|
35
|
+
const code = generateContainerModule(metas, lazyClassKeys, providerImports, { isDev: options.isDevMode });
|
|
33
36
|
writeTypeDefinitions(metas, loadedManifests, options.resolvedRoot, options.containerDeclarationDir);
|
|
34
|
-
writeVisualizationArtifact(metas,
|
|
37
|
+
writeVisualizationArtifact(metas, lazyClassKeys, options.resolvedVisualization);
|
|
35
38
|
return {
|
|
36
39
|
code,
|
|
37
40
|
moduleType: "js"
|
|
@@ -63,13 +66,13 @@ function writeTypeDefinitions(metas, loadedManifests, resolvedRoot, containerDec
|
|
|
63
66
|
const dtsDir = path.resolve(resolvedRoot, containerDeclarationDir ?? "./src");
|
|
64
67
|
const dtsContent = generateContainerTypeDefinition(metas, (filePath) => resolveDeclarationImportPath(dtsDir, filePath));
|
|
65
68
|
if (!fs.existsSync(dtsDir)) fs.mkdirSync(dtsDir, { recursive: true });
|
|
66
|
-
|
|
69
|
+
writeFileIfChanged(path.join(dtsDir, "alloy-container.d.ts"), dtsContent);
|
|
67
70
|
if (loadedManifests.length === 0) return;
|
|
68
71
|
const manifestsDts = generateManifestTypeDefinition(loadedManifests.map((m) => ({
|
|
69
72
|
packageName: m.packageName,
|
|
70
73
|
services: m.services
|
|
71
74
|
})));
|
|
72
|
-
|
|
75
|
+
writeFileIfChanged(path.join(dtsDir, "alloy-manifests.d.ts"), manifestsDts);
|
|
73
76
|
}
|
|
74
77
|
function resolveDeclarationImportPath(dtsDir, filePath) {
|
|
75
78
|
if (!path.isAbsolute(filePath)) return filePath;
|
|
@@ -86,7 +89,7 @@ function writeVisualizationArtifact(metas, lazyReferencedClassKeys, resolvedVisu
|
|
|
86
89
|
options: resolvedVisualization.mermaidOptions
|
|
87
90
|
});
|
|
88
91
|
ensureDirectoryForFile(resolvedVisualization.outputPath);
|
|
89
|
-
|
|
92
|
+
writeFileIfChanged(resolvedVisualization.outputPath, `%% ${GENERATED_FILE_NOTICE}\n${artifact.diagram}\n`);
|
|
90
93
|
}
|
|
91
94
|
//#endregion
|
|
92
95
|
export { loadVirtualContainerModule };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createClassKey } from "
|
|
2
|
-
import { createDiscoveryStore } from "
|
|
3
|
-
//#region src/plugins/
|
|
1
|
+
import { createClassKey } from "./utils.js";
|
|
2
|
+
import { createDiscoveryStore } from "./discovery-store.js";
|
|
3
|
+
//#region src/plugins/core/discovery-runtime.ts
|
|
4
4
|
/** Files the discovery scanner processes (mirrors the transform hook filter). */
|
|
5
5
|
function isDiscoverableFile(file) {
|
|
6
6
|
return /\.tsx?$/i.test(file) && !/\.d\.ts$/i.test(file) && !file.includes("node_modules");
|
|
@@ -62,15 +62,5 @@ function createDiscoveryRuntime() {
|
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Invalidate the generated container module in every environment's module
|
|
67
|
-
* graph so its `load` hook re-runs and regenerates from current discovery.
|
|
68
|
-
*/
|
|
69
|
-
function invalidateContainerModule(server, resolvedVirtualModuleId) {
|
|
70
|
-
for (const environment of Object.values(server.environments)) {
|
|
71
|
-
const mod = environment.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
72
|
-
if (mod) environment.moduleGraph.invalidateModule(mod);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
65
|
//#endregion
|
|
76
|
-
export { createDiscoveryRuntime,
|
|
66
|
+
export { createDiscoveryRuntime, isDiscoverableFile };
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { scanSource } from "./scanner.js";
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
//#region src/plugins/core/discovery-store.ts
|
|
3
4
|
/**
|
|
4
5
|
* Maintains a per-file cache of discovered DI metadata, lazy references, and
|
|
5
6
|
* optionally source snapshots to drive incremental recompilation inside the
|
|
6
7
|
* Alloy discovery pipeline.
|
|
7
8
|
*/
|
|
9
|
+
function hashContent(code) {
|
|
10
|
+
return crypto.createHash("sha1").update(code).digest("hex");
|
|
11
|
+
}
|
|
8
12
|
/**
|
|
9
13
|
* Creates a file-scoped discovery store that caches scanner output and
|
|
10
14
|
* optionally the original source for diagnostics or incremental rebuilds.
|
|
@@ -15,6 +19,7 @@ import { scanSource } from "./scanner.js";
|
|
|
15
19
|
function createDiscoveryStore(options = {}) {
|
|
16
20
|
const fileMetas = /* @__PURE__ */ new Map();
|
|
17
21
|
const fileLazyRefs = /* @__PURE__ */ new Map();
|
|
22
|
+
const fileContentHashes = /* @__PURE__ */ new Map();
|
|
18
23
|
const fileSources = options.trackSources ? /* @__PURE__ */ new Map() : void 0;
|
|
19
24
|
/**
|
|
20
25
|
* Scan and cache the latest metadata for a file, returning both the fresh
|
|
@@ -26,6 +31,14 @@ function createDiscoveryStore(options = {}) {
|
|
|
26
31
|
function updateFile(id, code) {
|
|
27
32
|
const previousMetas = fileMetas.get(id);
|
|
28
33
|
const previousLazyClassKeys = fileLazyRefs.get(id);
|
|
34
|
+
const contentHash = hashContent(code);
|
|
35
|
+
if (fileContentHashes.get(id) === contentHash) return {
|
|
36
|
+
metas: previousMetas ?? [],
|
|
37
|
+
lazyClassKeys: new Set(previousLazyClassKeys),
|
|
38
|
+
previousMetas,
|
|
39
|
+
previousLazyClassKeys
|
|
40
|
+
};
|
|
41
|
+
fileContentHashes.set(id, contentHash);
|
|
29
42
|
if (fileSources) fileSources.set(id, code);
|
|
30
43
|
const { metas, lazyClassKeys } = scanSource(code, id);
|
|
31
44
|
if (metas.length) fileMetas.set(id, metas);
|
|
@@ -49,6 +62,7 @@ function createDiscoveryStore(options = {}) {
|
|
|
49
62
|
const previousLazyClassKeys = fileLazyRefs.get(id);
|
|
50
63
|
fileMetas.delete(id);
|
|
51
64
|
fileLazyRefs.delete(id);
|
|
65
|
+
fileContentHashes.delete(id);
|
|
52
66
|
if (fileSources) fileSources.delete(id);
|
|
53
67
|
return {
|
|
54
68
|
previousMetas,
|
|
@@ -61,6 +75,7 @@ function createDiscoveryStore(options = {}) {
|
|
|
61
75
|
function clear() {
|
|
62
76
|
fileMetas.clear();
|
|
63
77
|
fileLazyRefs.clear();
|
|
78
|
+
fileContentHashes.clear();
|
|
64
79
|
fileSources?.clear();
|
|
65
80
|
}
|
|
66
81
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createClassKey, createSymbolKey, normalizeImportPath } from "
|
|
1
|
+
import { createClassKey, createSymbolKey, normalizeImportPath } from "./utils.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
//#region src/plugins/
|
|
3
|
+
//#region src/plugins/core/manifest-utils.ts
|
|
4
4
|
/**
|
|
5
5
|
* Reads a list of manifest objects and returns aggregated service + provider module specifiers.
|
|
6
6
|
*
|
|
@@ -83,9 +83,16 @@ async function readManifests(inputs) {
|
|
|
83
83
|
function readManifestByVersion(manifest) {
|
|
84
84
|
return (manifest.schemaVersion ?? 1) === 2 ? readManifestV2(manifest) : readManifestV1(manifest);
|
|
85
85
|
}
|
|
86
|
+
function warnInvalidManifest(manifest, error) {
|
|
87
|
+
const packageName = typeof manifest.packageName === "string" && manifest.packageName ? manifest.packageName : "<unknown package>";
|
|
88
|
+
console.warn(`[alloy] Ignoring invalid manifest "${packageName}" — its services and providers will not be registered:\n${z.prettifyError(error)}`);
|
|
89
|
+
}
|
|
86
90
|
function readManifestV1(manifest) {
|
|
87
91
|
const parsed = manifestSchemaV1.safeParse(manifest);
|
|
88
|
-
if (!parsed.success)
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
warnInvalidManifest(manifest, parsed.error);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
89
96
|
return {
|
|
90
97
|
schemaVersion: 1,
|
|
91
98
|
packageName: parsed.data.packageName,
|
|
@@ -98,7 +105,10 @@ function readManifestV1(manifest) {
|
|
|
98
105
|
}
|
|
99
106
|
function readManifestV2(manifest) {
|
|
100
107
|
const parsed = manifestSchemaV2.safeParse(manifest);
|
|
101
|
-
if (!parsed.success)
|
|
108
|
+
if (!parsed.success) {
|
|
109
|
+
warnInvalidManifest(manifest, parsed.error);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
102
112
|
return {
|
|
103
113
|
schemaVersion: 2,
|
|
104
114
|
packageName: parsed.data.packageName,
|
|
@@ -36,7 +36,19 @@ function collectFileImports(sourceFile) {
|
|
|
36
36
|
}
|
|
37
37
|
return imports;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Cheap pre-filter that avoids the TS parse for files that cannot contribute
|
|
41
|
+
* discovery results: decorators require an `@` and lazy references require
|
|
42
|
+
* the `Lazy` identifier.
|
|
43
|
+
*/
|
|
44
|
+
function mayContainDiscoverableSyntax(code) {
|
|
45
|
+
return code.includes("@") || code.includes("Lazy");
|
|
46
|
+
}
|
|
39
47
|
function scanSource(code, id) {
|
|
48
|
+
if (!mayContainDiscoverableSyntax(code)) return {
|
|
49
|
+
metas: [],
|
|
50
|
+
lazyClassKeys: /* @__PURE__ */ new Set()
|
|
51
|
+
};
|
|
40
52
|
const sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.ESNext, true);
|
|
41
53
|
const discovered = /* @__PURE__ */ new Map();
|
|
42
54
|
const lazyRefs = /* @__PURE__ */ new Set();
|
|
@@ -80,7 +92,10 @@ function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache
|
|
|
80
92
|
const decorators = ts.getDecorators ? ts.getDecorators(node) : void 0;
|
|
81
93
|
if (!decorators?.length) return;
|
|
82
94
|
for (const decorator of decorators) {
|
|
83
|
-
if (!ts.isCallExpression(decorator.expression))
|
|
95
|
+
if (!ts.isCallExpression(decorator.expression)) {
|
|
96
|
+
warnOnBareAlloyDecorator(decorator, sourceFile, fileImports, id, resolutionCache);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
84
99
|
const decoratorName = resolveDecoratorName(decorator.expression.expression, fileImports, id, new Set([id]), resolutionCache);
|
|
85
100
|
if (decoratorName) return {
|
|
86
101
|
decoratorCall: decorator.expression,
|
|
@@ -88,6 +103,20 @@ function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache
|
|
|
88
103
|
};
|
|
89
104
|
}
|
|
90
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Warn when an alloy decorator is applied bare (`@Injectable` instead of
|
|
108
|
+
* `@Injectable()`). The scanner only registers call-expression decorators, so
|
|
109
|
+
* the service would silently vanish from the container — and at runtime the
|
|
110
|
+
* factory throws. Surfacing the location here makes the misuse findable at
|
|
111
|
+
* build time.
|
|
112
|
+
*/
|
|
113
|
+
function warnOnBareAlloyDecorator(decorator, sourceFile, fileImports, id, resolutionCache) {
|
|
114
|
+
if (!ts.isIdentifier(decorator.expression) && !ts.isPropertyAccessExpression(decorator.expression)) return;
|
|
115
|
+
if (!resolveDecoratorName(decorator.expression, fileImports, id, new Set([id]), resolutionCache)) return;
|
|
116
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(decorator.getStart(sourceFile));
|
|
117
|
+
const appliedText = decorator.expression.getText(sourceFile);
|
|
118
|
+
console.warn(`[alloy] ${id}:${line + 1} applies @${appliedText} without calling it — use @${appliedText}(). The class will not be registered.`);
|
|
119
|
+
}
|
|
91
120
|
function resolveDecoratorName(expression, fileImports, id, visitedModules, resolutionCache) {
|
|
92
121
|
if (ts.isIdentifier(expression)) {
|
|
93
122
|
const importInfo = fileImports.get(expression.text);
|
|
@@ -104,11 +133,11 @@ function resolveImportedDecorator(importPath, requestedName, fromId, visitedModu
|
|
|
104
133
|
if (importPath === ALLOY_RUNTIME_MODULE) return isAlloyDecoratorName(requestedName) ? requestedName : void 0;
|
|
105
134
|
if (!importPath.startsWith(".")) return;
|
|
106
135
|
for (const candidate of resolveModuleSpecifierCandidates(fromId, importPath)) {
|
|
107
|
-
if (visitedModules.has(candidate) || !fs.existsSync(candidate)) continue;
|
|
108
136
|
const cacheKey = `${candidate}:${requestedName}`;
|
|
109
137
|
const cached = resolutionCache.get(cacheKey);
|
|
110
138
|
if (cached) return cached;
|
|
111
139
|
if (cached === null) continue;
|
|
140
|
+
if (visitedModules.has(candidate) || !fs.existsSync(candidate)) continue;
|
|
112
141
|
visitedModules.add(candidate);
|
|
113
142
|
try {
|
|
114
143
|
const source = fs.readFileSync(candidate, "utf8");
|
|
@@ -18,7 +18,7 @@ function ensureLeadingSlash(value) {
|
|
|
18
18
|
}
|
|
19
19
|
function hashString(value) {
|
|
20
20
|
let hash = 0;
|
|
21
|
-
for (let i = 0; i < value.length; i++) hash =
|
|
21
|
+
for (let i = 0; i < value.length; i++) hash = hash * 31 + value.charCodeAt(i) | 0;
|
|
22
22
|
return Math.abs(hash).toString(36);
|
|
23
23
|
}
|
|
24
24
|
function createClassKey(filePath, className) {
|
|
@@ -30,14 +30,51 @@ function createAliasName(className, filePath) {
|
|
|
30
30
|
function createSymbolKey(filePath, className) {
|
|
31
31
|
return `alloy:${normalizeImportPath(filePath)}#${className}`;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
const lastWrittenContent = /* @__PURE__ */ new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Write a generated artifact only when its content actually changed.
|
|
36
|
+
*
|
|
37
|
+
* @returns true when the file was written, false when it already matched.
|
|
38
|
+
*/
|
|
39
|
+
function writeFileIfChanged(filePath, content) {
|
|
40
|
+
if (lastWrittenContent.get(filePath) === content) return false;
|
|
41
|
+
try {
|
|
42
|
+
if (fs.readFileSync(filePath, "utf-8") === content) {
|
|
43
|
+
lastWrittenContent.set(filePath, content);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
fs.writeFileSync(filePath, content);
|
|
48
|
+
lastWrittenContent.set(filePath, content);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
function walkSync(dir, fileList = [], visitedDirs) {
|
|
52
|
+
const visited = visitedDirs ?? /* @__PURE__ */ new Set();
|
|
53
|
+
let realDir;
|
|
54
|
+
try {
|
|
55
|
+
realDir = fs.realpathSync(dir);
|
|
56
|
+
} catch {
|
|
57
|
+
return fileList;
|
|
58
|
+
}
|
|
59
|
+
if (visited.has(realDir)) return fileList;
|
|
60
|
+
visited.add(realDir);
|
|
61
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
62
|
+
if (entry.name.startsWith(".")) continue;
|
|
63
|
+
const filePath = path.join(dir, entry.name);
|
|
64
|
+
if (entry.isDirectory()) walkSync(filePath, fileList, visited);
|
|
65
|
+
else if (entry.isFile()) fileList.push(filePath);
|
|
66
|
+
else if (entry.isSymbolicLink()) {
|
|
67
|
+
let stat;
|
|
68
|
+
try {
|
|
69
|
+
stat = fs.statSync(filePath);
|
|
70
|
+
} catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (stat.isDirectory()) walkSync(filePath, fileList, visited);
|
|
74
|
+
else if (stat.isFile()) fileList.push(filePath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
40
77
|
return fileList;
|
|
41
78
|
}
|
|
42
79
|
//#endregion
|
|
43
|
-
export { createAliasName, createClassKey, createSymbolKey, hashString, normalizeImportPath, walkSync };
|
|
80
|
+
export { createAliasName, createClassKey, createSymbolKey, hashString, normalizeImportPath, walkSync, writeFileIfChanged };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MermaidDiagramOptions } from "./visualizer.js";
|
|
2
2
|
|
|
3
|
-
//#region src/plugins/
|
|
3
|
+
//#region src/plugins/core/visualization-utils.d.ts
|
|
4
4
|
interface AlloyMermaidVisualizerOptions extends MermaidDiagramOptions {
|
|
5
5
|
outputPath?: string;
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
//#region src/plugins/
|
|
3
|
+
//#region src/plugins/core/visualization-utils.ts
|
|
4
4
|
const DEFAULT_MERMAID_FILENAME = "alloy-di.mmd";
|
|
5
5
|
function resolveVisualizationOptions(input, projectRoot) {
|
|
6
6
|
if (!input) return null;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { createClassKey, createSymbolKey, hashString, normalizeImportPath } from "
|
|
1
|
+
import { createClassKey, createSymbolKey, hashString, normalizeImportPath } from "./utils.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
//#region src/plugins/
|
|
3
|
+
//#region src/plugins/core/visualizer.ts
|
|
4
4
|
const DEFAULT_SCOPE_COLORS = {
|
|
5
|
-
singleton: "#
|
|
6
|
-
transient: "#
|
|
5
|
+
singleton: "#3b6ea5",
|
|
6
|
+
transient: "#2a7d73"
|
|
7
7
|
};
|
|
8
8
|
const DEFAULT_OPTIONS = {
|
|
9
9
|
direction: "LR",
|
|
10
10
|
includeLegend: true,
|
|
11
11
|
scopeColors: DEFAULT_SCOPE_COLORS,
|
|
12
|
-
lazyNodeFill: "#
|
|
13
|
-
factoryNodeFill: "#
|
|
14
|
-
tokenNodeFill: "#
|
|
15
|
-
nodeStrokeColor: "#
|
|
16
|
-
nodeTextColor: "#
|
|
17
|
-
lazyEdgeColor: "#
|
|
18
|
-
eagerEdgeColor: "#
|
|
19
|
-
factoryEdgeColor: "#
|
|
12
|
+
lazyNodeFill: "#6c5cb8",
|
|
13
|
+
factoryNodeFill: "#9c6516",
|
|
14
|
+
tokenNodeFill: "#4b5c6b",
|
|
15
|
+
nodeStrokeColor: "#5a7488",
|
|
16
|
+
nodeTextColor: "#ffffff",
|
|
17
|
+
lazyEdgeColor: "#9385d6",
|
|
18
|
+
eagerEdgeColor: "#7c93a6",
|
|
19
|
+
factoryEdgeColor: "#c2922e"
|
|
20
20
|
};
|
|
21
21
|
const RESERVED_IDENTIFIERS = new Set([
|
|
22
22
|
"Lazy",
|
|
@@ -95,7 +95,7 @@ function generateMermaidDiagram({ metas, lazyClassKeys, options }) {
|
|
|
95
95
|
edges.push({
|
|
96
96
|
from: sourceNode,
|
|
97
97
|
to: target,
|
|
98
|
-
label: describeEdge(sourceNode, target
|
|
98
|
+
label: describeEdge(sourceNode, target),
|
|
99
99
|
isLazy: dep.isLazy,
|
|
100
100
|
stroke: selectEdgeColor(dep.isLazy, target, mergedOptions)
|
|
101
101
|
});
|
|
@@ -106,6 +106,7 @@ function generateMermaidDiagram({ metas, lazyClassKeys, options }) {
|
|
|
106
106
|
if (mergedOptions.includeLegend) {
|
|
107
107
|
lines.push(` %% Legend: singleton=${mergedOptions.scopeColors.singleton}, transient=${mergedOptions.scopeColors.transient}, lazy-only=${mergedOptions.lazyNodeFill}, factory=${mergedOptions.factoryNodeFill}, token=${mergedOptions.tokenNodeFill}`);
|
|
108
108
|
lines.push(` %% Edge colors: eager=${mergedOptions.eagerEdgeColor}, lazy=${mergedOptions.lazyEdgeColor}, factory=${mergedOptions.factoryEdgeColor}`);
|
|
109
|
+
lines.push(` %% Edge labels: Si=singleton, Tr=transient, Tk=token; solid=eager, dotted=lazy`);
|
|
109
110
|
}
|
|
110
111
|
const allNodes = [...serviceNodes, ...Array.from(tokenNodes.values())];
|
|
111
112
|
for (const node of allNodes) {
|
|
@@ -155,11 +156,18 @@ function sanitizeMermaidId(source, fallbackIndex) {
|
|
|
155
156
|
function escapeMermaidLabel(label) {
|
|
156
157
|
return label.replaceAll("\"", "\\\"").replaceAll("|", "/");
|
|
157
158
|
}
|
|
159
|
+
/** Single-token code for a node's scope/kind: Si, Tr, or Tk (token). */
|
|
160
|
+
function scopeCode(node) {
|
|
161
|
+
if (node.type === "token") return "Tk";
|
|
162
|
+
return node.scope === "transient" ? "Tr" : "Si";
|
|
163
|
+
}
|
|
158
164
|
/**
|
|
159
|
-
* Builds a
|
|
165
|
+
* Builds a compact edge label as a `source→target` scope transition (e.g.
|
|
166
|
+
* `Si→Tr`). Eager/lazy is conveyed by the arrow style and the target kind by
|
|
167
|
+
* node color, so they are intentionally omitted from the text. See the legend.
|
|
160
168
|
*/
|
|
161
|
-
function describeEdge(from, to
|
|
162
|
-
return `${
|
|
169
|
+
function describeEdge(from, to) {
|
|
170
|
+
return `${scopeCode(from)}→${scopeCode(to)}`;
|
|
163
171
|
}
|
|
164
172
|
/**
|
|
165
173
|
* Determines the fill color for a node based on its type, scope, and lazy/factory flags.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ServiceIdentifier } from "../../lib/service-identifiers.js";
|
|
2
2
|
import { AlloyManifest } from "../core/types.js";
|
|
3
|
-
import { AlloyMermaidVisualizerOptions, AlloyVisualizationOptions } from "
|
|
3
|
+
import { AlloyMermaidVisualizerOptions, AlloyVisualizationOptions } from "../core/visualization-utils.js";
|
|
4
4
|
import { Plugin } from "vite";
|
|
5
5
|
|
|
6
6
|
//#region src/plugins/vite-plugin/index.d.ts
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { normalizeImportPath, walkSync } from "../core/utils.js";
|
|
2
|
-
import { resolveVisualizationOptions } from "
|
|
3
|
-
import { loadVirtualContainerModule } from "
|
|
4
|
-
import { createDiscoveryRuntime,
|
|
2
|
+
import { resolveVisualizationOptions } from "../core/visualization-utils.js";
|
|
3
|
+
import { loadVirtualContainerModule } from "../core/container-loader.js";
|
|
4
|
+
import { createDiscoveryRuntime, isDiscoverableFile } from "../core/discovery-runtime.js";
|
|
5
|
+
import { invalidateContainerModule } from "./module-invalidation.js";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
//#region src/plugins/vite-plugin/index.ts
|
|
@@ -22,6 +23,7 @@ function alloy(options = {}) {
|
|
|
22
23
|
let resolvedRoot = process.cwd();
|
|
23
24
|
let packageName = "UNKNOWN_PACKAGE";
|
|
24
25
|
let resolvedVisualization = null;
|
|
26
|
+
let isDevMode;
|
|
25
27
|
const lazyServiceKeys = new Set((options.lazyServices ?? []).map(toLazyServiceKey));
|
|
26
28
|
const discoveryRuntime = createDiscoveryRuntime();
|
|
27
29
|
return {
|
|
@@ -29,6 +31,7 @@ function alloy(options = {}) {
|
|
|
29
31
|
enforce: "pre",
|
|
30
32
|
configResolved(config) {
|
|
31
33
|
resolvedRoot = config.root ?? process.cwd();
|
|
34
|
+
isDevMode = !config.isProduction;
|
|
32
35
|
try {
|
|
33
36
|
const pkgPath = path.resolve(resolvedRoot, "package.json");
|
|
34
37
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
@@ -102,7 +105,8 @@ function alloy(options = {}) {
|
|
|
102
105
|
packageName,
|
|
103
106
|
resolvedRoot,
|
|
104
107
|
containerDeclarationDir: options.containerDeclarationDir,
|
|
105
|
-
resolvedVisualization
|
|
108
|
+
resolvedVisualization,
|
|
109
|
+
isDevMode
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
112
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/plugins/vite-plugin/module-invalidation.ts
|
|
2
|
+
/**
|
|
3
|
+
* Invalidate the generated container module in every environment's module
|
|
4
|
+
* graph so its `load` hook re-runs and regenerates from current discovery.
|
|
5
|
+
*/
|
|
6
|
+
function invalidateContainerModule(server, resolvedVirtualModuleId) {
|
|
7
|
+
for (const environment of Object.values(server.environments)) {
|
|
8
|
+
const mod = environment.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
9
|
+
if (mod) environment.moduleGraph.invalidateModule(mod);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { invalidateContainerModule };
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Newable, Token, createToken } from "./lib/types.js";
|
|
2
2
|
import { ServiceIdentifier, clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier } from "./lib/service-identifiers.js";
|
|
3
3
|
import { Container } from "./lib/container.js";
|
|
4
|
+
import { EnvDetectionOverrides, setEnvDetectionOverrides } from "./lib/env-detection.js";
|
|
4
5
|
import { LAZY_IDENTIFIER, Lazy } from "./lib/lazy.js";
|
|
5
6
|
import { Injectable, Singleton, assertDeps, dependenciesRegistry, deps } from "./lib/decorators.js";
|
|
6
7
|
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 };
|
|
8
|
+
export { Container, type EnvDetectionOverrides, 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, setEnvDetectionOverrides };
|
package/dist/runtime.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createToken } from "./lib/types.js";
|
|
|
2
2
|
import { LAZY_IDENTIFIER, Lazy } from "./lib/lazy.js";
|
|
3
3
|
import { Injectable, Singleton, assertDeps, dependenciesRegistry, deps } from "./lib/decorators.js";
|
|
4
4
|
import { clearServiceIdentifierRegistry, getConstructorByIdentifier, getServiceIdentifier, registerServiceIdentifier } from "./lib/service-identifiers.js";
|
|
5
|
+
import { setEnvDetectionOverrides } from "./lib/env-detection.js";
|
|
5
6
|
import { Container } from "./lib/container.js";
|
|
6
7
|
import { applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle } from "./lib/providers.js";
|
|
7
|
-
export { Container, Injectable, LAZY_IDENTIFIER, Lazy, Singleton, applyProviders, asClass, asLazyClass, asValue, assertDeps, clearServiceIdentifierRegistry, createToken, defineProviders, dependenciesRegistry, deps, getConstructorByIdentifier, getServiceIdentifier, lifecycle, registerServiceIdentifier };
|
|
8
|
+
export { Container, Injectable, LAZY_IDENTIFIER, Lazy, Singleton, applyProviders, asClass, asLazyClass, asValue, assertDeps, clearServiceIdentifierRegistry, createToken, defineProviders, dependenciesRegistry, deps, getConstructorByIdentifier, getServiceIdentifier, lifecycle, registerServiceIdentifier, setEnvDetectionOverrides };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../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/
|
|
1
|
+
{"root":["../src/entry-points.test.ts","../src/fixture-subpaths.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/container-loader.test.ts","../src/plugins/core/container-loader.ts","../src/plugins/core/decorators.helpers.test.ts","../src/plugins/core/decorators.ts","../src/plugins/core/discovery-runtime.ts","../src/plugins/core/discovery-store.test.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/manifest-utils.ts","../src/plugins/core/manifest-utils.validation.test.ts","../src/plugins/core/scanner.test.ts","../src/plugins/core/scanner.ts","../src/plugins/core/types.ts","../src/plugins/core/utils.ts","../src/plugins/core/utils.walk.test.ts","../src/plugins/core/utils.write.test.ts","../src/plugins/core/visualization-utils.ts","../src/plugins/core/visualizer.test.ts","../src/plugins/core/visualizer.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/duplicate-registrations.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/module-generation.test.ts","../src/plugins/vite-plugin/module-invalidation.ts","../src/plugins/vite-plugin/test-utils.ts","../src/plugins/vite-plugin/transform-guards.test.ts","../src/plugins/vite-plugin/visualization-option.test.ts"],"version":"6.0.3"}
|