d_rocket 1.2.0
d_rocket: ^1.2.0 copied to clipboard
Dart/Flutter data-layer framework: @Serializable codegen, @RestClient with retry, deferred LINQ, and a code-first SQLite ORM with migrations. See README for platform support.
Changelog #
All notable changes to d_rocket are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.2.0 — 2026-06-15 #
Minor release. Adds the auto-migration system (opt-in): the framework detects diffs between the schema declared by the codegen-emitted entity list and the last applied schema on disk, applies the safe changes in a single transaction, and reports the unsafe changes for the user to handle manually.
-
New
autoMigrateflag onDb.openandDb.inMemory. When set,Db.open(andDb.inMemory) run the auto-migrator after any hand-writtenMigrationStrategy. The flag is opt-in: existing callers that do not passentityMetas:see no change in behaviour (nod_rocket_schema_statetable is created, no auto-migration runs). The migration system still applies hand-writtenMigrationBases first, so projects that mix hand-written and auto-migrations can do so without conflict. -
Safe operations applied automatically. CREATE TABLE, CREATE INDEX, and ADD COLUMN (nullable or with a default literal) are safe and are applied in a single transaction. The new schema snapshot is written in the same transaction, so the snapshot is never ahead of the actual schema.
-
Unsafe operations reported, never applied. DROP TABLE, DROP COLUMN, DROP INDEX, MODIFY COLUMN (type / nullability / default / FK change), and the rename heuristic are unsafe. They are returned in
Db.pendingSchemaDiff()and in theAutoMigrationResult.unsafelist fromDb.runAutoMigrations(). The user is expected to handle them explicitly (typically by writing a hand-rolled migration that performs the unsafe change). The auto-migrator never destroys data silently. -
New public API.
Db.runAutoMigrations()drives the auto-migration on demand (returns theAutoMigrationResultwith the safe diffs that were applied, the unsafe diffs that were reported, and the newSchemaSnapshot).Db.pendingSchemaDiff()returns the pending diff without applying anything (useful for logging, dry-runs, and CI checks). TheSchemaSnapshot/SchemaTable/SchemaColumn/SchemaIndex/SchemaForeignKey/SchemaDiff/DiffSeverity/SchemaOperationType/AutoMigrationResulttypes are exposed in the package barrel for advanced users (custom diff tooling, alternative orchestrators). -
New internal table
d_rocket_schema_state. A single-row key-value table that stores the last appliedSchemaSnapshotas JSON. The table is intentionally separate from the existing_d_rocket_migrations(which tracks hand-writtenMigrationBaseruns); the two coexist without sharing data. TheCHECK (id = 1)constraint guards against accidental multi-row inserts. The snapshot'sversionfield lets a 1.1.x runtime detect a snapshot from a newer d_rocket and refuse to migrate it (so a downgrade does not silently corrupt the schema). -
Snapshot persistence contract. When the auto-migrator runs and finds unsafe diffs, the safe diffs are applied but the new snapshot is NOT written to
d_rocket_schema_state. The unsafe diffs keep showing up inDb.pendingSchemaDiff()on every reopen until the user handles them (typically by writing a hand-rolled migration that performs the unsafe change explicitly, then re-opening). This is intentional: a pending unsafe diff is a louder signal than a pending safe change, and we want the user to handle the unsafe first. -
Tests. 27 new cases in
test/orm/auto_migration/. Covers the snapshot round-trip, every diff type (safe and unsafe), the round-trip viaDb.open/Db.inMemory, the drop report (not applied) path, the file- backed DB round-trip, the back-compatautoMigrate: falsedefault, and the empty-entityMetasno-op. Full suite: 855 pass + 1 skip. -
No codegen changes. The snapshot is computed at runtime from the existing
EntityMetalist. The codegen did not need to be touched. Thed_rocket_builder1.2.0 release is a no-op version bump (per the lockstep convention established in 1.1.1).
1.1.1 — 2026-06-15 #
Patch release. Three production-readiness fixes that close the data-loss and data-integrity risks flagged by an external review of d_rocket for a clinical-scenario use case.
-
Sync queue is now persisted. Before 1.1.1, the pending sync queue was a
List<SyncChange>in memory insideDbContext. A crash betweensaveChangesAsync()andsyncAsync()lost every queued change. Fix: a new internalSyncQueueStorebacks the queue with ad_rocket_sync_queuetable in the same database as the user data (so it picks up SQLCipher encryption for free when the main DB is encrypted).saveChangesAsyncinserts the queued change inside the same transaction as the data write; onsyncAsyncsuccess the rows are deleted in a transaction; a failed sync leaves the rows for the next call to retry. AmaxQueueSizecap (default 10,000 rows) drops the oldest rows when the cap is exceeded and logs a warning. No new parameters onDb.open/Db.inMemory/Db.saveChangesAsync/Db.syncAsync— the persistence is fully transparent. Existing callers get it for free. New public API:Db.pendingSyncChanges()is now an async getter that hydrates from the persistent store on first call. -
FK enforcement is now on by default.
PRAGMA foreign_keys = ONis emitted on everyDb.open()(inSqliteQueryProviderin both theinMemoryandfilefactories). SQLite ships with FK enforcement off for backwards compatibility; without the PRAGMA,FOREIGN KEY (col) REFERENCES table(col)clauses inCREATE TABLEare parsed and stored but never enforced at runtime. This was a silent data-integrity risk: a row could be inserted with a dangling reference and the constraint violation would only surface if a tool happened to re-enable FKs. The codegen has been emittingREFERENCESclauses correctly for@ForeignKeysince 1.0; the bug was that the engine was not enforcing them by default. The PRAGMA is a no-op when the schema has no FK clauses. -
Two existing tests updated. The
relations_test.dartandinclude_test.dartcases were inserting rows with dangling FKs (e.g. a sale withbook_id = 999when no such book existed), relying on the broken default. The inserts are updated to reference real rows, with a comment explaining why. -
Tests added. 4 new cases in
test/sqlite/foreign_keys_enforcement_test.dart(the PRAGMA is set after every open, on both factories, and an INSERT with a dangling FK raisesSqliteException— the load-bearing test that would silently pass if the PRAGMA were missing). 2 new cases intest/sync/persistent_sync_queue_test.dart(the file-backed round-trip and the schema shape). 6 new cases intest/orm/migration_ddl_includes_indexes_test.dart(ind_rocket_builder, the codegen side of the fix). Total: 12 new cases across the two packages. 828 + 1 skipped.
1.1.0 — 2026-06-15 #
Minor release. Expands the SQLCipher password support landed in 1.0.5 with the four pieces the ecosystem needs to make encryption actually deployable: typed tunables, an async key source, a key-rotation helper, and a redactor for accidental log leaks.
-
EncryptionConfig— typed SQLCipher tunables. A newEncryptionConfigclass is passed viaencryptionConfig:onDb.openandDb.inMemory. It wraps the four SQLCipher PRAGMAs a security-conscious app most commonly tunes:kdfIterations(default 256,000 →PRAGMA cipher_default_kdf_iter),pageSize(default 4096 →PRAGMA cipher_page_size),hmacUse(defaulttrue→PRAGMA cipher_use_hmac), andmemorySecurity(defaulttrue→PRAGMA cipher_memory_security). The four PRAGMAs are applied right afterPRAGMA keyin the order SQLCipher requires, and the config is validated at construction: a badkdfIterationsorpageSizeraisesArgumentErrorbefore the engine is touched. The default config matches SQLCipher 4.x defaults, so callers that pass the config without tuning any value get the same behavior as 1.0.5. -
KeyProvider— async password source. A newKeyProviderabstraction lets the encryption password come from any async store (typically the platform secure storage) instead of being passed as a literalString.Db.openandDb.inMemoryacceptkeyProvider:(mutually exclusive withpassword:); the value is awaited once per open and held in memory for the lifetime of the connection.d_rocketdoes not cache across opens, so rotating the key in the keychain takes effect on the nextDb.open. Two built-in providers ship:StaticKeyProvider(literal in memory; for tests) andCallbackKeyProvider(wraps an async function; for instrumentation or non-Stringsources). Consumers integrating withflutter_secure_storage(or any other vault) implement theKeyProviderinterface in five lines on the application side, so the production code can declare the dependency without taking a Flutter-specific dep. -
Db.changePassword()— key rotation. A newdb.changePassword(newPassword: …)method wrapsPRAGMA rekeywith the same single-quote escape used by the open path. The new key can also be supplied vianewKeyProvider: …; the two are mutually exclusive. The current connection stays open across the rekey (the engine re-encrypts the page cache on the next write). The rekey is applied to every page, so for a multi-megabyte database it can take a few hundred milliseconds. Replaces the "callPRAGMA rekeythrough the provider" workaround documented in the 1.0.5 FAQ. -
redactPragmaKey()— safe SQL logging. A new top-levelredactPragmaKey(String sql)function replaces the literal value of anyPRAGMA key = '...'orPRAGMA rekey = '...'in the input with'***'. Case-insensitive, whitespace-tolerant, and correctly handles the single-quote escape d_rocket uses internally. Useful for application-level SQL traces when the database is encrypted and the password must not appear in logs, crash reports, or any other observer. -
FAQ expanded. The "Security" section in
doc/13-faq.mdnow also coversKeyProviderwith aflutter_secure_storageexample,EncryptionConfigwith the four PRAGMAs and thepageSizemigration caveat, the newchangePasswordflow (replacing the "do-it-yourself through the provider" recipe from 1.0.5), andredactPragmaKeyfor log sanitization. The threat model entry from 1.0.5 is unchanged. -
New tests in
test/sqlite/encryption_ecosystem_test.dart(29 cases): EncryptionConfig validation (defaults, tuned values, bad inputs, every documented power-of-two pageSize), built-in KeyProviders, mutual exclusion and empty-key rejection onDb.open,Db.changePasswordargument validation, and the fullredactPragmaKeyredaction matrix (simple, escaped quote, case, whitespace, multi-statement, unrelated SQL, empty string). All tests run on the dev machine with nolibsqlcipherinstalled. -
Boxed
LoggingInterceptor. A newLoggingInterceptorinlib/src/rest/implementsRestInterceptorand writes one line per request, response, and error to a caller-supplied sink (e.g.print,developer.log). The default configuration is conservative (method, URL, status — no headers, no bodies) so it is safe to drop in production without exposing secrets. Headers and bodies are opt-in viaincludeHeaders: trueandincludeBodies: true. When bodies are included, the body text is passed throughredactPragmaKeyby default — a SQLCipher database password that ends up in a request body is never written to the log even when body logging is enabled. To disable redaction, passredactBody: (s) => s. Pairs naturally with the 1.0.5redactPragmaKeyutility to keep REST tracing safe by default. -
Typed
ConflictPolicyhierarchy. A new sealedConflictPolicyclass inlib/src/sync/is the preferred API over the bareConflictResolvertypedef. Four built-in constants are exposed:lww(aliasserverWins, remote value wins on collisions — the previous default), the inverseclientWins(local value wins), andcustom(resolver)for user-provided merge logic. The factory pairs naturally with the existingMergeStrategieshelpers (preferLocalColumns,preferRemoteColumns,maxOf). The oldLwwConflictResolver.instanceandCustomConflictResolver.wrapshims are retained for back-compat and behave identically to the newConflictPolicy.lwwandConflictPolicy.customequivalents. -
REST and sync docs updated. The Interceptors section in
doc/05-layer-2-rest.mdnow uses the realLoggingInterceptor(log: ...)API in its example (the oldLoggingInterceptor()no-arg call would not compile against the 1.1.0 signature) and documents theincludeHeaders/includeBodies/redactPragmaKeyopt-in. The conflict resolution section indoc/08-layer-5-sync.mdadds aConflictPolicywalkthrough alongside the existingConflictResolvertypedef. -
New tests in
test/rest/logging_interceptor_test.dart(11 cases): default line shape, opt-in headers, opt-in bodies,redactPragmaKeydefault, identity-function override, custom redactor, and pass-through semantics foronRequest/onResponse/onError. Intest/sync/conflict_policy_test.dart(16 cases): the constant identity (lww == serverWins, lww != clientWins), the merge semantics oflwwandclientWins(with the empty-payload edge cases), thecustom(resolver)factory (including theMergeStrategieshelpers), and the back-compat shims. -
Runtime observability helpers. Four additive, observability-focused public additions that hang off the same "tell me the state of this DB" question:
-
EncryptionStatusenum inlib/src/sqlite/encryption_status.dartwith three values:plain(no password used),encrypted(password used AND engine confirmed SQLCipher), andunknown(password used but the probe could not confirm the engine — most commonly because the consumer forgot to bundlesqlcipher_flutter_libson Flutter orlibsqlcipheron desktop). -
isSqlCipherAvailable()top-level function inlib/src/sqlite/sqlcipher_probe.dart. The probe was previously a private helper insidetest/sqlite/encrypted_db_test.dart; it is now part of the public API. The result is cached at the isolate level (the cost is paid at most once per process). A test-onlydebugResetSqlCipherProbeCache()clears the cache. -
Db.isOpengetter onDb. Thin wrapper overSqliteQueryProvider.isOpen(which tracks a_disposedflag set bydisposeAsync). -
Db.diagnostics()method onDb. Returns aMap<String, Object?>withisOpen,encrypted,encryptionStatus,keySource('password' | 'keyProvider' | 'none'), andencryptionConfig(the four SQLCipher tunables as a map, ornull). The map is easy to log to JSON, post to a debug endpoint, or print. The map never contains the resolved password — only the key source — so it is safe to forward to a server-side audit log.
Dbis now constructed with the originalpassword/keyProvider/encryptionConfigarguments tracked (previously it discarded them after resolving the key). The tracking is whatdiagnostics()needs; the resolved password itself is not stored, to keep the key out of long-lived memory. -
-
Doc cleanups. Two small, hygiene-only changes bundled with the rest of the polish: the Spanish doc comments in
lib/src/rest/interceptor.dartandlib/src/rest/error.dartare translated to English (the rest of the codebase is in English; these two files were the only outliers), and the README bullet that said "989 unit and integration tests" is updated to the actual current count. The FAQ gains two new entries under the Security section: "How do I check whether the engine is actually SQLCipher?" (aboutisSqlCipherAvailable) and "How do I tell, at runtime, whether my DB is encrypted?" (aboutDb.diagnosticsandEncryptionStatus). -
New tests in
test/sqlite/encryption_ecosystem_test.dart(10 new cases):Db.isOpen(open + closed),Db.diagnostics(plain, password, keyProvider, config, closed, status with or without SQLCipher), andisSqlCipherAvailable(cached value, debug reset). The fullencryption_ecosystem_test.dartfile goes from 29 to 39 cases.
No breaking changes; the password: parameter
from 1.0.5 is unchanged. keyProvider:
is mutually exclusive with password: (passing
both raises ArgumentError). The full 1.0.5
test suite (756 tests) still passes, plus the
29 new ones, plus the 1 libsqlcipher round-trip
test that is skipped without the engine.
1.0.5 — 2026-06-15 #
Patch release. Adds optional, end-to-end encryption of the local SQLite database via SQLCipher. Existing callers that do not opt in are unaffected.
-
Encrypted database support.
Db.openandDb.inMemorynow accept an optionalpasswordparameter that is forwarded to the underlying SQLite engine asPRAGMA keyafter open. The same parameter is exposed onSqliteQueryProvider.fileandSqliteQueryProvider.inMemoryfor advanced users. The default (password: null) preserves the 1.0.x behavior — plain SQLite, no key — so this change is fully backward compatible. -
Single-quote escaping. Passwords that contain
'characters are escaped by doubling (O'Brien→O''Brien) before being interpolated into thePRAGMA key = '...'literal. The escape is applied by a single private helper,SqliteQueryProvider._applyPragmaKey. -
Open-time wrong-password detection. After running
PRAGMA key, the provider issues a verification query (SELECT count(*) FROM sqlite_master) and closes the connection with aDatabaseExceptionif the first encrypted page cannot be decrypted. The exception message is explicit about the three possible causes (wrong password, non-SQLCipher file, engine is not SQLCipher) and links todoc/13-faq.mdfor the engine setup. This avoids the alternative failure mode where the database appears to open successfully and then returns garbage on the first read. -
Engine responsibility is the consumer's.
d_rocketdoes not bundle a SQLCipher build. The consumer installssqlcipher_flutter_libson Flutter, orlibsqlciphersystem-wide on desktop, and thePRAGMA keyis then effective. A vanilla SQLite engine silently ignoresPRAGMA key, so the parameter is a no-op without a SQLCipher native library. The documentation indoc/13-faq.md(new "Security" section) covers the platform setup in detail. -
New tests in
test/sqlite/encrypted_db_test.dart:- The
passwordparameter is accepted onDb.open,Db.inMemory,SqliteQueryProvider.file, andSqliteQueryProvider.inMemory(compile checks). - A password containing a single quote is escaped and does not produce a syntax error.
- Opening without a
passwordcontinues to work (back-compat, no behavior change). - An end-to-end round-trip
(open →
CREATE TABLE→INSERT→ close → reopen → read) and a wrong-password rejection are gated on a runtime probe forlibsqlcipher; on hosts without the engine they are skipped with an explanatory message.
- The
-
Cookbook recipe rewritten. The "Database encryption (SQLCipher)" entry in
doc/12-cookbook.mdpreviously documented anSqliteEncryption(...)API that does not exist in the package; the recipe has been rewritten to use the actualpassword:parameter onDb.open, and now links to the Security section of the FAQ for the engine setup and thePRAGMA rekeyrecipe. -
Threat-model section added to the FAQ.
doc/13-faq.mdnow has a "What does SQLCipher protect against — and what doesn't it?" entry that enumerates the realistic protection boundary (file at rest, backups, page HMAC, weak-password brute force) and the realistic non-protection boundary (root on a running device, the keychain itself, data in transit, the application process, side channels, a stolen unlocked device). The short mental model at the end ("SQLCipher makes a copied file unreadable without the key") is the line the docs want a security reviewer to walk away with.
1.0.4 — 2026-06-14 #
Patch release. Lifts the restriction
that primary keys in @Table entities had
to be int. UUID (and other non-int)
primary keys are now supported, and
auto-incrementing String PKs are filled
with a UUID v4 at INSERT time.
-
Non-
intprimary keys (DDL).EntityMeta._columnDdlinlib/src/orm/entity_meta.dartpreviously emittedINTEGER PRIMARY KEYfor every primary key, regardless of the field's Dart type. A@PrimaryKey(autoIncrement: false) String idfield — the typical UUID pattern — therefore generatedid INTEGER PRIMARY KEYin theCREATE TABLEDDL, which then failed at insert time when a UUID string was passed. The DDL now uses the field's actual SQLite type for non-auto-incrementing PKs, so aStringPK producesid TEXT PRIMARY KEY, aDateTimePK producesstarted_at TEXT PRIMARY KEY, and so on.intPKs withautoIncrement: truestill emitINTEGER PRIMARY KEY AUTOINCREMENT(SQLite'sAUTOINCREMENTis restricted toINTEGER PRIMARY KEY, so that branch is unchanged). ForisAutoIncrement: trueon a non-inttype, the column is<type> PRIMARY KEY(noAUTOINCREMENTkeyword) and the runtime fills the value. -
Auto-incrementing
StringPKs are filled with a UUID v4. When a@PrimaryKey()(defaultautoIncrement: true) is on aStringfield and the entity is inserted without a value set for that field,DbSet.insertOneinlib/src/orm/db_set.dartnow generates a UUID v4 via the newgenerateUuidV4()helper (also exported from the package) and writes it back through the codegen-suppliedmeta.setIdclosure. The column DDL isid TEXT PRIMARY KEY(see above). The new helper is backed byRandom.secure()and produces RFC 4122 v4 UUIDs (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxxwithyin8/9/a/b). -
@PrimaryKeydocstring updated inlib/src/orm/primary_key.dartto document the supported field types and theautoIncrementsemantics, including the "runtime fills the value for non-intPKs" behavior. -
New unit tests in
test/orm_runtime_test.dart:StringPK withautoIncrement: trueemitsid TEXT PRIMARY KEYand does not emitAUTOINCREMENT.StringPK withautoIncrement: false(the previous patch) still emitsid TEXT PRIMARY KEY.DateTimePK emitsstarted_at TEXT PRIMARY KEY.generateUuidV4()returns a valid RFC 4122 v4 UUID and consecutive calls do not collide.
No behavior change for existing int PKs.
For non-int PKs the generated
CREATE TABLE DDL is now correct instead
of broken, and for
autoIncrement: true String PKs the
runtime generates a UUID v4 so callers
no longer need to set a value before
saveChanges.
1.0.3 — 2026-06-14 #
Patch release. Lifts the restriction that
primary keys in @Table entities had to be
int. UUID (and other non-int) primary
keys are now supported.
-
Non-
intprimary keys.EntityMeta._columnDdlinlib/src/orm/entity_meta.dartpreviously emittedINTEGER PRIMARY KEYfor every primary key, regardless of the field's Dart type. A@PrimaryKey(autoIncrement: false) String idfield — the typical UUID pattern — therefore generatedid INTEGER PRIMARY KEYin theCREATE TABLEDDL, which then failed at insert time when a UUID string was passed. The DDL now uses the field's actual SQLite type for non-auto-incrementing PKs, so aStringPK producesid TEXT PRIMARY KEY, aDateTimePK producescreated_at TEXT PRIMARY KEY, and so on.intPKs withautoIncrement: truestill emitINTEGER PRIMARY KEY AUTOINCREMENT(SQLite'sAUTOINCREMENTis restricted toINTEGER PRIMARY KEY, so this branch is unchanged). -
@PrimaryKeydocstring updated inlib/src/orm/primary_key.dartto document the supported field types and theautoIncrementsemantics. -
New unit test in
test/orm_runtime_test.dartcoveringStringandDateTimeprimary keys. ExistingintPK tests are unchanged and still pass.
No runtime behavior changes for int PKs.
For non-int PKs the generated CREATE TABLE
DDL is now correct instead of broken.
[1.0.3] — 2026-06-14 #
Patch release. Two changes:
CREATE TABLEnow usesIF NOT EXISTS.EntityMeta.createTableDdl()inlib/src/orm/entity_meta.dartpreviously emittedCREATE TABLE $tableName (unconditionally. On a fresh database this is fine, but any re-run of the migration on an existing database (e.g. a development reset that left tables behind, or a hot-reload in Flutter) threwSqliteException(1): table X already exists. The 3 unit tests that asserted the oldCREATE TABLE Xprefix were updated to assert the newCREATE TABLE IF NOT EXISTS Xprefix. The behavior change is purely additive — the fresh-install path is unchanged (SQLite creates the table), and the re-run path is now a no-op (SQLite sees the table and skips).- README doc links dropped the
packages/d_rocket/prefix. The 14 doc links in the README's "Docs" section (and the 3 inline cross-references) pointed topackages/d_rocket/doc/in the monorepo. They now point todoc/at the repo root (e.g.https://github.com/torogoz-tech/d_rocket/blob/main/doc/01-overview.md). This is the URL shape the project README ships with on pub.dev.
No API or behavior changes.
1.0.2 — 2026-06-13 #
Patch release. Fixes the README's doc-link index
on the published package (the pub.dev tarball
includes the README, so the 1.0.1 fix only landed
on GitHub). The 1.0.2 tarball carries the same
README fix as GitHub commit c716699.
- 14 doc links in README pointed to
blob/main/doc/0X-...at the repo root, but the docs actually live underpackages/d_rocket/doc/0X-...(the repo is a monorepo with the d_rocket package underpackages/). All 14 now have the correctpackages/d_rocket/prefix. - 2 stale names in the "Docs" section survived
the v1.0.0 rename:
@RocketTable→@Tablein the Layer 4 bullet, and the CLI toolsd_rocket:rocket_migration/d_rocket:rocket_closure→d_rocket:migration/d_rocket:closure.
The 17 remaining @Rocket* / d_rocket:rocket_*
references in the README are the rename-mapping
table (lines 79-86) and the CHANGELOG entries —
intentional historical record.
1.0.1 — 2026-06-13 #
Patch release. No API changes. Fixes the pub.dev scoring report and the doc link index.
- Moved
lib/example/bookstore.dartandlib/example/quickstart.darttoexample/. Both files require the codegen output to compile (they importd_rocket_registry.g.dartandbookstore.g.dart), which made pana fail the static-analysis check on the published tarball (5 errors, allURI_HAS_NOT_BEEN_GENERATED/UNDEFINED_FUNCTION). Files inexample/are not analyzed by pana, so the score recovers. The codegen-emitted central registry (lib/d_rocket_registry.g.dart) was patched to import the example from its new location. The two test files that imported the example viapackage:d_rocket/example/bookstore.dartwere updated to use a relative import. - README doc-index fixes. The 14 links in
the doc index pointed to
/docs/(with 's'); the actual folder is/doc/. The 3 inline doc references pointed to short file names (serialization.md,rest.md,linq.md) that no longer exist (renamed to04-layer-1-serialization.md,05-layer-2-rest.md,06-layer-3-linq.mdin the v0.4 doc reorganization). Also two stale identifiers in the overview table and the top code sample (RocketDbContext→DbContext,RocketDb.open→Db.open). - CHANGELOG header cleanup. Removed the "— First stable release" subtitle from the 1.0.0 header to match the version-only convention used elsewhere.
1.0.0 — 2026-06-12 #
The first stable, production-ready release of d_rocket. The
public API is now frozen within the 1.x series: minor
versions may add features, patch versions fix bugs, and
breaking changes will trigger a 2.0 bump.
This release consolidates four prior pre-releases (0.1.0-dev, 0.3.0-dev,
0.4.0-dev, 0.5.0-dev) into a single, cohesive
1.0. The SQLite storage engine is now bundled directly in
this package; the d_rocket_provider_sqlite companion
package is kept as a thin compatibility shim for projects
that have not yet migrated.
Breaking changes #
- The
Rocketprefix is gone from the public API. Every public type and every CLI command has been renamed. The oldRocketTableis nowTable;RocketDbContextisDbContext; the CLI commandd_rocket:rocket_migrationis nowd_rocket:migration; etc. The full mapping is in the README's "Breaking changes in v1.0 — the rename" section. The annotation@RocketMigrationis now@Migration; the abstract base class that the codegen-emitted migration subclass extends isMigrationBase(the two names are deliberately distinct to avoid a same-library collision with the annotation). Codegen output is regenerated from scratch on every build, so users that ranbuild_runneron the pre-release will need a one-timedart run build_runner build --delete-conflicting-outputsafter upgrading.
Highlights #
- One package, one mental model, one generator. Annotation-
driven serialization, REST, LINQ, and ORM share the same
design vocabulary, error model, and
initializeD()wiring. - Async-first throughout. Every terminal query operator
has an
*Async_sibling that returns aFuture. No callback chains. - SQLite-bundled.
RocketDb.open(path: ...)returns a fully-wired database.package:sqlite3is the only engine shipped out of the box. - Offline-first sync & realtime.
SyncProviderfor push/pull pipelines,WebSocketClientandServerSentEventsClientfor typed realtime streams. - 989 tests across the runtime, the codegen, and the SQLite engine — all passing.
Added (since 0.4.0-dev) #
Layer 1 — Serialization
@SerializablewithfromJson/toJsoncodegen.JsonNamingpolicy:none,snakeCase,camelCase,kebabCase,pascalCase.UnknownKeyPolicy:ignore(default, drop unknown keys),strict(throw),capture(route extras to anextra: Map<String, Object?>field — the class must declare one).@JsonKey(name: ..., ignore: ..., requiredKey: ..., defaultValue: ..., converter: ..., useEnumIndex: ..., unknownEnumValue: ...)for per-field overrides.Format(class, not enum):Format.trim(),Format.uppercase(),Format.lowercase(),Format.date('yyyy-MM-dd' | 'iso8601'),Format.custom(name),Format.customWith(type).@SerializableUnionfor sealed sum types with discriminator dispatch.
Layer 2 — REST
@RestClientwith@HttpGet/@HttpPost/@HttpPut/@HttpPatch/@HttpDelete/@HttpHead.- Parameter binding:
@Path,@Query,@Header,@Body,@Field,@Part,@RawBody. RestConfigfor one-place resilience configuration.RetryPolicywithBackoff.exponential/Backoff.fixed/Backoff.linear.RateLimit(requestsPerSecond: ...)token-bucket throttle.CircuitBreakerstate machine (closed→open→halfOpen→closed) withdRest.circuitState<T>().RestInterceptorinterface anddRest.use(...)chain (auth, logging, tracing, metrics).- Typed exception hierarchy:
RestHttpException,NetworkException,RestConfigException. CancelTokenfor cancellable requests.Stream<T>return types for streaming endpoints.
Layer 3 — LINQ
IQueryable<T>with deferred execution.- Operators:
where_,ofType_,select_,take_,skip_,takeWhile_,skipWhile_,orderBy_,orderByDescending_,thenBy_,thenByDescending_,distinct_,concat_,union_,intersect_,except_,any_,all_,contains_,count_,longCount_,sum_,average_,min_,max_,aggregate_,first_,firstOrDefault_,single_,singleOrDefault_,elementAt_,elementAtOrDefault_,toList_,toSet_,toMap_,asEnumerable_,cast_,join_,groupJoin_,groupBy_. - Async terminal operators:
toListAsync_,toSetAsync_,firstAsync_,firstOrDefaultAsync_,countAsync_,sumAsync_,averageAsync_,minAsync_,maxAsync_,anyAsync_,allAsync_. ExprDSL for expression-tree portability (the samewhere_(...)predicate is evaluated in-memory by the LINQ provider or pushed down to SQL by the ORM).- Closure-sugar extensions for prototyping over
Iterable<T>. - Reactive
watch()returning aStreamfor live data.
Layer 4 — ORM (SQLite-bundled)
@RocketTable('table_name')for entity declaration.@PrimaryKey(autoIncrement: true),@Column(name: ..., nullable: true, unique: true).- Type mapping:
int,double,String,bool,DateTime(ISO-8601),Uint8List(BLOB). @BelongsToand@HasManyfor navigation properties.RocketDb.open(path: ..., strategy: ...)/RocketDb.inMemory().- Change-tracked
DbSet<T>:add/addAsync,updateWhere/updateWhereAsync,removeWhere/removeWhereAsync. saveChanges()/saveChangesAsync()flushes the change set in a single transaction.include_<T>()codegen for eager-loading related entities in one round-trip.asLinqQueryable()bridge to the SQL LINQ provider.- Bulk operations:
addAll,updateAll,removeAll. - Reactive queries:
watch()returns aStream.
Migrations
Migrationbase class withid,version,name,up(exec),down(exec).MigrationRunnerfor direct execution.MigrationStrategywith declarativemigrationslist AND imperativeonCreate/onUpgrade/onDowngradecallbacks.- Automatic upgrade / downgrade detection based on
currentVersion()vs.targetVersion. _d_rocket_migrationstable for persisted migration history.dart run d_rocket:rocket_migration add <name>CLI scaffolder.dart run d_rocket:rocket_migration doctorvalidator.
Sync (offline-first)
SyncProviderinterface for push / pull pipelines.SyncOpqueue with persistence to SQLite.- Background flush on table-change watch streams.
- Conflict-resolution policies:
lastWriterWins(default)serverWins+clientWins+ custom callback.
- Identity persistence for re-attach after process restart.
- Exponential-backoff retry on
NetworkException.
Realtime
@WebSocketRoutefor typed WebSocket methods returningStream<T>.@SseRoutefor typed Server-Sent Events methods returningStream<T>.- Reconnect with exponential backoff.
- Heartbeat / ping support.
Codegen (d_rocket_builder)
d_rocket:rocket_serializerbuilder — emits per-classfromJson/toJsonand centralregister<X>Serializer.d_rocket:rocket_rest_clientbuilder — emits per-interfaceRestClientimplementations with interceptors, retry, and serialization wired in.d_rocket:rocket_tablebuilder — emits per-classfromRowandsetIdclosures for the ORM.- Single generated
d_rocket_registry.g.dartwithinitializeD()that registers every annotated class in the project.
Migration from d_serializer / d_rest 0.x #
d_serializer1.3.0 was absorbed intod_rocket 1.0. Replacepackage:d_serializer/d_serializer.dartwithpackage:d_rocket/d_rocket.dart. The annotation API is unchanged; the only API renames areSerializer.fromJson→Serializer.fromJson<T>andFormatimport path.d_rest0.1.0 was absorbed intod_rocket 1.0. Replacepackage:d_rest/d_rest.dartwithpackage:d_rocket/d_rocket.dart. The@RestClientAPI is unchanged; resilience config moved fromRestClientBuildertoRestConfigand thecircuitState<T>()extension moved todRest.circuitState<T>().
Acknowledgements #
The API design draws on patterns from several well-known frameworks: Entity Framework Core (DbContext, change tracking, Migrations), .NET LINQ (deferred execution, operator matrix), Retrofit (annotated interfaces), Moshi and kotlinx.serialization (annotation-driven serialization), and sqflite (SQLite migration runner).
License #
© Torogoz Tech. Released under the MIT License.