-
-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
albertoodev edited this page Aug 21, 2026
·
8 revisions
SPM exposes one public library, package:spm/spm.dart, for Flutter code that uses SpmState. The
CLI remains internal under lib/src/. Its five feature modules separate data access, domain logic,
and command wiring with a data/ → domain/ → presentation/ layout.
lib/
├── spm.dart # public Flutter integration API; exports SpmState
└── src/
├── core/
│ ├── constants/
│ ├── errors/ # failures.dart: 20 typed Failure subclasses + CompoundFailure
│ ├── injection/ # static service locators (AnalysisDI, IsolationDI, InjectionDI, ValidationDI…)
│ ├── loggor/ # SpmLogger (use instead of print)
│ └── types.dart # shared type aliases (Result<T>, AsyncResult<T>, JsonRecord, …)
│
└── features/
├── analysis/ # static AST analysis → analyze
├── injection/ # code transformation + app execution → inject / run
├── isolation/ # rebuild-scope extraction → isolate
├── profiler/ # runtime-side API imported by the instrumented app
└── validation/ # structural-mutation gate → validate
Each feature follows the same three-layer layout:
<feature>/
├── data/
│ ├── data_sources/
│ ├── models/
│ └── repositories/
├── domain/
│ ├── entities/
│ ├── repositories/
│ └── use_cases/
└── presentation/ # one Command subclass, wiring CLI args → use cases via the feature's DI
| Feature | Command | Responsibility |
|---|---|---|
| analysis | analyze | Walks Dart files, finds every rebuild scope (the same kinds isolation detects), extracts build-tree metrics from each scope's body via feature sets → specialized extractors and visitors. TreeExtractor returns an ExtractionSet, pairing the feature set with the closure of files it read, so each row can report its dependencies and any it could not resolve. Streams AnalysisResultEntity records as JSONL. --scope-types narrows the emitted kinds. |
| isolation | isolate | Finds rebuild scopes (State, ConsumerWidget/HookConsumerWidget, builder patterns like BlocBuilder/Consumer/Selector/Obx/GetX/Observer) and transplants each into a self-contained .dart file, grouped by type, lifting the bindings the scope closed over onto the generated State and formatting the output directory. Dependencies that build UI are inlined whole, and a widget arrives with the base class it extends, since a broken supertype chain would change what analyze counts. Everything else, meaning models, services, constants and third-party symbols, gets a declaration-only stand-in from data/data_sources/emitters/shim_emitter.dart, which mirrors the original's supertype so widget-versus-value-object classification survives. References the analyzer resolved to nothing, an extension behind a refused import or anything at all in a project whose pub get failed, are rebuilt from their call sites by emitters/synthetic_shim_emitter.dart. Imports are gathered by emitters/import_collector.dart, keyed by URI and prefix so an as clause survives, and gated to dart: and package:flutter/ by helpers/sdk_uris.dart. verifier/output_verifier.dart then analyses the written files in process, prunes unused imports, and reports per-file diagnostics into the mapping JSONL. DI class: IsolationDI. The scope-kind lists and predicates live in AppConstants.rebuildScopeTypes and analysis/.../extensions/state_class_detector.dart, shared with the analysis feature. |
| injection | inject, run | Reads the analysis JSONL, skips rows whose scopeType is not State, and rewrites each target State class to extends SpmState<T> (adding an instanceId getter and the SpmState import); modes inject / remove. run then launches the app and connects to its Dart VM Service over WebSocket to collect ext.spm.profiler events. |
| profiler | (imported by the app) | Runtime-side API. SpmState.setState picks SpmProfiler.monitor() under --profile or SpmProfiler.monitorDataFlow() under --debug, measuring frame timings and rebuild counts and emitting events back to SPM over the VM Service. |
| validation | validate | Static gate deciding whether a mutation is a clean structural variant of a base file. DI class: ValidationDI. See AST Internals. |
-
types.dartholds all shared type aliases. The key ones areResult<T> = Either<Failure, T>,AsyncResult<T>,StreamResult<T>,AsyncVoidResult = AsyncResult<void>,AsyncVoid = Future<void>(data layer, may throw), andJsonRecord = Map<String, dynamic>. Always use these aliases; never spell out the full types inline. -
errors/failures.dartdefines 20 typedFailuresubclasses (e.g.InjectionFailure,RunAppFailure,FlutterAnalyzeFailure);CompoundFailureaggregates multiple failures. -
injection/holds static service locators built on lazy singleton getters (??=), with no external DI framework. Each DI class exposes areset()to clear all singletons; call it insetUp/tearDownwhen testing through the DI layer. -
loggor/logger.dartprovidesSpmLoggerfor stdout/stderr. Use it instead ofprint.
-
lib/spm.dartis the supported public import. It exportsSpmState; all other implementation libraries live underlib/src/. -
bin/spm.dart→lib/src/runner.dart(SpmRunner, aCommandRunnerwith the featureCommandsubclasses registered). - Each feature's
presentation/contains oneCommandsubclass wiring CLI args to use cases via the feature's DI singleton.
Either<Failure, T> is used throughout the data and domain layers (Left = error, Right =
success). Data sources may throw domain-specific exceptions; repositories translate those into
typed failures. Presentation commands fold results and return a non-zero status on failure, and
bin/spm.dart terminates the process with that returned status.
| Package | Role |
|---|---|
analyzer |
Dart AST parsing and semantic resolution. |
dartz |
Either type for functional error handling. |
vm_service |
Connect to the Dart VM to collect profiler events. |
web_socket_channel |
WebSocket transport to the VM Service. |
args |
CLI argument parsing. |
path |
Cross-platform path utilities. |
Commands
Reference
Internals
Contributing