alloy-di 1.2.2 → 1.2.3
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/service-identifiers.d.ts +5 -2
- package/dist/lib/service-identifiers.js +6 -2
- package/dist/plugins/core/codegen.js +118 -47
- package/dist/plugins/core/scanner.js +19 -2
- package/dist/plugins/core/utils.js +1 -1
- package/dist/plugins/vite-plugin/container-loader.js +9 -6
- 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
|
}
|
|
@@ -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
|
}
|
|
@@ -18,30 +18,73 @@ function createSymbolDescription(meta) {
|
|
|
18
18
|
return createSymbolKey(meta.filePath, meta.className);
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
+
* Allocates module-local names from a single shared pool so the generated
|
|
22
|
+
* module's independent naming domains (runtime helpers, service imports,
|
|
23
|
+
* factory-lazy stubs, dependency imports, identifier consts, generated
|
|
24
|
+
* locals) can never collide.
|
|
25
|
+
*/
|
|
26
|
+
function createNamePool(reservedNames) {
|
|
27
|
+
const used = new Set(reservedNames);
|
|
28
|
+
return { claim(base) {
|
|
29
|
+
let candidate = base;
|
|
30
|
+
let suffix = 1;
|
|
31
|
+
while (used.has(candidate)) {
|
|
32
|
+
candidate = `${base}_${suffix}`;
|
|
33
|
+
suffix++;
|
|
34
|
+
}
|
|
35
|
+
used.add(candidate);
|
|
36
|
+
return candidate;
|
|
37
|
+
} };
|
|
38
|
+
}
|
|
39
|
+
function stripModuleExtension(importPath) {
|
|
40
|
+
return importPath.replace(/\.[cm]?[jt]sx?$/i, "");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Identity of an imported binding: the module (extension-insensitive, since
|
|
44
|
+
* references may resolve extensionless while service paths keep theirs) plus
|
|
45
|
+
* the export name.
|
|
46
|
+
*/
|
|
47
|
+
function createBindingKey(importPath, exportName) {
|
|
48
|
+
return `${stripModuleExtension(importPath)}::${exportName}`;
|
|
49
|
+
}
|
|
50
|
+
/** Resolves a referenced import's specifier to a normalized module path. */
|
|
51
|
+
function resolveReferencePath(ref, meta) {
|
|
52
|
+
return normalizeImportPath(ref.path.startsWith(".") ? path.resolve(path.dirname(meta.filePath), ref.path) : ref.path);
|
|
53
|
+
}
|
|
54
|
+
function resolveDependencyImport(ref, normalizedPath, pool, serviceBindings, runtimeImports) {
|
|
55
|
+
if (normalizedPath === "alloy-di/runtime" && ref.originalName && ref.name === ref.originalName && runtimeImports.has(ref.originalName)) return {
|
|
56
|
+
localName: ref.originalName,
|
|
57
|
+
importPath: normalizedPath,
|
|
58
|
+
originalName: ref.originalName,
|
|
59
|
+
reusesExistingBinding: true
|
|
60
|
+
};
|
|
61
|
+
const serviceLocalName = serviceBindings.get(createBindingKey(normalizedPath, ref.originalName ?? "default"));
|
|
62
|
+
if (serviceLocalName) return {
|
|
63
|
+
localName: serviceLocalName,
|
|
64
|
+
importPath: normalizedPath,
|
|
65
|
+
originalName: ref.originalName,
|
|
66
|
+
reusesExistingBinding: true
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
localName: pool.claim(ref.name),
|
|
70
|
+
importPath: normalizedPath,
|
|
71
|
+
originalName: ref.originalName
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
21
75
|
* Analyzes dependencies across all discovered services and resolves imports.
|
|
22
|
-
* Deduplicates imports
|
|
76
|
+
* Deduplicates imports by binding identity, reuses bindings the module
|
|
77
|
+
* already declares (runtime helpers, service imports, factory-lazy stubs),
|
|
78
|
+
* and claims fresh local names from the shared pool otherwise.
|
|
23
79
|
*/
|
|
24
|
-
function resolveDependencyImports(metas) {
|
|
80
|
+
function resolveDependencyImports(metas, pool, serviceBindings, runtimeImports) {
|
|
25
81
|
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
|
-
}
|
|
82
|
+
for (const meta of metas) for (const ref of meta.referencedImports ?? []) {
|
|
83
|
+
if (ref.isTypeOnly) continue;
|
|
84
|
+
const normalizedPath = resolveReferencePath(ref, meta);
|
|
85
|
+
const key = createBindingKey(normalizedPath, ref.originalName ?? "default");
|
|
86
|
+
if (importMap.has(key)) continue;
|
|
87
|
+
importMap.set(key, resolveDependencyImport(ref, normalizedPath, pool, serviceBindings, runtimeImports));
|
|
45
88
|
}
|
|
46
89
|
return {
|
|
47
90
|
dependencyImports: Array.from(importMap.values()),
|
|
@@ -60,7 +103,7 @@ function reconstructDependencyExpression(dep, rewriter, contextDir) {
|
|
|
60
103
|
});
|
|
61
104
|
return expr;
|
|
62
105
|
}
|
|
63
|
-
function reconstructOptionsText(meta, importMap) {
|
|
106
|
+
function reconstructOptionsText(meta, importMap, serviceRenames) {
|
|
64
107
|
const { scope, dependencies, factory } = meta.metadata;
|
|
65
108
|
const parts = [];
|
|
66
109
|
if (factory) {
|
|
@@ -73,12 +116,11 @@ function reconstructOptionsText(meta, importMap) {
|
|
|
73
116
|
return reconstructDependencyExpression(dep, (ident) => {
|
|
74
117
|
const ref = meta.referencedImports?.find((r) => r.name === ident && !r.isTypeOnly);
|
|
75
118
|
if (ref) {
|
|
76
|
-
const
|
|
77
|
-
const key = `${normalizeImportPath(ref.path.startsWith(".") ? path.resolve(dir, ref.path) : ref.path)}::${ref.originalName ?? "default"}`;
|
|
119
|
+
const key = createBindingKey(resolveReferencePath(ref, meta), ref.originalName ?? "default");
|
|
78
120
|
const resolved = importMap.get(key);
|
|
79
121
|
return resolved ? resolved.localName : ident;
|
|
80
122
|
}
|
|
81
|
-
return ident;
|
|
123
|
+
return serviceRenames.get(ident) ?? ident;
|
|
82
124
|
}, path.dirname(meta.filePath));
|
|
83
125
|
});
|
|
84
126
|
parts.push(`dependencies: () => [${depExprs.join(", ")}]`);
|
|
@@ -86,13 +128,38 @@ function reconstructOptionsText(meta, importMap) {
|
|
|
86
128
|
if (parts.length === 0) return "{}";
|
|
87
129
|
return `{ ${parts.join(", ")} }`;
|
|
88
130
|
}
|
|
89
|
-
function buildImportsAndRegistrations(metas, lazyReferencedClassKeys,
|
|
131
|
+
function buildImportsAndRegistrations(metas, lazyReferencedClassKeys, providerModulePaths) {
|
|
132
|
+
const hasProviderModules = providerModulePaths.length > 0;
|
|
90
133
|
const activeMetas = filterActiveMetas(metas, lazyReferencedClassKeys);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
134
|
+
const resolver = new IdentifierResolver(activeMetas);
|
|
135
|
+
const runtimeImports = computeRuntimeImports(activeMetas, hasProviderModules);
|
|
136
|
+
const pool = createNamePool([
|
|
137
|
+
...runtimeImports,
|
|
138
|
+
"registrations",
|
|
139
|
+
"container",
|
|
140
|
+
"providerDefinitions",
|
|
141
|
+
...providerModulePaths.map((_, idx) => `providers_${idx}`)
|
|
142
|
+
]);
|
|
143
|
+
const serviceNames = /* @__PURE__ */ new Map();
|
|
144
|
+
const serviceRenames = /* @__PURE__ */ new Map();
|
|
145
|
+
const serviceBindings = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const meta of activeMetas) {
|
|
147
|
+
const baseName = resolver.resolve(meta.className, meta.filePath);
|
|
148
|
+
const name = pool.claim(baseName);
|
|
149
|
+
serviceNames.set(meta, name);
|
|
150
|
+
if (name !== baseName) serviceRenames.set(baseName, name);
|
|
151
|
+
serviceBindings.set(createBindingKey(getServiceImportPath(meta), meta.className), name);
|
|
152
|
+
}
|
|
153
|
+
const { dependencyImports, importMap } = resolveDependencyImports(activeMetas, pool, serviceBindings, runtimeImports);
|
|
154
|
+
const resolvedRegistrations = enrichRegistrations(activeMetas, {
|
|
155
|
+
resolver,
|
|
156
|
+
serviceNames,
|
|
157
|
+
serviceRenames,
|
|
158
|
+
importMap,
|
|
159
|
+
pool
|
|
160
|
+
});
|
|
94
161
|
const runtimeImportStatement = formatRuntimeImportStatement(runtimeImports);
|
|
95
|
-
const stubsBlock = createStubBlock(dependencyImports, resolvedRegistrations
|
|
162
|
+
const stubsBlock = createStubBlock(dependencyImports, resolvedRegistrations);
|
|
96
163
|
return {
|
|
97
164
|
runtimeImportStatement,
|
|
98
165
|
registrationsBlock: createRegistrationsBlock(buildRegistrationEntries(resolvedRegistrations)),
|
|
@@ -103,13 +170,14 @@ function buildImportsAndRegistrations(metas, lazyReferencedClassKeys, hasProvide
|
|
|
103
170
|
function filterActiveMetas(metas, lazyReferencedClassKeys) {
|
|
104
171
|
return metas.filter((meta) => !lazyReferencedClassKeys.has(createClassKey(meta.filePath, meta.className)));
|
|
105
172
|
}
|
|
106
|
-
function enrichRegistrations(activeMetas,
|
|
173
|
+
function enrichRegistrations(activeMetas, naming) {
|
|
174
|
+
const { resolver, serviceNames, serviceRenames, importMap, pool } = naming;
|
|
107
175
|
return activeMetas.map((meta) => {
|
|
108
|
-
const importName =
|
|
109
|
-
const identifierConst = `${importName}Identifier
|
|
176
|
+
const importName = serviceNames.get(meta) ?? meta.className;
|
|
177
|
+
const identifierConst = pool.claim(`${importName}Identifier`);
|
|
110
178
|
const exportKey = createIdentifierExportKey(meta, resolver);
|
|
111
179
|
const symbolDescription = meta.identifierKey ?? createSymbolDescription(meta);
|
|
112
|
-
const optionsText = reconstructOptionsText(meta, importMap);
|
|
180
|
+
const optionsText = reconstructOptionsText(meta, importMap, serviceRenames);
|
|
113
181
|
return {
|
|
114
182
|
...meta,
|
|
115
183
|
importName,
|
|
@@ -121,34 +189,29 @@ function enrichRegistrations(activeMetas, resolver, importMap) {
|
|
|
121
189
|
};
|
|
122
190
|
});
|
|
123
191
|
}
|
|
124
|
-
function computeRuntimeImports(
|
|
192
|
+
function computeRuntimeImports(activeMetas, hasProviderModules) {
|
|
125
193
|
const imports = new Set(["Container", "dependenciesRegistry"]);
|
|
126
|
-
const needsLazyImport =
|
|
194
|
+
const needsLazyImport = activeMetas.some((m) => m.metadata.dependencies.some((d) => d.isLazy) || !!m.metadata.factory);
|
|
127
195
|
if (hasProviderModules) imports.add("applyProviders");
|
|
128
196
|
if (needsLazyImport) imports.add("Lazy");
|
|
129
|
-
if (
|
|
197
|
+
if (activeMetas.length) imports.add("registerServiceIdentifier");
|
|
130
198
|
return imports;
|
|
131
199
|
}
|
|
132
200
|
function formatRuntimeImportStatement(imports) {
|
|
133
201
|
return `\nimport { ${Array.from(imports).join(", ")} } from 'alloy-di/runtime';\n`;
|
|
134
202
|
}
|
|
135
|
-
function createStubBlock(dependencyImports, registrations
|
|
203
|
+
function createStubBlock(dependencyImports, registrations) {
|
|
136
204
|
const statements = [];
|
|
137
|
-
const importedNames = new Set(runtimeImports);
|
|
138
205
|
for (const dep of dependencyImports) {
|
|
139
|
-
if (dep.
|
|
140
|
-
if (importedNames.has(dep.localName)) continue;
|
|
206
|
+
if (dep.reusesExistingBinding) continue;
|
|
141
207
|
statements.push(createDependencyImportStatement(dep));
|
|
142
|
-
importedNames.add(dep.localName);
|
|
143
208
|
}
|
|
144
209
|
for (const meta of registrations) {
|
|
145
210
|
if (meta.isFactoryLazy) {
|
|
146
211
|
statements.push(`class ${meta.importName} {}`);
|
|
147
212
|
continue;
|
|
148
213
|
}
|
|
149
|
-
if (importedNames.has(meta.importName)) continue;
|
|
150
214
|
statements.push(createServiceImportStatement(meta));
|
|
151
|
-
importedNames.add(meta.importName);
|
|
152
215
|
}
|
|
153
216
|
return statements.length ? `${statements.join("\n")}\n` : "";
|
|
154
217
|
}
|
|
@@ -158,8 +221,11 @@ function createDependencyImportStatement(dep) {
|
|
|
158
221
|
if (dep.originalName && dep.originalName !== dep.localName) return `import { ${dep.originalName} as ${dep.localName} } from '${dep.importPath}';`;
|
|
159
222
|
return `import { ${dep.localName} } from '${dep.importPath}';`;
|
|
160
223
|
}
|
|
224
|
+
function getServiceImportPath(meta) {
|
|
225
|
+
return !/^(\/|[A-Za-z]:\\|\.|~)/.test(meta.filePath) && !meta.filePath.includes("\\") ? meta.filePath : normalizeImportPath(meta.filePath);
|
|
226
|
+
}
|
|
161
227
|
function createServiceImportStatement(meta) {
|
|
162
|
-
const importPath =
|
|
228
|
+
const importPath = getServiceImportPath(meta);
|
|
163
229
|
if (meta.importName === meta.className) return `import { ${meta.className} } from '${importPath}';`;
|
|
164
230
|
return `import { ${meta.className} as ${meta.importName} } from '${importPath}';`;
|
|
165
231
|
}
|
|
@@ -193,7 +259,7 @@ function createIdentifierExports(registrations) {
|
|
|
193
259
|
*/
|
|
194
260
|
function generateContainerModule(metas, lazyReferencedClassKeys, providerModulePaths) {
|
|
195
261
|
const hasProviderModules = providerModulePaths.length > 0;
|
|
196
|
-
const { runtimeImportStatement, registrationsBlock, stubsBlock, identifierExportBlock } = buildImportsAndRegistrations(metas, lazyReferencedClassKeys,
|
|
262
|
+
const { runtimeImportStatement, registrationsBlock, stubsBlock, identifierExportBlock } = buildImportsAndRegistrations(metas, lazyReferencedClassKeys, providerModulePaths);
|
|
197
263
|
let providerImportBlock = "";
|
|
198
264
|
let providerInvocationBlock = "";
|
|
199
265
|
if (hasProviderModules) {
|
|
@@ -224,10 +290,15 @@ export default container;
|
|
|
224
290
|
*/
|
|
225
291
|
function generateContainerTypeDefinition(metas, pathResolver) {
|
|
226
292
|
const resolver = new IdentifierResolver(metas);
|
|
293
|
+
const pool = createNamePool([
|
|
294
|
+
"Container",
|
|
295
|
+
"ServiceIdentifier",
|
|
296
|
+
"container"
|
|
297
|
+
]);
|
|
227
298
|
const imports = [];
|
|
228
299
|
const interfaceMembers = [];
|
|
229
300
|
for (const meta of metas) {
|
|
230
|
-
const importName = resolver.resolve(meta.className, meta.filePath);
|
|
301
|
+
const importName = pool.claim(resolver.resolve(meta.className, meta.filePath));
|
|
231
302
|
const importPath = pathResolver(meta.filePath);
|
|
232
303
|
if (importName === meta.className) imports.push(`import { ${meta.className} } from '${importPath}';`);
|
|
233
304
|
else imports.push(`import { ${meta.className} as ${importName} } from '${importPath}';`);
|
|
@@ -80,7 +80,10 @@ function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache
|
|
|
80
80
|
const decorators = ts.getDecorators ? ts.getDecorators(node) : void 0;
|
|
81
81
|
if (!decorators?.length) return;
|
|
82
82
|
for (const decorator of decorators) {
|
|
83
|
-
if (!ts.isCallExpression(decorator.expression))
|
|
83
|
+
if (!ts.isCallExpression(decorator.expression)) {
|
|
84
|
+
warnOnBareAlloyDecorator(decorator, sourceFile, fileImports, id, resolutionCache);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
84
87
|
const decoratorName = resolveDecoratorName(decorator.expression.expression, fileImports, id, new Set([id]), resolutionCache);
|
|
85
88
|
if (decoratorName) return {
|
|
86
89
|
decoratorCall: decorator.expression,
|
|
@@ -88,6 +91,20 @@ function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache
|
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Warn when an alloy decorator is applied bare (`@Injectable` instead of
|
|
96
|
+
* `@Injectable()`). The scanner only registers call-expression decorators, so
|
|
97
|
+
* the service would silently vanish from the container — and at runtime the
|
|
98
|
+
* factory throws. Surfacing the location here makes the misuse findable at
|
|
99
|
+
* build time.
|
|
100
|
+
*/
|
|
101
|
+
function warnOnBareAlloyDecorator(decorator, sourceFile, fileImports, id, resolutionCache) {
|
|
102
|
+
if (!ts.isIdentifier(decorator.expression) && !ts.isPropertyAccessExpression(decorator.expression)) return;
|
|
103
|
+
if (!resolveDecoratorName(decorator.expression, fileImports, id, new Set([id]), resolutionCache)) return;
|
|
104
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(decorator.getStart(sourceFile));
|
|
105
|
+
const appliedText = decorator.expression.getText(sourceFile);
|
|
106
|
+
console.warn(`[alloy] ${id}:${line + 1} applies @${appliedText} without calling it — use @${appliedText}(). The class will not be registered.`);
|
|
107
|
+
}
|
|
91
108
|
function resolveDecoratorName(expression, fileImports, id, visitedModules, resolutionCache) {
|
|
92
109
|
if (ts.isIdentifier(expression)) {
|
|
93
110
|
const importInfo = fileImports.get(expression.text);
|
|
@@ -104,11 +121,11 @@ function resolveImportedDecorator(importPath, requestedName, fromId, visitedModu
|
|
|
104
121
|
if (importPath === ALLOY_RUNTIME_MODULE) return isAlloyDecoratorName(requestedName) ? requestedName : void 0;
|
|
105
122
|
if (!importPath.startsWith(".")) return;
|
|
106
123
|
for (const candidate of resolveModuleSpecifierCandidates(fromId, importPath)) {
|
|
107
|
-
if (visitedModules.has(candidate) || !fs.existsSync(candidate)) continue;
|
|
108
124
|
const cacheKey = `${candidate}:${requestedName}`;
|
|
109
125
|
const cached = resolutionCache.get(cacheKey);
|
|
110
126
|
if (cached) return cached;
|
|
111
127
|
if (cached === null) continue;
|
|
128
|
+
if (visitedModules.has(candidate) || !fs.existsSync(candidate)) continue;
|
|
112
129
|
visitedModules.add(candidate);
|
|
113
130
|
try {
|
|
114
131
|
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) {
|
|
@@ -8,7 +8,11 @@ import path from "node:path";
|
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
//#region src/plugins/vite-plugin/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);
|
|
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"
|
|
@@ -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/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/container-loader.ts","../src/plugins/vite-plugin/discovery-runtime.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/test-utils.ts","../src/plugins/vite-plugin/transform-guards.test.ts","../src/plugins/vite-plugin/visualization-option.test.ts","../src/plugins/vite-plugin/visualization-utils.ts","../src/plugins/vite-plugin/visualizer.test.ts","../src/plugins/vite-plugin/visualizer.ts"],"version":"6.0.3"}
|
|
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/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/container-loader.test.ts","../src/plugins/vite-plugin/container-loader.ts","../src/plugins/vite-plugin/discovery-runtime.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/test-utils.ts","../src/plugins/vite-plugin/transform-guards.test.ts","../src/plugins/vite-plugin/visualization-option.test.ts","../src/plugins/vite-plugin/visualization-utils.ts","../src/plugins/vite-plugin/visualizer.test.ts","../src/plugins/vite-plugin/visualizer.ts"],"version":"6.0.3"}
|