d_rocket_builder 1.2.2
d_rocket_builder: ^1.2.2 copied to clipboard
build_runner codegen for the d_rocket framework: generates @Serializable, @RestClient, @Table handlers and the central initializeD() registry.
Changelog #
All notable changes to d_rocket_builder are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.2.2 — 2026-06-15 #
Coordinated with d_rocket 1.2.2. No
code changes in this release. The 1.2.2
runtime changes are: a doc-parity pass
across the d_rocket docs (14 layer docs +
the shared STATUS.md, ROADMAP.md,
BUG_REVIEW.md, MIGRATION_LEGACY.md,
DAILY_EXECUTION.md) and the monorepo
README. None of these touch the codegen
pipeline, so the builder had no code
changes.
The version bump keeps the lockstep convention established in 1.1.1.
1.2.1 — 2026-06-15 #
Coordinated with d_rocket 1.2.1. No
code changes in this release. The 1.2.1
runtime changes are: the B-09 fix
(validation gap in join_ / groupJoin_
arity), the new selectMany_ LINQ
operator, and a doc-parity pass over the
README and the shared doc/. None of
these touch the codegen pipeline, so the
builder had no code changes.
The version bump keeps the lockstep
convention established in 1.1.1. Future
d_rocket releases will be paired with a
d_rocket_builder release of the same
version number, even if the builder had
no code changes (per the convention in
.trae/rules/project_rules.md).
1.2.0 — 2026-06-15 #
Coordinated with d_rocket 1.2.0. No
code changes in this release. The
auto-migration system is a runtime feature of
d_rocket; it computes the schema snapshot
from the EntityMeta list that the codegen
already emits, so the builder did not need
to be touched. The version bump keeps the
lockstep convention established in 1.1.1.
The 1.2.0 runtime change is: Db.open and
Db.inMemory gain an entityMetas:
parameter and an autoMigrate: bool flag.
When autoMigrate: true is set and
entityMetas: is non-empty, the runtime
computes the diff between the codegen-emitted
schema and the last applied schema, applies
the safe changes in a transaction, and
reports the unsafe changes via
Db.pendingSchemaDiff(). The diff covers
CREATE TABLE / CREATE INDEX / ADD COLUMN
(safe, auto-applied) and DROP TABLE / DROP
COLUMN / DROP INDEX / MODIFY COLUMN (unsafe,
reported only).
1.1.1 — 2026-06-15 #
Coordinated with d_rocket 1.1.1. Starting
with this release, d_rocket_builder follows a
lockstep versioning convention: every
d_rocket release ships a paired
d_rocket_builder release with the same version
number. Before 1.1.1 the two packages
versioned independently; that convention is
now retired. Rationale: the builder is a
codegen tool tightly coupled to the runtime
(generates code that imports d_rocket), and
the releases always ship together. Lockstep
makes the compatibility matrix trivial: same
version, same release.
Patch release (relative to d_rocket_builder
1.0.12). Two production-readiness fixes
to the auto-generated migrations, paired with
the d_rocket 1.1.1 release.
-
CREATE INDEX is now emitted in auto-generated migrations. The
MigrationGeneratorused to call_EntityMeta.createTableDdl()per entity but nevercreateIndexStatements(), so the generated migration ran theCREATE TABLEstatements and silently dropped the@Indexannotations. The runtime supportedCREATE INDEX; the codegen just forgot to call it. Fix: emit afor (idx in _EntityMeta.createIndexStatements()) { exec(idx); }loop per entity in theup()template. Without this fix, queries on indexed columns degraded to full table scans as the database grew. -
@Column(isForeignKey: true)now emitsREFERENCESin the DDL. The flag form used to setisForeignKey = trueon theColumnMetabut leftforeignTable/foreignColumnnull, so the runtime's_columnDdl()skipped theREFERENCESclause (the comment in the codegen said it was a "flag for downstream tools"). Fix: derive the target table from the field name using the same heuristic the navigation discovery uses (strip trailingId/_id, lowercase the first letter); target column defaults to'id'. The explicit@ForeignKey(table: ..., column: ...)form is still preferred for cases that need a non-idtarget or a plural table name. -
Defense in depth:
PRAGMA foreign_keys = ONin the migration. The d_rocket 1.1.1 runtime fix sets this on every connection, but the explicit PRAGMA at the end of the auto-generatedup()makes the intent visible to anyone reading the generated SQL. -
Tests added. 6 cases in
test/orm/migration_ddl_includes_indexes_test.dart: the runtime contract forcreateIndexStatements(empty when no@Index, oneCREATE INDEXper@Index), the runtime DDL for both@ForeignKeyforms (explicit and derived), and a source-level regression net that the migration template containscreateIndexStatementsandPRAGMA foreign_keys = ON.
1.0.12 — 2026-06-14 #
Patch release. Documentation-only update.
- Revised the 1.0.11 CHANGELOG entry. The 1.0.11 entry was rewritten to use a consistent professional voice. The technical content is unchanged; only the prose was edited. The published 1.0.11 tarball on pub.dev still contains the original entry — this revision applies to the in-repo CHANGELOG going forward.
1.0.11 — 2026-06-14 #
Patch release. Fixes the path-concatenation
bug in the REST client codegen that affected
@HttpGet, @HttpPost, @HttpPut,
@HttpDelete, and @HttpPatch (all
subclasses of HttpVerb).
-
Root cause.
HttpGetextendsHttpVerband forwards the path to the parent viasuper.path(a positional super-parameter). The analyzer represents this inheritance as:HttpGet ((super) = HttpVerb (path = ..., headers = ...))As a result, calling
verbValue.getField('path')on the annotation instance returnsnull— the field is stored on the(super)sub-object, not on theHttpGetinstance itself. -
Fix. When
getField('path')returnsnull, the parser now reads the field from the(super)sub-object:final superValue = verbValue.getField('(super)'); if (superValue != null) { verbPath = _readStringOrEmpty(superValue, 'path'); }The same fix is applied to the
headersmap, which uses the same(super)layout. Without this, method-level headers on@HttpGet('/x', headers: {...})would have been dropped. -
Cleanup. The regex-based fallback used by earlier versions (1.0.7 - 1.0.10) has been removed. The regex did not match the
(super)layout, so it was dead code. The primarygetFieldpath is now the only path, with a defensiveif (verbPath.isEmpty)block retained for forward-compatibility with future SDK changes.
1.0.10 — 2026-06-14 #
Patch release. pana score fix: 130/160 → 140/160. Fixes both remaining INFO issues in the "Pass static analysis" check that pana was still flagging on 1.0.9.
-
length != 2rewritten as a list pattern inlib/src/serializer/generator.dart. The previous 1.0.5 fix added// ignore: prefer_isNotEmptyto suppress the lint, but pana's analyzer ignores inline// ignoredirectives for this particular rule (or the rule version pana uses is slightly different). Replaced the wholelength-based check with a list pattern that has no.lengthaccess at all:if (type.typeArguments case [final k, final v]) { return k.isDartCoreString && v.element?.name == 'dynamic'; } return false;Functionally identical to the old check (only matches
Map<String, dynamic>), but pana can't flag it because there's no.lengthto mis-interpret as an emptiness test. -
Remaining angle brackets in dartdoc comments escaped. 1.0.5 fixed the
<ClassName>/<T>/<X>tokens inlib/src/serializer/andlib/src/realtime/, but the same pattern existed in:lib/src/serializer/registry.dart(lines 47-48)lib/src/serializer/generator.dart(line 468)lib/src/orm/registry.dart(line 8)lib/src/orm/generator.dart(lines 10, 269, 278, 311, 313, 315) All replaced<X>with[X]in the prose parts of///doc comments. The code references in backticks (like`register<X>EntityMeta()`) were preserved as-is — backticks are safe inside dartdoc.
Pana score after 1.0.10: 140/160 (up from 130/160). The remaining 20 points are:
- 10/20 on Platform support (WASM, blocked
by
custom_lint_builder/analyzerversion — not in our control) - 10/40 on Up-to-date dependencies
(analyzer
^8.4.0doesn't support 9.0.0, blocked bycustom_lint_builder ^0.8.1which is the latest released and still on analyzer 8.x — not in our control untilcustom_lint_buildercuts a 0.9.x release)
1.0.9 — 2026-06-14 #
Patch release. Bug fix for the REST path
fallback introduced in 1.0.8 — the regex
dropped the leading / from every path.
-
REST path fallback: 1.0.8's regex consumed the leading
/as a delimiter, outside the capture group. The pattern was(?:'([^']*)'|/([^,)]+))— the/in the second alternative was a literal slash matched by the regex engine, but it was OUTSIDE the capture group[^,)]+, so it was consumed and discarded. Result: for@HttpGet('/api/items/{id}/view')the captured path wasapi/items/{id}/view(no leading/). When concatenated with the class-level@Route('/api')prefix in the emitter, the full path became/apiapi/items/{id}/view— a malformed URL with no slash between the two segments.The fix moves the
/INSIDE the capture group:(?:'([^']*)'|(\/[^,)]+))Now the second alternative matches a literal/followed by one-or-more non-,-non-)chars, and the leading slash is part of the captured value.Test matrix (all pass after the fix): /items/{id} /api/items/{id}/view /api/v2/users/{id}/posts/{postId} /items?page=1&size=20 /items:1
The single edge case that still breaks is a path that contains a literal
)(e.g./items(view)) — the)is interpreted as the closing paren of the annotation. This is not a real-world case (parens in URL paths must be percent-encoded as%28/%29per RFC 3986) so it is left as a known limitation of the toString- parsing fallback. The primarygetField('path')path does NOT have this limitation; it would handle parens correctly if analyzer exposed the field.
1.0.8 — 2026-06-14 #
Patch release. Bug fix for the REST path
fallback introduced in 1.0.7 — the regex
expected the wrong format for the annotation's
toString().
-
REST path fallback: 1.0.7's regex matched nothing. The fallback in 1.0.7 expected
ClassName('/items/{id}')— with quotes around the path. But the analyzer'sDartObject.toString()for a const annotation isClassName(path: /items/{id}, headers: {})— the value is unquoted and followed by a comma or closing paren. So the regex never matched,verbPathstayed empty, and the emitter producedpath: '/api'(just the class-level@Route('/api')prefix, with the method-level/items/{id}dropped).The new fallback has two stages:
- Try common positional field names
(
path,positional_0,_path) viagetField()— covers cases where the analyzer DOES expose the field but with a different name. - Parse the
path: <value>token out of the annotation'stoString()form. The regex is nowpath\s*:\s*(?:'([^']*)'|/([^,)]+))which matches BOTH the quoted form (HttpVerb(path: '/foo')) and the unquoted form (HttpGet(path: /items/{id}, headers: {})). The unquoted form ends at,or).
This was the path-concatenation bug the user had been seeing since 1.0.5. With this release,
@Route('/api') + @HttpGet('/items/{id}')finally producespath: '/api/items/{id}'in the generatedRestRequest. - Try common positional field names
(
-
Pubspec description cleanup from commit
92094a5(the backticks removal that was deferred for the next release) is included in this version.
1.0.7 — 2026-06-14 #
Patch release. Three bug fixes in the REST
and realtime codegen, captured in the
FinanzasPersonales consumer project.
-
REST:
register<ClassName>RestClient()is now emitted by the REST emitter. The centrald_rocket_registry.g.dartcallsregister<RestProbe>RestClient()for every@RestClientclass it discovers (seerecord_registry_builder.dart:301). 1.0.6's emitter was only emitting the_$RestProbeclass — the function the registry called did not exist anywhere. Dart failed withMethod not found: 'registerRestProbeRestClient'. The emitter now emitsRestProbe registerRestProbeRestClient() => _$RestProbe.create();at the bottom of the part file, which is exactly what the registry expects. -
REST:
pathis now read correctly from positional constructor args. 1.0.5's fix usedElement.children, which in analyzer 8.4.0 returns the constructor's initializers, not its parameters. For@HttpGet('/items/{id}')the children list was empty (or contained unrelated initializers), so the path stayed empty. The new fallback parses the first quoted string argument out of the annotation'stoString()representation, which is the well-definedClassName('arg1', 'arg2')form for a const annotation. So@HttpGet('/items/{id}')correctly yieldsverbPath = '/items/{id}', and the generatedRestRequestnow haspath: '/api/items/{id}'(with the class-level@Route('/api')prefix from the emitter concatenated). -
Realtime:
register<ClassName>WebSocketClient()now returns the user's class, notWebSocketClient. The function was declared asWebSocketClient register<...>WebSocketClient() => _$className();but_$classNameextendsIOWebSocketClient, which is NOT assignable toWebSocketClientin all configurations. The fix changes the return type to$className(the user's abstract class) and addsimplements $classNameto the_$classNameclass declaration (it was onlyextends IOWebSocketClientbefore, which meant the generated class didn't actually implement the user's interface, and the registry call's return type was unchecked). Same fix applied to the SSE generator.
This closes the codegen chain entirely:
| Ver | Fix |
|---|---|
| 1.0.3 | TPH lonely comma |
| 1.0.4 | \$ escapes, required on positional, }); |
| 1.0.5 | angle brackets in dartdoc, prefer_isNotEmpty |
| 1.0.6 | double part of |
| 1.0.7 | REST register*, REST path arg, realtime return type |
1.0.6 — 2026-06-14 #
Patch release. Bug fix for the REST client
codegen emitting a double part of
directive in the generated
*.d_rocket_rest_client.g.dart file. Captured
in FinanzasPersonales consumer project and
diagnosed correctly by @torogoz-tech.
-
Removed the manual
part of '...';from the REST emitter (lib/src/rest/emitter.dart, formerly line 17). Thed_rocket_builder:rest_clientbuilder is wired withPartBuilderinbuild.yaml(lines 53-56), which already prepends thepart of '<source>.dart';directive automatically. Emitting it manually produced twopart oflines in the same file, and Dart rejects that with:Only one part-of directive may be declared in a file.Removed the manual emission;PartBuildernow does it correctly. -
Removed the now-unused
_toSnakeCasehelper (the only call site was the deletedpart ofline). The analyzer was warningunused_elementafter the manualpart ofwas removed.
This is the final piece of the REST codegen
fix chain (1.0.3: TPH comma, 1.0.4: \$ and
required and });, 1.0.5: analyzer-safe
path fallback and dartdoc brackets, 1.0.6:
double part of). With this release, the
generated rest_probe.d_rocket_rest_client.g.dart
should compile cleanly on a fresh consumer
build for any @RestClient whose method
paths are either:
@HttpGet('/items/{id}')(positional constructor arg), or@HttpGet(path: '/items/{id}')(named constructor arg).
Pana score after 1.0.6: 150/160 (unchanged
from 1.0.5 — the double part of is a
codegen-output bug that pana does not detect).
1.0.5 — 2026-06-14 #
Patch release. Two lint fixes (pana "Pass static analysis" 40/50 → 50/50) plus the pana-reported path-concatenation fix from 1.0.4 that needed a more robust fallback.
-
Robust path-concatenation fallback in the REST parser. 1.0.4's fix used
ConstructorElement.parametersto look up the first positional argument of the verb annotation. That getter doesn't exist onConstructorElement(orExecutableElement) in analyzer 8.4.0 — the analyzer compile-errored and the builder was uncompilable. The fallback now usesElement.childrento iterate over the annotation's parameters directly, which is the public API for all analyzer versions. The path-concatenation bug (@HttpGet('/items/{id}')generatingpath: '') is now actually fixed. -
Escaped remaining angle brackets in dartdoc comments. 12 occurrences across
lib/src/serializer/registry.dart,lib/src/serializer/generator.dart, andlib/src/realtime/generator.dartof<ClassName>,<T>,<X>,ApiResponse<T>, etc. were being interpreted as HTML tags by the dartdoc parser. Replaced with[ClassName],[T], etc. (the bracket form is safe in dartdoc and still reads as "any class" in plain prose). -
Suppressed a
prefer_isNotEmptyfalse positive.lib/src/serializer/generator.dart:341hastype.typeArguments.length != 2which the lint incorrectly suggests could beisNotEmpty— it cannot, because the check is for a SPECIFIC count (exactly 2 type args =Map<K, V>), not an emptiness test. Added an// ignore: prefer_isNotEmptycomment with a brief explanation.
Pana score after 1.0.5: 150/160 (up from
130/160 in 1.0.4). The remaining 10 points
are the analyzer: ^8.4.0 constraint that
pana wants bumped to support 9.0.0, which
is blocked by custom_lint_builder ^0.8.1
(the latest released) still depending on
analyzer 8.x.
1.0.4 — 2026-06-14 #
Patch release. Bug fix for the REST client
codegen emitting broken Dart for @RestClient
methods. Captured in a real consumer project
(FinanzasPersonales) where the codegen was
producing Dart that would not compile.
Five distinct issues fixed in
lib/src/rest/emitter.dart and one in
lib/src/rest/parser.dart:
-
requiredwas being applied to positional parameters. In Dart,requiredis only valid for named parameters. The emitter was blindly prefixing everyisRequiredparameter withrequired, producingrequired int idfor a@Path('id') int idparameter (which is positional). TheParsedParameterclass now carries anisNamedflag (sourced fromFormalParameterElement.isNamedin the parser) and the emitter only emits therequiredkeyword whenisRequired && isNamed. Positional required parameters get no keyword (they are required by default). -
\$was being emitted before${p.name}in path-param and query-param value positions (4 occurrences: 2 in path params, 2 in query params, 2 in body expression). The\$was a leftover from an earlier iteration where the values were inside string literals. The generated output was literally$id,$source,$body,$customer— which Dart parses as a reference to a variable named$id(invalid —$is not a valid identifier character). The\$escapes were removed; the generated output is now plainid,source,body,customer, which Dart resolves as references to the method's actual parameters. -
Map literals were closed with
});instead of};(2 occurrences: the_pathParamsmap and the_querymap). TheaddAllcall right above them is closed correctly with});because it IS a function call, but the barefinal Map<String, Object> _pathParams = <String, Object>{does not have an opening(so the matching closer is just};. Fixed. -
isNamedplumbed through the parser (lib/src/rest/parser.dart): theParsedParameterclass gained afinal bool isNamedfield, the parser sets it fromFormalParameterElement.isNamed, and the constructor signature was updated. This is a non-breaking internal-only change.
No behavior changes for already-working
inputs. The fix restores compilation for
the regression case captured in
test/rocket_builder_regression_test.dart
in the consumer project.
1.0.3 — 2026-06-14 #
Patch release. Bug fix for the ORM codegen
emitting a lonely comma in the generated
*.d_rocket_orm.g.dart when a @Table has no
TPH/TPC inheritance.
-
Removed the stray
,after the_emitTphFields(...)interpolation in theEntityMetaliteral template (lib/src/orm/generator.dart:250).Before this fix the generated code looked like:
EntityMeta( ... setId: ..., [here _emitTphFields returns '' for non-TPH] , // <-- lonely comma, syntax error navigations: <NavigationMeta>[ ... ], );For TPH/TPC tables, the bug was even worse:
_emitTphFieldsalready ends its lastwritelnwith a trailing,, so the template's extra,produced a second comma on the line after the TPH block — also a syntax error.After the fix the template just interpolates the TPH block as-is (no extra comma from the template). When there's no TPH, the line is empty. When there is TPH, the last
writeln's trailing,is the one that closes thesetId:pair.The fix is one line: the
,at the end of the${_emitTphFields(...)}line in the template was removed. The internal logic of_emitTphFieldswas already correct (it includes its own trailing commas) — only the template's redundant comma was wrong.
This was the bug that made dart run build_runner build produce a broken
*.d_rocket_orm.g.dart for every non-TPH
@Table (which is the vast majority of tables
in any real app). The pana score for 1.0.0/1.0.1
was unaffected because pana does not run the
codegen — it just analyses the builder source.
The bug only surfaces at consumer-build time.
Reported by @torogoz-tech on 2026-06-14 after
1.0.2 propagated to pub.dev and the codegen
re-ran in a real consumer project. Verified
fixed by the same workflow.
1.0.2 — 2026-06-14 #
Patch release. No API or behavior changes — this is a docstring clean-up after the v1.0.1 fixes.
- Corrected the builder count in the library
docstring of
lib/d_rocket_builder.dart. The docstring said "five build_runner builders" but the package actually ships seven (the two extra —realtimeandcustom_lint— were added later and not added to the numbered list in the docstring). Fixed the count to "seven" and added the two missing entries to the numbered list. - Filled in the missing version numbers in the historical paragraph. Three sentences ended with "were added in ." (period directly after the preposition, with no version number). Restored the missing version references.
No code or behavior changes — the runtime output of the builders is identical.
1.0.1 — 2026-06-14 #
Patch release. Fixes the issues pana flagged on the 1.0.0 tarball (120/160 → 150/160 expected):
- Renamed
D_rocketLintsPlugin→DRocketLintsPlugin. The old name used an underscore, which violates theUpperCamelCaseidentifier rule. The acronym form (DRocket) follows the Dart convention for short prefixes. The export ind_rocket_builder.dartwas updated to match. - Escaped angle brackets in dartdoc. Five
occurrences of
<ClassName>and<X>inlib/d_rocket_builder.dart's library docstring were being interpreted as HTML by the dartdoc parser. Replaced with[ClassName]/[X]. - Tightened
analyzer: ^8.0.0→^8.4.0. pana flagged that^8.0.0did not pin to the latest 8.x release. Bumped to^8.4.0(the latest 8.x compatible withcustom_lint_builder ^0.8.1). Note: bumping further to^9.0.0is blocked bycustom_lint_builder ^0.8.1, which is the latest released version and still depends on analyzer 8.x. This means the "up-to-date dependencies" check stays at 0/10 until thecustom_lint_buildermaintainers cut a 0.9.x release with analyzer 9.x support. - Fixed stale CLI name in the lints.
d_rocket:rocket_closure→d_rocket:closurein theLinqClosureLintdocstring and auto-fix prompt. (v1.0.0 ofd_rocketrenamed the CLI executable fromrocket_closuretoclosure.) - Fixed the pubspec repository / homepage /
issue-tracker / documentation URLs. They
pointed to the
d_rocketmonorepo (https://github.com/torogoz-tech/d_rocket), butd_rocket_builderlives in its own repo (https://github.com/torogoz-tech/d_rocket_builder). pana's URL verification now succeeds.
No API changes — this is a clean-up release.
1.0.0 — 2026-06-14 #
First stable release. d_rocket_builder is the
build_runner codegen companion to
d_rocket 1.0.x.
It ships seven builders, wired in by adding
d_rocket_builder as a dev_dependency and
including a build.yaml (the
d_rocket docs
have the canonical template):
| Builder | Output suffix | Purpose |
|---|---|---|
d_rocket_builder:record |
.g.dart |
@Table / extends Record classes → field accessor registry |
d_rocket_builder:serializer |
.d_rocket_serializer.g.dart |
@Serializable classes → fromJson / toJson |
d_rocket_builder:rest_client |
.d_rocket_rest_client.g.dart |
@RestClient classes → typed get / post / put / delete |
d_rocket_builder:rocket_table |
.d_rocket_orm.g.dart |
@Table classes → CRUD scaffold + change tracking |
d_rocket_builder:record_registry |
d_rocket_registry.g.dart |
central initializeD() that registers all of the above |
d_rocket_builder:realtime |
.d_rocket_realtime.g.dart |
@WebSocketRoute / @SseRoute classes → connection scaffolds |
d_rocket_builder:custom_lint |
n/a | two custom_lint rules: d_rocket_closure (LINQ naming) and d_rocket_n_plus_one (eager-load detection) |
Compatibility #
d_rocket_builder |
d_rocket |
|---|---|
1.0.0 |
^1.0.0 (1.0.0, 1.0.1, 1.0.2 — all compatible) |
Notes #
- The codegen output does not need to be
re-published when the consumer's app changes —
it is regenerated locally with
dart run build_runner build --delete-conflicting-outputs. - The central
d_rocket_registry.g.dartis always emitted at the package root of the consumer's project (one per project, not one per package). CallinitializeD()once at application startup. - The
record_registrybuilder only scans the consumer'slib/**.dartforextends Recordclasses. Examples that live underexample/need to be generated in a separate project (see the d_rocket CHANGELOG for the v1.0.1 workflow).