alloy-di 1.4.0 → 2.0.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/README.md +12 -7
- package/bin/alloy.js +9 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.js +168 -0
- package/dist/generate.d.ts +28 -0
- package/dist/generate.js +46 -0
- package/dist/lib/container.d.ts +78 -3
- package/dist/lib/container.js +130 -6
- package/dist/lib/lazy.d.ts +5 -1
- package/dist/lib/providers.d.ts +26 -2
- package/dist/lib/providers.js +23 -1
- package/dist/lib/scope.d.ts +21 -1
- package/dist/lib/types.d.ts +3 -1
- package/dist/plugins/consumer-plugin.d.ts +40 -0
- package/dist/plugins/consumer-plugin.js +101 -0
- package/dist/plugins/core/codegen.js +3 -3
- package/dist/plugins/core/container-loader.js +32 -17
- package/dist/plugins/core/discovery-runtime.js +19 -4
- package/dist/plugins/core/discovery-store.js +20 -6
- package/dist/plugins/core/generation-inputs.js +41 -0
- package/dist/plugins/core/scanner.js +71 -7
- package/dist/plugins/core/utils.js +1 -1
- package/dist/plugins/core/visualization-utils.js +1 -1
- package/dist/plugins/core/visualizer.js +40 -10
- package/dist/plugins/rollup-plugin/index.js +3 -5
- package/dist/plugins/vite-plugin/index.d.ts +2 -29
- package/dist/plugins/vite-plugin/index.js +19 -65
- package/dist/plugins/webpack-like-plugin.d.ts +32 -0
- package/dist/plugins/webpack-like-plugin.js +86 -0
- package/dist/rspack.d.ts +7 -0
- package/dist/rspack.js +10 -0
- package/dist/runtime.d.ts +6 -6
- package/dist/runtime.js +4 -4
- package/dist/scopes.d.ts +9 -1
- package/dist/scopes.js +29 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vite.d.ts +2 -1
- package/dist/webpack.d.ts +7 -0
- package/dist/webpack.js +10 -0
- package/package.json +28 -12
- package/dist/lib/testing/mocking.d.ts +0 -12
- package/dist/lib/testing/mocking.js +0 -107
- package/dist/lib/testing/registry.js +0 -17
- package/dist/test.d.ts +0 -50
- package/dist/test.js +0 -65
package/dist/lib/providers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Newable, Token } from "./types.js";
|
|
2
2
|
import { ServiceScope } from "./scope.js";
|
|
3
|
-
import { Container } from "./container.js";
|
|
3
|
+
import { Container, FactoryFn } from "./container.js";
|
|
4
4
|
import { Lazy } from "./lazy.js";
|
|
5
5
|
|
|
6
6
|
//#region src/lib/providers.d.ts
|
|
@@ -56,11 +56,19 @@ interface LazyServiceProviderDescriptor<T = unknown> {
|
|
|
56
56
|
lifecycle: ProviderLifecycle;
|
|
57
57
|
deps?: DependenciesOption;
|
|
58
58
|
}
|
|
59
|
+
/** Descriptor for a token-bound factory provider with an explicit lifecycle. */
|
|
60
|
+
interface FactoryProviderDescriptor<T = unknown> {
|
|
61
|
+
kind: "factory";
|
|
62
|
+
token: Token<T>;
|
|
63
|
+
factory: FactoryFn<T>;
|
|
64
|
+
lifecycle: ProviderLifecycle;
|
|
65
|
+
}
|
|
59
66
|
/** Aggregate provider definitions for batch application. */
|
|
60
67
|
interface ProviderDefinitions {
|
|
61
68
|
values?: ValueProviderDescriptor[];
|
|
62
69
|
services?: ServiceProviderDescriptor[];
|
|
63
70
|
lazyServices?: LazyPlaceholder[];
|
|
71
|
+
factories?: FactoryProviderDescriptor[];
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
74
|
* Identity helper for provider definition blocks.
|
|
@@ -76,6 +84,22 @@ declare function asClass<T extends Newable<unknown>>(useClass: T, options: {
|
|
|
76
84
|
lifecycle: ProviderLifecycle;
|
|
77
85
|
deps?: DependenciesOption;
|
|
78
86
|
}): ServiceProviderDescriptor<T>;
|
|
87
|
+
/**
|
|
88
|
+
* Create a factory provider: bind a token to a function executed at resolution
|
|
89
|
+
* time. Mirrors `asClass` — a token, a factory function, and a lifecycle.
|
|
90
|
+
*
|
|
91
|
+
* The factory receives the resolving context and may be async, so it can
|
|
92
|
+
* resolve its own dependencies. `singleton` caches the result on the root;
|
|
93
|
+
* `transient` re-runs on every resolution; a custom scope caches once per
|
|
94
|
+
* matching scope instance and disposes the result with it.
|
|
95
|
+
*
|
|
96
|
+
* @param token Token the produced value is bound to (its type checks `factory`'s return).
|
|
97
|
+
* @param factory Function invoked with the resolving context; may be async.
|
|
98
|
+
* @param options.lifecycle Lifecycle scope (singleton, transient, or a custom scope).
|
|
99
|
+
*/
|
|
100
|
+
declare function asFactory<T>(token: Token<T>, factory: FactoryFn<T>, options: {
|
|
101
|
+
lifecycle: ProviderLifecycle;
|
|
102
|
+
}): FactoryProviderDescriptor<T>;
|
|
79
103
|
/**
|
|
80
104
|
* Convenience lifecycle helpers for fluent provider creation.
|
|
81
105
|
*/
|
|
@@ -108,4 +132,4 @@ declare function asLazyClass<T, const TDeps extends DependenciesOption>(importer
|
|
|
108
132
|
*/
|
|
109
133
|
declare function applyProviders(container: Container, definitions: ProviderDefinitions | ProviderDefinitions[]): void;
|
|
110
134
|
//#endregion
|
|
111
|
-
export { ProviderDefinitions, applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle };
|
|
135
|
+
export { FactoryProviderDescriptor, ProviderDefinitions, applyProviders, asClass, asFactory, asLazyClass, asValue, defineProviders, lifecycle };
|
package/dist/lib/providers.js
CHANGED
|
@@ -34,6 +34,27 @@ function asClass(useClass, options) {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
+
* Create a factory provider: bind a token to a function executed at resolution
|
|
38
|
+
* time. Mirrors `asClass` — a token, a factory function, and a lifecycle.
|
|
39
|
+
*
|
|
40
|
+
* The factory receives the resolving context and may be async, so it can
|
|
41
|
+
* resolve its own dependencies. `singleton` caches the result on the root;
|
|
42
|
+
* `transient` re-runs on every resolution; a custom scope caches once per
|
|
43
|
+
* matching scope instance and disposes the result with it.
|
|
44
|
+
*
|
|
45
|
+
* @param token Token the produced value is bound to (its type checks `factory`'s return).
|
|
46
|
+
* @param factory Function invoked with the resolving context; may be async.
|
|
47
|
+
* @param options.lifecycle Lifecycle scope (singleton, transient, or a custom scope).
|
|
48
|
+
*/
|
|
49
|
+
function asFactory(token, factory, options) {
|
|
50
|
+
return {
|
|
51
|
+
kind: "factory",
|
|
52
|
+
token,
|
|
53
|
+
factory,
|
|
54
|
+
lifecycle: options.lifecycle
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
37
58
|
* Convenience lifecycle helpers for fluent provider creation.
|
|
38
59
|
*/
|
|
39
60
|
const lifecycle = {
|
|
@@ -145,6 +166,7 @@ function applyProviders(container, definitions) {
|
|
|
145
166
|
detectProviderCycles(planEntries);
|
|
146
167
|
for (const definition of list) {
|
|
147
168
|
for (const valueProvider of definition.values ?? []) container.provideValue(valueProvider.token, valueProvider.value);
|
|
169
|
+
for (const factoryProvider of definition.factories ?? []) container.provideFactory(factoryProvider.token, factoryProvider.factory, { lifecycle: factoryProvider.lifecycle });
|
|
148
170
|
const registerService = (ctor, lifecycle, deps, factory) => {
|
|
149
171
|
dependenciesRegistry.set(ctor, {
|
|
150
172
|
scope: lifecycle,
|
|
@@ -161,4 +183,4 @@ function applyProviders(container, definitions) {
|
|
|
161
183
|
}
|
|
162
184
|
}
|
|
163
185
|
//#endregion
|
|
164
|
-
export { applyProviders, asClass, asLazyClass, asValue, defineProviders, lifecycle };
|
|
186
|
+
export { applyProviders, asClass, asFactory, asLazyClass, asValue, defineProviders, lifecycle };
|
package/dist/lib/scope.d.ts
CHANGED
|
@@ -10,16 +10,36 @@ interface AlloyScopes {
|
|
|
10
10
|
transient: true;
|
|
11
11
|
}
|
|
12
12
|
type ServiceScope = keyof AlloyScopes;
|
|
13
|
+
/**
|
|
14
|
+
* A cached factory result tagged with the `generation` of the descriptor that
|
|
15
|
+
* produced it. A mismatch against the current descriptor's generation (after a
|
|
16
|
+
* factory is re-registered) makes the entry stale, so it is recomputed.
|
|
17
|
+
*/
|
|
18
|
+
interface FactoryCacheEntry {
|
|
19
|
+
generation: number;
|
|
20
|
+
value: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** An in-flight factory resolution tagged with its descriptor generation. */
|
|
23
|
+
interface FactoryPendingEntry {
|
|
24
|
+
generation: number;
|
|
25
|
+
promise: Promise<unknown>;
|
|
26
|
+
}
|
|
13
27
|
interface ResolutionContext {
|
|
14
28
|
readonly scopeName: ServiceScope;
|
|
15
29
|
getCached(target: Constructor): unknown;
|
|
30
|
+
hasCached(target: Constructor): boolean;
|
|
16
31
|
setCached(target: Constructor, instance: unknown): void;
|
|
17
32
|
getPending(target: Constructor): Promise<unknown> | undefined;
|
|
18
33
|
setPending(target: Constructor, promise: Promise<unknown>): void;
|
|
19
34
|
deletePending(target: Constructor): void;
|
|
20
35
|
getProvider(tokenId: symbol): unknown;
|
|
21
36
|
hasProvider(tokenId: symbol): boolean;
|
|
37
|
+
getFactoryValue(tokenId: symbol): FactoryCacheEntry | undefined;
|
|
38
|
+
setFactoryValue(tokenId: symbol, generation: number, value: unknown): void;
|
|
39
|
+
getFactoryPending(tokenId: symbol): FactoryPendingEntry | undefined;
|
|
40
|
+
setFactoryPending(tokenId: symbol, generation: number, promise: Promise<unknown>): void;
|
|
41
|
+
deleteFactoryPending(tokenId: symbol, generation: number): void;
|
|
22
42
|
readonly parent: ResolutionContext | null;
|
|
23
43
|
}
|
|
24
44
|
//#endregion
|
|
25
|
-
export { AlloyScopes, ResolutionContext, ServiceScope };
|
|
45
|
+
export { AlloyScopes, FactoryCacheEntry, FactoryPendingEntry, ResolutionContext, ServiceScope };
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -18,5 +18,7 @@ interface Token<T> {
|
|
|
18
18
|
}
|
|
19
19
|
/** Create a unique typed token for values or abstractions. */
|
|
20
20
|
declare function createToken<T>(description?: string): Token<T>;
|
|
21
|
+
declare function isToken(value: unknown): value is Token<unknown>;
|
|
22
|
+
declare function isConstructor(value: unknown): value is Constructor;
|
|
21
23
|
//#endregion
|
|
22
|
-
export { Constructor, Newable, Token, createToken };
|
|
24
|
+
export { Constructor, Newable, Token, createToken, isConstructor, isToken };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ServiceIdentifier } from "../lib/service-identifiers.js";
|
|
2
|
+
import { AlloyManifest } from "./core/types.js";
|
|
3
|
+
import { AlloyScopesConfig } from "./core/scopes-validation.js";
|
|
4
|
+
import { AlloyMermaidVisualizerOptions, AlloyVisualizationOptions } from "./core/visualization-utils.js";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/consumer-plugin.d.ts
|
|
7
|
+
interface AlloyPluginOptions {
|
|
8
|
+
providers?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Source directories to scan for decorated services before the virtual
|
|
11
|
+
* container is loaded. Relative paths are resolved against the project root.
|
|
12
|
+
* Defaults to ["src"].
|
|
13
|
+
*/
|
|
14
|
+
sourceDirs?: string[];
|
|
15
|
+
/** Optional list of manifest objects to ingest */
|
|
16
|
+
manifests?: AlloyManifest[];
|
|
17
|
+
/** List of ServiceIdentifiers to mark as instantiation-lazy (adds factory Lazy wrapper) */
|
|
18
|
+
lazyServices?: ServiceIdentifier[];
|
|
19
|
+
/**
|
|
20
|
+
* Output directory for the generated `virtual-container.d.ts` file.
|
|
21
|
+
* Relative paths are resolved against the project root.
|
|
22
|
+
* Defaults to "./src".
|
|
23
|
+
*/
|
|
24
|
+
containerDeclarationDir?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Emit dependency graph artifacts. When `true`, writes a Mermaid diagram to
|
|
27
|
+
* `${projectRoot}/alloy-di.mmd`. Provide an object to customize output.
|
|
28
|
+
*/
|
|
29
|
+
visualize?: boolean | AlloyVisualizationOptions;
|
|
30
|
+
/**
|
|
31
|
+
* Declares custom, application-defined scopes and their parent ordering, e.g.
|
|
32
|
+
* `{ session: { parent: 'singleton' }, request: { parent: 'session' } }`.
|
|
33
|
+
* Drives type-safe scope names (emitted into the generated declaration),
|
|
34
|
+
* runtime hierarchy registration, and build-time scope-stability validation.
|
|
35
|
+
*/
|
|
36
|
+
scopes?: AlloyScopesConfig;
|
|
37
|
+
}
|
|
38
|
+
declare const ALLOY_PLUGIN_OPTIONS: unique symbol;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { ALLOY_PLUGIN_OPTIONS, AlloyPluginOptions };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { normalizeImportPath, writeFileIfChanged } from "./core/utils.js";
|
|
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 { DEFAULT_SOURCE_DIRS, readPackageName, scanSourceDirectories, toLazyServiceKey } from "./core/generation-inputs.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
//#region src/plugins/consumer-plugin.ts
|
|
9
|
+
const ALLOY_PLUGIN_OPTIONS = Symbol.for("alloy-di.plugin-options");
|
|
10
|
+
function createConsumerPluginContext(options = {}) {
|
|
11
|
+
const configuredProviderEntries = Array.from(options.providers ?? []);
|
|
12
|
+
const configuredSourceDirs = Array.from(options.sourceDirs ?? DEFAULT_SOURCE_DIRS);
|
|
13
|
+
const providerModuleRefs = [];
|
|
14
|
+
const lazyServiceKeys = new Set((options.lazyServices ?? []).map(toLazyServiceKey));
|
|
15
|
+
const discoveryRuntime = createDiscoveryRuntime();
|
|
16
|
+
let resolvedRoot = process.cwd();
|
|
17
|
+
let packageName = "UNKNOWN_PACKAGE";
|
|
18
|
+
let resolvedVisualization = null;
|
|
19
|
+
let isDevMode;
|
|
20
|
+
function configure(config = {}) {
|
|
21
|
+
resolvedRoot = config.root ?? process.cwd();
|
|
22
|
+
isDevMode = config.isDevMode;
|
|
23
|
+
packageName = readPackageName(resolvedRoot);
|
|
24
|
+
providerModuleRefs.length = 0;
|
|
25
|
+
for (const entry of configuredProviderEntries) {
|
|
26
|
+
const absPath = path.isAbsolute(entry) ? entry : path.resolve(resolvedRoot, entry);
|
|
27
|
+
providerModuleRefs.push({
|
|
28
|
+
absPath,
|
|
29
|
+
importPath: normalizeImportPath(absPath)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
resolvedVisualization = resolveVisualizationOptions(options.visualize, resolvedRoot);
|
|
33
|
+
}
|
|
34
|
+
async function loadContainer() {
|
|
35
|
+
return loadVirtualContainerModule({
|
|
36
|
+
localMetas: Array.from(discoveryRuntime.discoveredClasses.values()),
|
|
37
|
+
lazyReferencedClassKeys: discoveryRuntime.lazyReferencedClassKeys,
|
|
38
|
+
manifests: options.manifests ?? [],
|
|
39
|
+
providerImportPaths: providerModuleRefs.map((ref) => ref.importPath),
|
|
40
|
+
factoryProviders: shouldTrackFactoryProviders() ? Array.from(discoveryRuntime.factoryProvidersByFile.values()).flat() : [],
|
|
41
|
+
lazyServiceKeys,
|
|
42
|
+
packageName,
|
|
43
|
+
resolvedRoot,
|
|
44
|
+
containerDeclarationDir: options.containerDeclarationDir,
|
|
45
|
+
resolvedVisualization,
|
|
46
|
+
isDevMode,
|
|
47
|
+
scopes: options.scopes
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function shouldTrackFactoryProviders() {
|
|
51
|
+
return Boolean(resolvedVisualization);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
options,
|
|
55
|
+
virtualModuleId: "virtual:alloy-container",
|
|
56
|
+
resolvedVirtualModuleId: "\0virtual:alloy-container",
|
|
57
|
+
get root() {
|
|
58
|
+
return resolvedRoot;
|
|
59
|
+
},
|
|
60
|
+
sourceDirs: configuredSourceDirs,
|
|
61
|
+
configure,
|
|
62
|
+
shouldTrackFactoryProviders,
|
|
63
|
+
processTransform(code, id) {
|
|
64
|
+
return discoveryRuntime.processUpdate(id, code, { factoryProviders: shouldTrackFactoryProviders() });
|
|
65
|
+
},
|
|
66
|
+
processFileUpdate(file, code) {
|
|
67
|
+
return discoveryRuntime.processUpdate(file, code, { factoryProviders: shouldTrackFactoryProviders() });
|
|
68
|
+
},
|
|
69
|
+
removeFile(file) {
|
|
70
|
+
return discoveryRuntime.removeDiscoveredFile(file);
|
|
71
|
+
},
|
|
72
|
+
buildStart(addWatchFile) {
|
|
73
|
+
discoveryRuntime.clear();
|
|
74
|
+
for (const ref of providerModuleRefs) {
|
|
75
|
+
addWatchFile?.(ref.absPath);
|
|
76
|
+
if (shouldTrackFactoryProviders()) try {
|
|
77
|
+
const code = fs.readFileSync(ref.absPath, "utf-8");
|
|
78
|
+
discoveryRuntime.processUpdate(ref.absPath, code, { factoryProviders: true });
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
scanSourceDirectories(discoveryRuntime, resolvedRoot, configuredSourceDirs, { factoryProviders: shouldTrackFactoryProviders() });
|
|
82
|
+
},
|
|
83
|
+
loadContainer,
|
|
84
|
+
async writeContainerCache(filePath) {
|
|
85
|
+
const result = await loadContainer();
|
|
86
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
87
|
+
return writeFileIfChanged(filePath, result.code);
|
|
88
|
+
},
|
|
89
|
+
getWatchFiles() {
|
|
90
|
+
return providerModuleRefs.map((ref) => ref.absPath);
|
|
91
|
+
},
|
|
92
|
+
getWatchDirectories() {
|
|
93
|
+
return configuredSourceDirs.map((sourceDir) => path.isAbsolute(sourceDir) ? sourceDir : path.resolve(resolvedRoot, sourceDir));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function isAlloyDiscoverableFile(file) {
|
|
98
|
+
return isDiscoverableFile(file);
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { ALLOY_PLUGIN_OPTIONS, createConsumerPluginContext, isAlloyDiscoverableFile };
|
|
@@ -171,7 +171,7 @@ function reconstructOptionsText(meta, importMap, serviceRenames) {
|
|
|
171
171
|
const expr = reconstructDependencyExpression(factory, () => "", path.dirname(meta.filePath));
|
|
172
172
|
parts.push(`factory: ${expr}`);
|
|
173
173
|
}
|
|
174
|
-
if (scope
|
|
174
|
+
if (scope && scope !== "transient") parts.push(`scope: '${escapeSingleQuotes(scope)}'`);
|
|
175
175
|
if (dependencies && dependencies.length > 0) {
|
|
176
176
|
const depExprs = dependencies.map((dep) => {
|
|
177
177
|
return reconstructDependencyExpression(dep, (ident) => {
|
|
@@ -381,8 +381,8 @@ function generateContainerTypeDefinition(metas, pathResolver) {
|
|
|
381
381
|
for (const meta of metas) {
|
|
382
382
|
const importName = pool.claim(resolver.resolve(meta.className, meta.filePath));
|
|
383
383
|
const importPath = pathResolver(meta.filePath);
|
|
384
|
-
if (importName === meta.className) imports.push(`import { ${meta.className} } from '${importPath}';`);
|
|
385
|
-
else imports.push(`import { ${meta.className} as ${importName} } from '${importPath}';`);
|
|
384
|
+
if (importName === meta.className) imports.push(` import { ${meta.className} } from '${importPath}';`);
|
|
385
|
+
else imports.push(` import { ${meta.className} as ${importName} } from '${importPath}';`);
|
|
386
386
|
const exportKey = createIdentifierExportKey(meta, resolver);
|
|
387
387
|
interfaceMembers.push(`${exportKey}: ServiceIdentifier<${importName}>;`);
|
|
388
388
|
}
|
|
@@ -5,10 +5,29 @@ import { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateM
|
|
|
5
5
|
import { validateScopeStability, validateScopesConfig } from "./scopes-validation.js";
|
|
6
6
|
import { generateMermaidDiagram } from "./visualizer.js";
|
|
7
7
|
import { ensureDirectoryForFile } from "./visualization-utils.js";
|
|
8
|
-
import path from "node:path";
|
|
9
8
|
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
10
|
//#region src/plugins/core/container-loader.ts
|
|
11
11
|
async function loadVirtualContainerModule(options) {
|
|
12
|
+
const prepared = await prepareContainerData(options);
|
|
13
|
+
const code = generateContainerModule(prepared.metas, prepared.lazyClassKeys, prepared.providerImports, {
|
|
14
|
+
isDev: options.isDevMode,
|
|
15
|
+
scopes: options.scopes
|
|
16
|
+
});
|
|
17
|
+
writeDeclarationArtifacts({
|
|
18
|
+
metas: prepared.metas,
|
|
19
|
+
loadedManifests: prepared.loadedManifests,
|
|
20
|
+
resolvedRoot: options.resolvedRoot,
|
|
21
|
+
containerDeclarationDir: options.containerDeclarationDir,
|
|
22
|
+
scopes: options.scopes
|
|
23
|
+
});
|
|
24
|
+
writeVisualizationArtifact(prepared.metas, prepared.lazyClassKeys, options.factoryProviders, options.resolvedVisualization, options.scopes);
|
|
25
|
+
return {
|
|
26
|
+
code,
|
|
27
|
+
moduleType: "js"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function prepareContainerData(options) {
|
|
12
31
|
const metas = options.localMetas.map((meta) => ({
|
|
13
32
|
...meta,
|
|
14
33
|
metadata: { ...meta.metadata }
|
|
@@ -33,19 +52,13 @@ async function loadVirtualContainerModule(options) {
|
|
|
33
52
|
const providerImports = Array.from(new Set([...options.providerImportPaths, ...manifestData.providers]));
|
|
34
53
|
reconcileLazySet(metas, lazyClassKeys, collectEagerReferencedNames(metas));
|
|
35
54
|
augmentFactoryLazyServices(metas, options.lazyServiceKeys);
|
|
36
|
-
if (options.scopes && Object.keys(options.scopes).length > 0)
|
|
37
|
-
|
|
38
|
-
validateScopeStability(metas, options.scopes);
|
|
39
|
-
}
|
|
40
|
-
const code = generateContainerModule(metas, lazyClassKeys, providerImports, {
|
|
41
|
-
isDev: options.isDevMode,
|
|
42
|
-
scopes: options.scopes
|
|
43
|
-
});
|
|
44
|
-
writeTypeDefinitions(metas, loadedManifests, options.resolvedRoot, options.containerDeclarationDir, options.scopes);
|
|
45
|
-
writeVisualizationArtifact(metas, lazyClassKeys, options.resolvedVisualization, options.scopes);
|
|
55
|
+
if (options.scopes && Object.keys(options.scopes).length > 0) validateScopesConfig(options.scopes);
|
|
56
|
+
validateScopeStability(metas, options.scopes);
|
|
46
57
|
return {
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
metas,
|
|
59
|
+
loadedManifests,
|
|
60
|
+
lazyClassKeys,
|
|
61
|
+
providerImports
|
|
49
62
|
};
|
|
50
63
|
}
|
|
51
64
|
function assignIdentifierKeys(metas, packageName, resolvedRoot) {
|
|
@@ -70,13 +83,14 @@ function assertNoDuplicateManifestServices(metas, manifestServices) {
|
|
|
70
83
|
"Resolve by removing one source (local or manifest) to avoid ambiguous DI keys."
|
|
71
84
|
].join("\n"));
|
|
72
85
|
}
|
|
73
|
-
function
|
|
86
|
+
function writeDeclarationArtifacts(options) {
|
|
87
|
+
const { metas, loadedManifests, resolvedRoot, containerDeclarationDir } = options;
|
|
74
88
|
const dtsDir = path.resolve(resolvedRoot, containerDeclarationDir ?? "./src");
|
|
75
89
|
const dtsContent = generateContainerTypeDefinition(metas, (filePath) => resolveDeclarationImportPath(dtsDir, filePath));
|
|
76
90
|
if (!fs.existsSync(dtsDir)) fs.mkdirSync(dtsDir, { recursive: true });
|
|
77
91
|
writeFileIfChanged(path.join(dtsDir, "alloy-container.d.ts"), dtsContent);
|
|
78
92
|
const scopeAugmentationPath = path.join(dtsDir, "alloy-scopes.d.ts");
|
|
79
|
-
const scopeAugmentation = generateScopeAugmentationDefinition(scopes ? Object.keys(scopes) : []);
|
|
93
|
+
const scopeAugmentation = generateScopeAugmentationDefinition(options.scopes ? Object.keys(options.scopes) : []);
|
|
80
94
|
if (scopeAugmentation) writeFileIfChanged(scopeAugmentationPath, scopeAugmentation);
|
|
81
95
|
else if (fs.existsSync(scopeAugmentationPath)) fs.rmSync(scopeAugmentationPath);
|
|
82
96
|
if (loadedManifests.length === 0) return;
|
|
@@ -93,11 +107,12 @@ function resolveDeclarationImportPath(dtsDir, filePath) {
|
|
|
93
107
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
94
108
|
return rel;
|
|
95
109
|
}
|
|
96
|
-
function writeVisualizationArtifact(metas, lazyReferencedClassKeys, resolvedVisualization, scopes) {
|
|
110
|
+
function writeVisualizationArtifact(metas, lazyReferencedClassKeys, factoryProviders, resolvedVisualization, scopes) {
|
|
97
111
|
if (!resolvedVisualization) return;
|
|
98
112
|
const artifact = generateMermaidDiagram({
|
|
99
113
|
metas,
|
|
100
114
|
lazyClassKeys: new Set(lazyReferencedClassKeys),
|
|
115
|
+
factoryProviders,
|
|
101
116
|
options: resolvedVisualization.mermaidOptions,
|
|
102
117
|
scopes
|
|
103
118
|
});
|
|
@@ -105,4 +120,4 @@ function writeVisualizationArtifact(metas, lazyReferencedClassKeys, resolvedVisu
|
|
|
105
120
|
writeFileIfChanged(resolvedVisualization.outputPath, `%% ${GENERATED_FILE_NOTICE}\n${artifact.diagram}\n`);
|
|
106
121
|
}
|
|
107
122
|
//#endregion
|
|
108
|
-
export { loadVirtualContainerModule };
|
|
123
|
+
export { loadVirtualContainerModule, prepareContainerData, writeDeclarationArtifacts };
|
|
@@ -34,31 +34,46 @@ function lazyKeysSignature(keys) {
|
|
|
34
34
|
if (!keys || keys.size === 0) return "";
|
|
35
35
|
return Array.from(keys).toSorted().join("|");
|
|
36
36
|
}
|
|
37
|
+
function factoryProvidersSignature(providers) {
|
|
38
|
+
return JSON.stringify((providers ?? []).map((provider) => ({
|
|
39
|
+
filePath: provider.filePath,
|
|
40
|
+
tokenExpression: provider.tokenExpression,
|
|
41
|
+
tokenLabel: provider.tokenLabel,
|
|
42
|
+
lifecycle: provider.lifecycle
|
|
43
|
+
})));
|
|
44
|
+
}
|
|
37
45
|
function createDiscoveryRuntime() {
|
|
38
46
|
const discovery = createDiscoveryStore();
|
|
39
47
|
const discoveredClasses = /* @__PURE__ */ new Map();
|
|
40
48
|
const lazyReferencedClassKeys = /* @__PURE__ */ new Set();
|
|
49
|
+
const factoryProvidersByFile = /* @__PURE__ */ new Map();
|
|
41
50
|
return {
|
|
42
51
|
discoveredClasses,
|
|
43
52
|
lazyReferencedClassKeys,
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
factoryProvidersByFile,
|
|
54
|
+
processUpdate(id, code, options) {
|
|
55
|
+
const trackFactoryProviders = options?.factoryProviders ?? true;
|
|
56
|
+
const { metas, lazyClassKeys, factoryProviders, previousMetas, previousLazyClassKeys, previousFactoryProviders } = discovery.updateFile(id, code, { factoryProviders: trackFactoryProviders });
|
|
46
57
|
if (previousMetas) for (const meta of previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
|
|
47
58
|
for (const meta of metas) discoveredClasses.set(createClassKey(meta.filePath, meta.className), meta);
|
|
48
59
|
if (previousLazyClassKeys) for (const key of previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
|
|
49
60
|
if (lazyClassKeys.size) for (const key of lazyClassKeys) lazyReferencedClassKeys.add(key);
|
|
50
|
-
|
|
61
|
+
if (trackFactoryProviders && factoryProviders.length) factoryProvidersByFile.set(id, factoryProviders);
|
|
62
|
+
else factoryProvidersByFile.delete(id);
|
|
63
|
+
return metasSignature(previousMetas ?? []) !== metasSignature(metas) || lazyKeysSignature(previousLazyClassKeys) !== lazyKeysSignature(lazyClassKeys) || trackFactoryProviders && factoryProvidersSignature(previousFactoryProviders) !== factoryProvidersSignature(factoryProviders);
|
|
51
64
|
},
|
|
52
65
|
removeDiscoveredFile(file) {
|
|
53
66
|
const removed = discovery.removeFile(file);
|
|
54
67
|
if (removed.previousMetas) for (const meta of removed.previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
|
|
55
68
|
if (removed.previousLazyClassKeys) for (const key of removed.previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
|
|
56
|
-
|
|
69
|
+
factoryProvidersByFile.delete(file);
|
|
70
|
+
return Boolean(removed.previousMetas?.length || removed.previousLazyClassKeys?.size || removed.previousFactoryProviders?.length);
|
|
57
71
|
},
|
|
58
72
|
clear() {
|
|
59
73
|
discovery.clear();
|
|
60
74
|
discoveredClasses.clear();
|
|
61
75
|
lazyReferencedClassKeys.clear();
|
|
76
|
+
factoryProvidersByFile.clear();
|
|
62
77
|
}
|
|
63
78
|
};
|
|
64
79
|
}
|
|
@@ -19,6 +19,7 @@ function hashContent(code) {
|
|
|
19
19
|
function createDiscoveryStore(options = {}) {
|
|
20
20
|
const fileMetas = /* @__PURE__ */ new Map();
|
|
21
21
|
const fileLazyRefs = /* @__PURE__ */ new Map();
|
|
22
|
+
const fileFactoryProviders = /* @__PURE__ */ new Map();
|
|
22
23
|
const fileContentHashes = /* @__PURE__ */ new Map();
|
|
23
24
|
const fileSources = options.trackSources ? /* @__PURE__ */ new Map() : void 0;
|
|
24
25
|
/**
|
|
@@ -28,28 +29,36 @@ function createDiscoveryStore(options = {}) {
|
|
|
28
29
|
* @param id - Module identifier or path.
|
|
29
30
|
* @param code - Current file contents to analyze.
|
|
30
31
|
*/
|
|
31
|
-
function updateFile(id, code) {
|
|
32
|
+
function updateFile(id, code, options) {
|
|
33
|
+
const scanFactoryProviders = options?.factoryProviders ?? true;
|
|
32
34
|
const previousMetas = fileMetas.get(id);
|
|
33
35
|
const previousLazyClassKeys = fileLazyRefs.get(id);
|
|
34
|
-
const
|
|
36
|
+
const previousFactoryProviders = fileFactoryProviders.get(id);
|
|
37
|
+
const contentHash = hashContent(`${scanFactoryProviders ? "factory:1" : "factory:0"}\0${code}`);
|
|
35
38
|
if (fileContentHashes.get(id) === contentHash) return {
|
|
36
39
|
metas: previousMetas ?? [],
|
|
37
40
|
lazyClassKeys: new Set(previousLazyClassKeys),
|
|
41
|
+
factoryProviders: previousFactoryProviders ?? [],
|
|
38
42
|
previousMetas,
|
|
39
|
-
previousLazyClassKeys
|
|
43
|
+
previousLazyClassKeys,
|
|
44
|
+
previousFactoryProviders
|
|
40
45
|
};
|
|
41
46
|
fileContentHashes.set(id, contentHash);
|
|
42
47
|
if (fileSources) fileSources.set(id, code);
|
|
43
|
-
const { metas, lazyClassKeys } = scanSource(code, id);
|
|
48
|
+
const { metas, lazyClassKeys, factoryProviders } = scanSource(code, id, { factoryProviders: scanFactoryProviders });
|
|
44
49
|
if (metas.length) fileMetas.set(id, metas);
|
|
45
50
|
else fileMetas.delete(id);
|
|
46
51
|
if (lazyClassKeys.size) fileLazyRefs.set(id, lazyClassKeys);
|
|
47
52
|
else fileLazyRefs.delete(id);
|
|
53
|
+
if (factoryProviders.length) fileFactoryProviders.set(id, factoryProviders);
|
|
54
|
+
else fileFactoryProviders.delete(id);
|
|
48
55
|
return {
|
|
49
56
|
metas,
|
|
50
57
|
lazyClassKeys,
|
|
58
|
+
factoryProviders,
|
|
51
59
|
previousMetas,
|
|
52
|
-
previousLazyClassKeys
|
|
60
|
+
previousLazyClassKeys,
|
|
61
|
+
previousFactoryProviders
|
|
53
62
|
};
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
@@ -60,13 +69,16 @@ function createDiscoveryStore(options = {}) {
|
|
|
60
69
|
function removeFile(id) {
|
|
61
70
|
const previousMetas = fileMetas.get(id);
|
|
62
71
|
const previousLazyClassKeys = fileLazyRefs.get(id);
|
|
72
|
+
const previousFactoryProviders = fileFactoryProviders.get(id);
|
|
63
73
|
fileMetas.delete(id);
|
|
64
74
|
fileLazyRefs.delete(id);
|
|
75
|
+
fileFactoryProviders.delete(id);
|
|
65
76
|
fileContentHashes.delete(id);
|
|
66
77
|
if (fileSources) fileSources.delete(id);
|
|
67
78
|
return {
|
|
68
79
|
previousMetas,
|
|
69
|
-
previousLazyClassKeys
|
|
80
|
+
previousLazyClassKeys,
|
|
81
|
+
previousFactoryProviders
|
|
70
82
|
};
|
|
71
83
|
}
|
|
72
84
|
/**
|
|
@@ -75,12 +87,14 @@ function createDiscoveryStore(options = {}) {
|
|
|
75
87
|
function clear() {
|
|
76
88
|
fileMetas.clear();
|
|
77
89
|
fileLazyRefs.clear();
|
|
90
|
+
fileFactoryProviders.clear();
|
|
78
91
|
fileContentHashes.clear();
|
|
79
92
|
fileSources?.clear();
|
|
80
93
|
}
|
|
81
94
|
return {
|
|
82
95
|
fileMetas,
|
|
83
96
|
fileLazyRefs,
|
|
97
|
+
fileFactoryProviders,
|
|
84
98
|
fileSources,
|
|
85
99
|
updateFile,
|
|
86
100
|
removeFile,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { walkSync } from "./utils.js";
|
|
2
|
+
import { createDiscoveryRuntime } from "./discovery-runtime.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
//#region src/plugins/core/generation-inputs.ts
|
|
6
|
+
const DEFAULT_SOURCE_DIRS = ["src"];
|
|
7
|
+
function toLazyServiceKey(identifier) {
|
|
8
|
+
const description = identifier.description;
|
|
9
|
+
if (!description?.startsWith("alloy:")) throw new Error("[alloy] lazyServices entries must be serviceIdentifiers exported by Alloy manifests.");
|
|
10
|
+
return description;
|
|
11
|
+
}
|
|
12
|
+
function readPackageName(root) {
|
|
13
|
+
try {
|
|
14
|
+
const pkgPath = path.resolve(root, "package.json");
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
16
|
+
if (typeof pkg.name === "string") return pkg.name;
|
|
17
|
+
} catch {}
|
|
18
|
+
return "UNKNOWN_PACKAGE";
|
|
19
|
+
}
|
|
20
|
+
function isSourceFile(file) {
|
|
21
|
+
return /\.tsx?$/i.test(file) && !file.endsWith(".d.ts");
|
|
22
|
+
}
|
|
23
|
+
function scanSourceDirectories(discoveryRuntime, root, sourceDirs = DEFAULT_SOURCE_DIRS, options) {
|
|
24
|
+
for (const sourceDir of sourceDirs) {
|
|
25
|
+
const files = walkSync(path.isAbsolute(sourceDir) ? sourceDir : path.resolve(root, sourceDir));
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
if (!isSourceFile(file)) continue;
|
|
28
|
+
try {
|
|
29
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
30
|
+
discoveryRuntime.processUpdate(file, code, options);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function createDiscoveryRuntimeForSourceDirs(root, sourceDirs = DEFAULT_SOURCE_DIRS, options) {
|
|
36
|
+
const discoveryRuntime = createDiscoveryRuntime();
|
|
37
|
+
scanSourceDirectories(discoveryRuntime, root, sourceDirs, options);
|
|
38
|
+
return discoveryRuntime;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { DEFAULT_SOURCE_DIRS, createDiscoveryRuntimeForSourceDirs, readPackageName, scanSourceDirectories, toLazyServiceKey };
|