@angular-modernizer/worker
v1.2.0
Published
Worker thread script for parallel analysis in the Angular Modernization Platform
Maintainers
Readme
@angular-modernizer/worker
Worker thread script for parallel analysis in the Angular Modernization Platform.
Overview
@angular-modernizer/core can run analysis rules in Node.js worker threads (WorkerPool, ParallelExecutor). Each worker is started from a script file. That script lives in this package, and getWorkerScriptPath() returns its path.
The package exports:
getWorkerScriptPath()- absolute path to the compiled worker script (dist/analysis-worker.js)WorkerTask,WorkerResult- message types, re-exported from@angular-modernizer/core
The script itself is also published as the subpath export @angular-modernizer/worker/analysis-worker. It can only run as a worker thread; importing it on the main thread throws.
Why a Separate Package?
Worker threads run in isolated V8 contexts, so the worker script has to load all analysis plugins itself. The plugins depend on core, and core contains the WorkerPool. Placing the script in core would create a circular dependency.
This package resolves that by:
- Depending on all plugin packages (it builds after them)
- Exporting a helper that returns the worker script path
- Being used by both programmatic API users and the MCP adapter (
scan-architecturepasses the path to the parallel executor)
Installation
pnpm add @angular-modernizer/worker @angular-modernizer/coreThe worker script is loaded from dist/, so in the monorepo all workspace packages must be built first (pnpm build from the root).
Usage
With the ParallelExecutor
ParallelExecutor decides on its own whether to use worker threads (see Mode Selection). Pass the script path, the tsconfig the workers load and the plugin IDs whose rules the workers run:
import { getWorkerScriptPath } from '@angular-modernizer/worker';
import { ParallelExecutor } from '@angular-modernizer/core';
const executor = new ParallelExecutor({ maxWorkers: 4 });
const results = await executor.executeParallel(sourceFiles, rules, {
tsConfigPath: '/path/to/tsconfig.json',
workerScriptPath: getWorkerScriptPath(),
pluginIds: ['architecture', 'solid'],
});
await executor.shutdown();With the WorkerPool directly
import { getWorkerScriptPath } from '@angular-modernizer/worker';
import { WorkerPool } from '@angular-modernizer/core';
const pool = new WorkerPool({
tsConfigPath: '/path/to/tsconfig.json',
workerScriptPath: getWorkerScriptPath(),
maxWorkers: 4,
});
const result = await pool.executeTask({
id: 'task-1',
type: 'analyze-rule',
filePath: '/path/to/file.ts',
ruleIds: [], // empty: all rules of the listed plugins
pluginIds: ['architecture', 'solid'],
});
if (result.error) {
console.error(result.error);
}
console.log(result.results);
await pool.terminate();WorkerPool throws when workerScriptPath is missing.
How Workers Run
Each worker started from the script:
- Reads
workerIdandtsConfigPathfromworkerDataand loads the whole project from that tsconfig into its own ts-morphProject. Rules need cross-file context (inheritance, imports), so a worker does not load single files. - Creates its own
PublicApiand posts{ type: 'ready', workerId }. If loading fails, it posts{ type: 'init-error', workerId, error }and exits. The pool only hands out tasks to workers that sentready. - Receives
WorkerTaskmessages and answers each with aWorkerResult.
For a task the worker:
- loads the analysis rules of the plugins in
pluginIds; rules are cached per plugin ID for later tasks - keeps only the rules listed in
ruleIdswhen that list is not empty - builds the context with
ContextFactory.createAnalysisContext()and runs the rules onfilePath - returns the results with the severity
warning(plugin results carry no severity)
Accepted plugin IDs:
| Plugin ID | Package |
|-|-|
| architecture | @angular-modernizer/plugin-architecture |
| solid | @angular-modernizer/plugin-solid |
| angular | @angular-modernizer/plugin-angular |
| analyzer | @angular-modernizer/plugin-analyzer |
| standalone | @angular-modernizer/plugin-standalone (no analysis rules) |
The full package name works as well. Unknown IDs are logged and ignored. With no matching rules the task returns an empty result list, so pass pluginIds: the ParallelExecutor sends an empty list when pluginIds is not set.
Errors:
- A rule that throws is logged and skipped; the other rules still run.
- A task fails (empty
results,errorset) whenfilePathis not part of the loaded project or the worker is not initialized. - An uncaught exception or unhandled rejection in a worker posts
init-errorand ends the worker.
Workers log to the console with the prefix [Worker <id>].
Mode Selection
ParallelExecutor.executeParallel() chooses between Promise-based parallelism on the main thread and worker threads. With autoSelectMode (default) and no useWorkerThreads, it computes a complexity score from 0 to 100:
| Factor | Weight | |-|-| | File count (2000 files = 100) | 30% | | Estimated lines of code (1M = 100) | 35% | | Large files over 50 KB | 20% | | Current heap usage above 70% | 15% |
| Score | Mode |
|-|-|
| below 35 | Promise mode |
| 35 to below 50 | Promise mode with memory monitoring; aborts when heap usage reaches 85% and falls back to workers if workerScriptPath is set |
| 50 and above | Worker threads |
Overrides:
useWorkerThreads: trueuses workers when there are at leastminFilesForWorkersfiles (default 200), otherwise Promise modeuseWorkerThreads: falsealways uses Promise modeautoSelectMode: falsewithoutuseWorkerThreadsuses Promise mode
If worker execution fails, the executor terminates the pool and falls back to Promise mode.
Details: docs/architecture/parallelism.md.
Limits
| Setting | Default |
|-|-|
| Workers (WorkerPool) | CPU cores - 1, at least 1 |
| Heap per worker (WorkerPool, workerHeapLimit) | 512 MB old generation; ParallelExecutor sets 4096 MB |
| Task timeout (defaultTimeout / timeout) | 30000 ms, counted from the moment a worker is ready |
| Wait for the first ready worker | 120 s |
Each worker loads the full project, so memory grows with the number of workers. A worker that hits its heap limit is ended by Node.js; lower maxWorkers or raise workerHeapLimit for large projects. Workers do not report their heap usage, so the pool cannot recycle them based on real memory use.
Data crossing the thread boundary is copied with the structured clone algorithm. Use plain objects, primitives and arrays: functions and Symbols cause a DataCloneError, class instances arrive as plain objects without their methods. WorkerTask.config must follow this rule.
Build Order
This package builds after all plugins in the monorepo:
1. core
2. plugin-system
3. api
4. plugin-analyzer, plugin-solid, plugin-angular, plugin-standalone, plugin-architecture, orchestration
5. worker (this package)
6. adapter-mcpTesting
The package has no tests of its own. The worker pool tests live in @angular-modernizer/core and are skipped unless ENABLE_WORKER_TESTS=true is set and the worker script is built:
pnpm run build
ENABLE_WORKER_TESTS=true pnpm --filter @angular-modernizer/core testDependencies
@angular-modernizer/core-WorkerTask,WorkerResult@angular-modernizer/api-createPublicApifor each worker@angular-modernizer/plugin-system-ContextFactory, rule contracts@angular-modernizer/plugin-analyzer,plugin-solid,plugin-angular,plugin-standalone,plugin-architecture- rules run in workersts-morph- project loading
See Also
- packages/core/README.md -
WorkerPool,ParallelExecutor - packages/plugin-system/README.md - Plugin contracts
