relax_orm 1.2.0
relax_orm: ^1.2.0 copied to clipboard
A local-first ORM for Flutter with offline support, real-time streams, automatic sync, and encryption.
1.2.0 #
Adds schema migrations. Until now a table that already existed was left exactly
as it was: RelaxDB.open emitted CREATE TABLE IF NOT EXISTS, which SQLite
answers by comparing names and never shapes. A column added to a model was
therefore missing from every database created before it — silently, because
reads still worked (SELECT * simply returned rows without it) and only the
first INSERT failed, with table … has no column named …, far from the change
that caused it. No breaking changes.
Added #
- Additive reconciliation, on every open. Any column a schema declares and
its table lacks is appended with
ALTER TABLE … ADD COLUMN. Adding a nullable column — or aNOT NULLone with adefaultValue— now needs no migration and no version bump at all. ANOT NULLcolumn without a default throws with an explanation instead of being skipped: SQLite has nothing to write into the rows already there, and skipping it is what made the original bug invisible. versionandonUpgradeonopen,openFileandopenInMemory. Raiseversionwhen a change cannot be applied by appending a column, and describe it inonUpgrade(migrator, from, to). Both default to the previous behaviour, so existing call sites are unaffected.Migrator—addColumn,renameColumn,rebuildTable, plusexecute/select/columnsOffor anything else.rebuildTableperforms the create-copy-drop-rename procedure the SQLite documentation prescribes, in one transaction, and is the way through for what SQLite cannot alter in place: a column that changes type, losesNOT NULL, or goes away. Itsfromargument maps a new column to any SQL expression over the old table, which is how a rename keeps its rows.RelaxDB.executeandRelaxDB.select— raw SQL, for what the typed API does not cover.executetakes the tables it writes so that activewatch()streams still refresh; Drift cannot see inside a hand-written statement.ColumnDef.definition— the column'sCREATE TABLEfragment, now shared withADD COLUMNso the two can no longer spell the same column differently.ColumnDef.isAddable— whether SQLite can append this column at all.TableSchema.toCreateTableSqltakesasandifNotExists, for the scratch table a rebuild needs.
Fixed #
- An open that failed no longer leaks the database file. The caller was handed an error and never a handle, so it had nothing to close and the file stayed locked for the rest of the process.
Notes #
- The version is kept in a
_relax_schematable, not inPRAGMA user_version: Drift already stores its ownschemaVersionin the SQLite header, and writing another number there makes it refuse to open the database. - A database created by 1.1.1 or earlier has no recorded version and reports
from: 0toonUpgrade— "unknown, assume the oldest". Its additive drift is repaired regardless, so apps that only ever added columns need no migration code to catch up. - Opening a database whose recorded version is newer than the build now throws rather than reading rows with a schema that no longer describes them.
1.1.1 #
Changed #
- Requires
relax_orm_generator ^1.0.1, which fixes thetoMapcode generated for nullable JSON-backed fields (nested models andList<T>?) — the previous output did not compile. Rundart run build_runner buildto regenerate.
1.1.0 #
Adds a seeding engine and a relax_orm command. No breaking changes.
Added #
- Seed engine.
db.seedsexposes aSeedRunnerthat runs registeredSeeders exactly once each, recording them in a_relax_seedsledger table — so callingrun()on every app start is a no-op after the first time. Each seeder runs in its own transaction, so a failure leaves no partial rows and no ledger entry.run({only, force, continueOnError}),fresh()(wipe the seeded tables and re-seed),forget(),appliedNames(),hasRun().- Failures are reported in the returned
SeedReportrather than thrown; callreport.throwIfFailed()to surface them.
Seeder— base class for hand-written seeds, withorder(execution order) andtables(whatfresh()clears).TableSeeder<T>— base class for the generated per-table seeders. Its constructor takescount,records,builder,randomSeedandorder, so a generated seeder can be retuned without subclassing it.SeedFaker— deterministic fake-data generator (names, emails, sentences, UUIDs, dates, bytes, …). The same seed always produces the same values.@RelaxSeed(count:, order:, enabled:)— opts a model into (or out of) seeder generation. Requiresrelax_orm_generator ^1.0.0.dart run relax_orm— a wrapper aroundbuild_runner:dart run relax_ormgenerates schemas,dart run relax_orm --seedgenerates schemas and seeders, pluswatch/cleanand--seed-count=N. Arguments after--are forwarded tobuild_runner.dart run build_runner buildkeeps working unchanged.Collection.upsertAll— batch insert that overwrites rows conflicting on the primary key. This is what makes re-running a seeder converge on the same rows instead of failing on a duplicate key.
1.0.0 #
First stable release. Addresses the findings of the 0.1.7 technical audit (sync
engine, persistence, schema) and commits to a stable public API. Two changes to
the SyncAdapter contract since 0.1.7 are breaking; see below.
Breaking #
SyncAdapter.pushDeletesnow returnsFuture<List<Object>>(the ids the server confirmed as deleted) instead ofFuture<void>(SY-2).SyncAdapter.pushsemantics tightened: return only the entities the server accepted. Entities omitted from the result are treated as not-yet-synced and stay queued for retry instead of being silently completed (SY-1).Collection.addnow returnsFuture<T>andCollection.addAllreturnsFuture<List<T>>(the stored entities). This is source-compatible with existingawait-and-ignore call sites (ORM-2).
Fixed #
- SY-1 — Silent loss of unconfirmed pushes. The engine now completes only the queued operations whose entity the adapter confirmed (matched by primary key); unconfirmed operations remain pending and are retried on the next sync.
- SY-2 — Partial deletes.
pushDeletesreports confirmed ids, so a partial server-side delete no longer marks the whole batch as synced. - ORM-1 —
addAlldidn't refresh streams.rawBatchInsertnow emits an explicit table update, so activewatchAll()/watchOne()listeners refresh after a bulk import. - ORM-2 — Generated ids unreachable.
add()/addAll()return the stored entity (with any generated UUID primary key) instead ofvoid. - ORM-3 —
upsert()race. The existence check and the write now run inside a single transaction, so a concurrent write to the same key can't turn the insert into aPRIMARY KEYviolation.
Changed #
- SY-3 — Conflict-resolution asymmetry documented.
SyncConfig.conflictResolverruns only on pull; push-confirmation write-backs are authoritative. Documented in the docstring and README. - SY-4 — Targeted queue clear.
OfflineQueue.clear({String? tableName})can now purge a single table's pending operations. - ORM-4 — Schema versioning for the offline queue.
TableSchemagains aversionfield; queued operations record it, and the engine discards operations whose recorded version no longer matches the current schema so stale SQL-encoded rows are never decoded with an incompatible schema. (Existing queued rows are treated as version0/"unspecified" and kept, so upgrading is lossless.) - ORM-5 — O(1) schema lookup.
RelaxDB.collection<T>()resolves schemas via a direct type-keyed lookup instead of a linear scan. - GEN-1 — Generator status. Removed the stale "Phase 1a/Phase 2" comments;
the
relax_orm_generatoris available now, alongside hand-written schemas.
0.1.7 #
Fixed #
- Long debug-log messages are no longer truncated by the console/logcat: the default
dart:developersink splits long text (by line, then into 800-character chunks), tagging multi-chunk records with[i/n].
0.1.6 #
Added #
- Opt-in debug logging via
RelaxLogger, passed toRelaxDB.open/openFile/openInMemory. Disabled by default; supports category filtering (database,encryption,crud,query,sync,queue), a minimum level, and a custom sink. Defaults todart:developer'slog()(Flutter DevTools "Logging" view). RelaxDB.debugCheckEncryption()— inspects the database file header and reports whether data on disk is actually encrypted (EncryptionCheck.isEncrypted/.isMisconfigured), the direct answer to "are my data really encrypted?".
0.1.5 #
Added #
SyncPullResult.serverTime— an optional server-authoritative watermark reused as thesincevalue for the next pull, avoiding client/server clock drift- Offline-queue coalescing: repeated edits to the same entity are folded both on write (one row per entity) and on push (one server write per entity); offline create-then-delete is dropped entirely
Changed #
- Queued sync payloads are now SQL-encoded, so entities with
DateTime/boolfields no longer break the offline queue's JSON serialization SyncAdapter.pushreturn values (server-confirmed entities) are written back to the local database- Pull changes are applied inside a single transaction
Fixed #
- Failed sync operations are now retried by the periodic auto-sync (previously only on reconnect/restart), honoring each table's
maxRetries - Unique operation ids no longer collide when many operations are queued within the same clock tick
0.1.4 #
Added #
- Added public
RelaxOrmJsonhelpers for generated schemas that need JSON serialization and deserialization - Added base64 helpers for
Uint8Listvalues used inside JSON-backed fields
Changed #
- Exported
src/core/relax_orm_json.dartfrom the mainrelax_orm.dartlibrary - Bumped
relax_orm_generatorto^0.1.6to support generated mappings for nested objects andList<T>fields
0.1.3 #
- Update dependencies
0.1.2 #
Added #
- Automatic UUID generation for text primary keys when inserting an entity with a null ID
Changed #
Collection.add()now queues the persisted entity after ID generation so sync payloads keep the effective primary key- Bumped
relax_orm_generatorto^0.1.2
Fixed #
- Synced inserts with generated text primary keys now keep database rows and queued operations aligned
0.1.1 #
Changed #
- Annotations (
@RelaxTable,@PrimaryKey,@Column,@Ignore) are now the single source of truth in this package - Added
relax_orm_annotations.dart— lightweight export without Flutter/Drift dependencies, safe for use by code generators and pure-Dart contexts - SDK constraint is now bounded (
>=3.11.0 <4.0.0) - Added
license,platforms,issue_trackermetadata to pubspec
Fixed #
- Removed runtime dependency on
relax_orm_generator— heavy build-time packages (analyzer,source_gen,build) are no longer pulled into the app's dependency tree
0.1.0 #
- Initial release
- ORM Core:
RelaxDB,Collection<T>with full CRUD (add, addAll, update, upsert, delete, deleteAll, get, getAll, count) - Real-time streams:
watchAll(),watchOne()with Drift-powered reactive queries - Query builder: fluent API with filters (equals, greaterThan, contains, isIn, isNull...), orderBy, limit, offset
- Encryption: transparent SQLite3MultipleCiphers encryption via
encryptionKeyparameter - Sync engine: offline queue, push/pull sync, configurable conflict resolution (remoteWins, localWins, custom)
- Code generation:
@RelaxTable,@PrimaryKey,@Column,@Ignoreannotations with automatic schema generation - Schema definition:
TableSchema<T>with type-safe column definitions and automatic Dart/SQL type conversion