relax_orm 1.2.0 copy "relax_orm: ^1.2.0" to clipboard
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 a NOT NULL one with a defaultValue — now needs no migration and no version bump at all. A NOT NULL column 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.
  • version and onUpgrade on open, openFile and openInMemory. Raise version when a change cannot be applied by appending a column, and describe it in onUpgrade(migrator, from, to). Both default to the previous behaviour, so existing call sites are unaffected.
  • MigratoraddColumn, renameColumn, rebuildTable, plus execute / select / columnsOf for anything else. rebuildTable performs 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, loses NOT NULL, or goes away. Its from argument maps a new column to any SQL expression over the old table, which is how a rename keeps its rows.
  • RelaxDB.execute and RelaxDB.select — raw SQL, for what the typed API does not cover. execute takes the tables it writes so that active watch() streams still refresh; Drift cannot see inside a hand-written statement.
  • ColumnDef.definition — the column's CREATE TABLE fragment, now shared with ADD COLUMN so the two can no longer spell the same column differently.
  • ColumnDef.isAddable — whether SQLite can append this column at all.
  • TableSchema.toCreateTableSql takes as and ifNotExists, 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_schema table, not in PRAGMA user_version: Drift already stores its own schemaVersion in 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: 0 to onUpgrade — "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 the toMap code generated for nullable JSON-backed fields (nested models and List<T>?) — the previous output did not compile. Run dart run build_runner build to regenerate.

1.1.0 #

Adds a seeding engine and a relax_orm command. No breaking changes.

Added #

  • Seed engine. db.seeds exposes a SeedRunner that runs registered Seeders exactly once each, recording them in a _relax_seeds ledger table — so calling run() 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 SeedReport rather than thrown; call report.throwIfFailed() to surface them.
  • Seeder — base class for hand-written seeds, with order (execution order) and tables (what fresh() clears).
  • TableSeeder<T> — base class for the generated per-table seeders. Its constructor takes count, records, builder, randomSeed and order, 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. Requires relax_orm_generator ^1.0.0.
  • dart run relax_orm — a wrapper around build_runner: dart run relax_orm generates schemas, dart run relax_orm --seed generates schemas and seeders, plus watch / clean and --seed-count=N. Arguments after -- are forwarded to build_runner. dart run build_runner build keeps 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.pushDeletes now returns Future<List<Object>> (the ids the server confirmed as deleted) instead of Future<void> (SY-2).
  • SyncAdapter.push semantics 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.add now returns Future<T> and Collection.addAll returns Future<List<T>> (the stored entities). This is source-compatible with existing await-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. pushDeletes reports confirmed ids, so a partial server-side delete no longer marks the whole batch as synced.
  • ORM-1 — addAll didn't refresh streams. rawBatchInsert now emits an explicit table update, so active watchAll() / 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 of void.
  • 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 a PRIMARY KEY violation.

Changed #

  • SY-3 — Conflict-resolution asymmetry documented. SyncConfig.conflictResolver runs 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. TableSchema gains a version field; 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 version 0/"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_generator is 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:developer sink 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 to RelaxDB.open/openFile/openInMemory. Disabled by default; supports category filtering (database, encryption, crud, query, sync, queue), a minimum level, and a custom sink. Defaults to dart:developer's log() (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 the since value 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/bool fields no longer break the offline queue's JSON serialization
  • SyncAdapter.push return 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 RelaxOrmJson helpers for generated schemas that need JSON serialization and deserialization
  • Added base64 helpers for Uint8List values used inside JSON-backed fields

Changed #

  • Exported src/core/relax_orm_json.dart from the main relax_orm.dart library
  • Bumped relax_orm_generator to ^0.1.6 to support generated mappings for nested objects and List<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_generator to ^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_tracker metadata 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 encryptionKey parameter
  • Sync engine: offline queue, push/pull sync, configurable conflict resolution (remoteWins, localWins, custom)
  • Code generation: @RelaxTable, @PrimaryKey, @Column, @Ignore annotations with automatic schema generation
  • Schema definition: TableSchema<T> with type-safe column definitions and automatic Dart/SQL type conversion
2
likes
160
points
32
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A local-first ORM for Flutter with offline support, real-time streams, automatic sync, and encryption.

Repository (GitHub)
View/report issues
Contributing

Topics

#orm #database #offline-first #sync #sqlite

License

MIT (license)

Dependencies

drift, drift_flutter, flutter, path_provider, uuid

More

Packages that depend on relax_orm