Generated code reference¶
What diana_generator emits, where, and under which names. Generated files
start with // GENERATED CODE - DO NOT MODIFY BY HAND and suppress lints
with // ignore_for_file: type=lint; they are analyzed for errors like
any other code. Output is deterministic — rebuilding unchanged sources is
byte-identical, so generated files can (and should) be committed.
The three builders¶
| Builder | Input | Output | Visible? |
|---|---|---|---|
diana_metadata |
every lib/**.dart with Diana annotations |
<file>.diana.json |
no (build cache) |
diana_part |
libraries needing local code | <file>.diana.dart (part) |
yes (source tree) |
diana_bootstrap |
the @DianaApplication library |
<file>.diana_app.dart |
yes (source tree) |
They are auto-applied when diana_generator is in dev_dependencies — no
build.yaml needed in applications.
Part files (<file>.diana.dart)¶
A library gets a part file when it declares any of:
- a
@RestControllerwith routes → route adapters - a class with validation constraints (or referenced via
@Valid) → validators - a
@Jsonclass → JSON codecs ($XFromJson/$XToJson) - a
@ConfigurationPropertiesclass → config binders
The library must contain part '<file>.diana.dart'; — the build fails
with the exact line to add when it's missing. Libraries with only
injectables/middleware/advices don't need one.
Route adapters¶
One public function per route method:
$diana$<ControllerName>$<methodName>
Future<DianaResponse> Function(<ControllerName> c, DianaRequest req)
The adapter performs, in order: parameter binding → body decoding → DTO
construction (fromJson) → validation (if the DTO has constraints) →
method invocation (awaited if Future) → result serialization (with
@ResponseStatus applied).
Variations by annotation/return type:
@Consumesroutes decode throughdecodeMapBody/decodeListBodywith the declared MIME list instead of the JSON-only helpers.@Producesroutes serialize throughnegotiatedResponsewith the declared MIME list instead ofDianaResponse.json.Streamreturns compile toreturn sseResponse(req, result);—Stream<Dto>elements are first mapped throughServerSentEvent.json($<Dto>ToJson(e)).@Principal()parameters bind viabindPrincipal<T>(req)(exported frompackage:diana, so the part file needs nodiana_authimport).
Adapters are public because the bootstrap library (a different library)
references them; treat the $diana$ prefix as internal.
For providers with @Transactional methods the composition root also emits
a $Tx$<Provider> proxy subclass (in the app library) overriding each such
method to wrap super.method(...) in Database.runTransactional; the graph
injects the proxy in place of the raw class.
Providers with @Timed/@Cacheable/@Retryable/@CircuitBreaker methods
get a $Aspect$<Provider> proxy instead (its @Transactional methods fold
in as the innermost layer): the constructor prepends a DianaObserver?
(when @Timed is present — the observability provider, or null without
one), a DianaCacheStore (when @Cacheable is present and the app
declares a store provider), and the Database (when @Transactional is
present), in that order. Each retried/guarded method carries a
_retry$m/_breaker$m state field; each cached method carries either a
_cache$m MethodCache field (built-in cache) or a static const _spec$m
CacheSpec routed through _cacheStore.run(...) (pluggable store). Every
override nests the aspect runtimes in the fixed order Timed → Cacheable →
CircuitBreaker → Retryable → Transactional. Generic methods re-declare
their type parameters on the override.
Abstract query repositories (extends DianaRepository<E>) get a
$Repo$<Provider> implementation: the constructor forwards the user's
parameters (the Database among them), the four CRUD methods are emitted
against the entity's @Table, and each parsed query method compiles to a
single fluent chain (db.table(...).where(...).orderBy(...)) with rows
mapped through the entity's $EFromJson. The graph constructs the
implementation in place of the abstract class.
When the app uses STOMP, the context gains a late final stompBroker =
StompBroker(eventBus, config); field (STOMP reuses the event bus for
cross-isolate relay, so eventBus is emitted too), and the constructor body
registers one stompBroker.registerMapping(...) per @MessageMapping
method (decoding the payload and encoding the @SendTo reply inline).
StompEndpoint subclasses and StompPublisher injections receive the
broker; the endpoint is registered like any WebSocket controller.
Validators¶
One public class per constrained DTO:
One check* call per constraint, plus mergeNested blocks for @Valid
cascades. Public so adapters in other libraries (controllers using the
DTO) can invoke them.
JSON codecs¶
One public pair per @Json class:
$<ClassName>FromJson(Map<String, dynamic>) → <ClassName>
$<ClassName>ToJson(<ClassName>) → Map<String, dynamic>
Route adapters call these automatically (wrapped in decodeDto, so shape
mismatches surface as 400s).
Config binders¶
One public function per @ConfigurationProperties class:
The composition root (<file>.diana_app.dart)¶
A standalone library generated next to the @DianaApplication class. All
imports are prefixed (_i1, _i2, …) and managed automatically.
DianaContext¶
The DI container. Contents, in topological (dependency) order:
final DianaConfig configandfinal DianaOverrides _overrides(constructor parameters)- one
late final <Type> <camelCaseName>field per singleton provider — wired as_overrides.x ?? RealX(...)— including controllers, middleware, advices, and@ConfigurationPropertiesclasses (initialized via their generated binder). Name collisions are disambiguated with$1,$2, … suffixes. - one
<Type> create<Type>()factory method per transient provider. - when the app uses events: a
final DianaEventBus eventBus = DianaEventBus();field, with oneeventBus.on<EventType>((e) => provider.method(e));registration per@EventListenerin the constructor body (closures, so no provider is constructed eagerly), andDianaEventPublisherconstructor parameters satisfied witheventBus(or_ctx.eventBuson the request scope). - when the app uses observability:
DianaServer, the event bus, the scheduler andDatabasereceiveobserver: <the DianaObservability provider field>, and@Scheduledtasks are emitted with aname:. - when the app uses persistence: a
late final Database database = Database(connector, config);field (frompackage:diana_data), opened (and migrated) at the start ofinitialize()and closed at the end ofdispose();Databaseconstructor parameters are satisfied with it. Future<void> initialize()— constructs every singleton in dependency order and runs@PostConstructhooks.Future<void> dispose()— runs@PreDestroyhooks in reverse order.
DianaOverrides¶
One nullable final field per provider (typed by the concrete class),
const constructor — the test-override surface (see
lifecycle guide).
DianaApp¶
context + server + initialize()/dispose(). When @Scheduled
methods exist, it also owns a DianaScheduler (late final, registrations
in the initializer, perIsolate: true forwarded from the annotation)
started by initialize() and stopped first in dispose() — the scheduler
itself skips leader-only tasks in worker isolates. When @GrpcService classes exist, it owns a
DianaGrpcServer (from package:diana_grpc) holding every service
instance from the context, served by initialize() and shut down first in
dispose() — exposed as app.grpcServer.
DianaRequestScope¶
Emitted only when request-scoped providers exist. Holds a reference to the
DianaContext plus late final fields for request-scoped providers (and
factory methods for transients that depend on them). A fresh scope is
created per request for request-scoped controllers.
_mapException¶
Emitted when @ControllerAdvice classes exist: a private function chaining
if (e is X) return ctx.advice.handler(e[, req]); checks, most specific
exception type first, ending in return null; (fall through to built-in
handling).
Public functions¶
DianaContext buildDianaContext({DianaConfig? config, DianaOverrides overrides});
Future<DianaApp> buildDianaApp({DianaConfig? config, DianaOverrides overrides});
Future<DianaServer> buildDianaServer({DianaConfig? config, DianaOverrides overrides});
Future<void> runDiana({DianaConfig? config, DianaOverrides overrides});
const String dianaOpenApiJson;
buildDianaApp:
config ?? await DianaConfig.load('<configFile>')(profile overlays included)- constructs
DianaContext(cfg, overrides) - constructs
DianaServerwith the order-sorted global middleware, the advice chain and any discoveredBodyCodecsingletons (bodyCodecs: [...], emitted only when codecs exist) - registers actuator endpoints (guarded by
management.enabled) and the OpenAPI route (guarded byserver.openapi.enabled) - registers every route:
server.register(HttpMethod.x, '/full/path', (req) => $diana$Ctrl$method(ctx.ctrl, req), middleware: [...], cors: ...)— controllers sorted by type, routes in declaration order. When a route (or its controller) carries@Secured/@RolesAllowed, aconst SecuredGuard()/RolesGuard([...])frompackage:diana_authis appended last inmiddleware:(runs after authentication, before the handler) - registers every WebSocket endpoint (sorted by path) with its handshake
middleware:
server.registerWebSocket('/ws/x', () => ctx.handler, middleware: [...])
runDiana wraps buildDianaApp in DianaRuntime.run (initialize → serve
→ graceful shutdown → dispose), passing eventBus: app.context.eventBus
to DianaRunnable when the app uses events (enables the cross-isolate
broadcast bridge).
Interop with other generators¶
Diana uses the .diana.dart suffix (not .g.dart) precisely so it
composes with source_gen-based generators like json_serializable — a
library can have both part 'x.diana.dart'; and part 'x.g.dart';, and
one build_runner invocation drives all builders.
Determinism guarantees¶
- All emission is sorted (providers topologically then by name, controllers
by type, global middleware by
(order, type), summaries by path). - No timestamps or random values are ever emitted.
- Optional features (
eventBus,grpcServer,scheduler,bodyCodecs, auth guards, the gRPC auth interceptor, thedatabasefield,$Tx$/$Aspect$proxies, and the observability wiring //actuator/metrics) are emitted only when used — an app without them produces byte-identical output before and after a generator upgrade that adds the feature. - CI can enforce freshness with
git diff --exit-codeafter a build.