datum 1.1.0
datum: ^1.1.0 copied to clipboard
A powerful, offline-first data synchronization engine for Flutter and Dart, featuring relational data support, real-time queries, and intelligent conflict resolution.
1.1.0 #
All changes below are additive and backward compatible unless noted.
- Exported from the package barrel:
DatumConfigPresets,DatumPersistence,InMemoryDatumPersistence.
𧬠Typed schemas & auto-migration (no codegen) #
DatumFieldSpec<E, V>: a full runtime field descriptor that IS-ADatumQueryFieldβ typed query refs (whereField/orderByField/Filter helpers) plus acodec,sqlType,defaultValue, and arenamedFromrename hint.DatumFieldCodecships inferred primitive codecs, lenient DateTime (ISO-8601 and epoch-ms decode),durationMicros,uri,bigInt,enumByName,jsonObject, and a.nullablewrapper.DatumSchema<E>: declare each entity's serialized shape once (datumCoreFieldSpecs<E>()provides the six sync fields with overridable keys). Powers cast-free map reads (schema.reader(map)/schema.decodewith field-namedSchemaReadExceptions), optionaltoMapdelegation, derived SQLite columns (sqlColumns()), and a stable declarationfingerprint.- Auto-migration:
DatumConfig(schema:, autoMigrate: true)reconciles the store's actual shape with the declaration duringinitialize()β after the manualSchemaMigrationchain, never touching the stored int schema version. Missing fields are added and backfilled with their defaults (fail-fast when a non-nullable field has none), renames followrenamedFrom:hints without data loss, and undeclared columns are kept and warned about unlessautoMigrateDropColumns: true. RealALTER TABLE/UPDATEin one transaction on SQL adapters (introspected viaPRAGMA table_infothroughrawQuery); snapshot-protected raw-map rewrites on schemaless stores. A stored fingerprint (SchemaFingerprintCapable) makes unchanged launches skip the pass entirely. - Schema-driven
diffOf/propsOf: the schema also replaces the hand-writtendiffandpropsboilerplate βschema.diffOf(old, new)emits a payload-only delta through the field codecs (stamping the newmodifiedAt/version), andschema.propsOf(entity)feedsEquatable. Core sync fields are marked viaDatumFieldSpec.coreRole. - Typed queries certified across adapters:
whereField/orderByFieldwith specs produce results identical to string queries and a reference evaluation on every adapter (seerunTypedQueryConformanceTestsindatum_test); micro-benchmarks show ~80 ns per query build over the string path. The auto-migration stamp includes the drop policy, so enablingautoMigrateDropColumnslater re-runs the pass instead of being silently ignored. - Typed relations:
DatumRelationSpec<E, R>declaresbelongsTo/hasOne/hasMany/manyToMany(via a registered pivot entity, both pivot keys as its field specs) with the relation name, both entity types, the foreign key (as aDatumFieldSpec), and cascade behavior bound at compile time.buildRelations()becomesdatumRelationsFor(this, [specs]); eager loading useswithRelated: [spec].names; typed access viaspec.listOf/spec.oneOf; typed lazy fetching viaspec.fetchListFor/spec.fetchOneForβ resolved through the registered managers, identical on every adapter. - New capability mixins:
SchemaFingerprintCapableandSqlSchemaCapable(additive; existing adapters unaffected). NewAutoMigrationExecutor,diffSchema,SchemaRenameOperation, and introspectors are exported for direct use.
β‘ Sync performance & scale #
- cursor-based incremental pull (delta v2):
CursorSyncCapableβreadChanges(cursor)returns(items, nextCursor)with an opaque cursor, the natural fit for changes-feed backends (Firestore tokens, DynamoDB streams, CouchDB sequences, monotonic counters) and immune to clock skew. Anullcursor means "from the beginning", so even first syncs ride the feed; the cursor persists per device in local sync metadata (customMetadata['__sync_cursor__']) and is deliberately stripped from the remote metadata beacon β a foreign device adopting another's cursor would silently skip changes. When an adapter advertises both capabilities, the cursor path wins over the timestamp path. - incremental pull (delta sync): remote adapters can mix in
DeltaSyncCapableand implementreadSince(since); the pull phase then fetches only rows modified since the last sync watermark instead of the full dataset β the biggest scalability lever for large stores. Enabled by default when the adapter is capable (DatumConfig.enableDeltaSync), withdeltaSyncOverlapclock-skew tolerance (re-delivered rows are dropped by the strictly-newer check). The engine still pulls full for a user's first sync and fordetectRemoteDeletionscycles, which need the complete remote id set. Prefer a server-maintained received-at column over the client'smodifiedAtβ see theDeltaSyncCapabledocs. - metadata hash cache: stamping sync metadata no longer re-reads and
re-hashes the whole local dataset on cycles where nothing changed locally
β a per-user
(hash, count)cache is invalidated by every write chokepoint (manager CRUD, engine pull application, external changes, migrations, clears) and reused otherwise. Escape hatch:DatumConfig.enableMetadataHashCache: false.
π§Ή CRDT compaction #
- RgaList/RgaText: added
compacted()/compact()andtombstoneCount. Compaction purges deletion tombstones (which otherwise accumulate forever), preserving element ids β cursor anchors stay valid β and the visible order exactly. Compact only at a synchronization barrier; the coordination contract (and the stale-replica resurrection hazard it prevents) is documented onRgaList.compactedand pinned by tests.
β¨ Realtime / collaborative editing #
- crdt: added
RgaList<T>β a Replicated Growable Array (convergent ordered-sequence CRDT) β andRgaText, a collaborative text type built on it. Concurrent inserts/deletes on different devices merge deterministically (commutative, associative, idempotent), contiguous runs never interleave, and element/character ids provide stable anchors for remote cursors. Combine withCRDTResolver, per-devicedeviceId, and vector clocks for editor-grade multi-device sync; seetest/integration/collaborative_editor_test.dartfor the end-to-end reference pattern (two devices editing offline and converging, including the backend).
ποΈ Schema migrations #
- migration: added
SchemaMigrationβ declare a migration as a list ofColumnOperations (addwith default or computed value,rename,remove,transform, arbitraryrowrewrite) instead of hand-written map surgery. Rows can be scoped byentityType(__typename) or awherepredicate, andmigratenever mutates its input, keeping the executor's rollback snapshot intact even for adapters that hand out live references. - migration: added a native SQL migration path.
SqlMigrationGeneratortranslates the sameColumnOperations into dialect-aware DDL/DML (ALTER TABLE ADD/RENAME/DROP COLUMN, backfillingUPDATEs; sqlite + postgresql, with type inference fromdefaultValueandsqlType/sqlExpression/sqlWhereoverrides), andSqlMigrationExecutorruns the chain through anyRawQueryCapableadapter inside its transaction β every statement is generated and validated before anything touches the database. Custom operations join in by implementingSqlConvertibleOperation. - migration: added
MigrationPlan.resolveβ validates the whole version chain (gaps, duplicate starting versions, backwards steps, overshoot) and reports every problem in oneMigrationException.MigrationExecutor.execute()now resolves the plan up front, so a misconfigured chain fails fast before any data is read or written instead of mid-migration.
π Bug fixes (sync-engine hardening) #
-
soft delete is real now: tombstones (
isDeleted: true) are invisible to every default read path βread/readAll/query/watchAll/watchQuery/watchById(which emits null) β while the row survives underneath for sync;includeDeleted: trueopts back in, and a query explicitly filtering onisDeletedis never rewritten. Tombstone exclusion is query PUSHDOWN, solimit/offsetcount live rows only. The tombstone delta also bumpsversion(conflict detection must see the delete as newer) and only carriesvectorClockfor entities that serialize one (a phantom clock column threw understrictColumns). -
delta-sync watermark derives from the data, not the clock: the pull phase stages the newest remote
modifiedAtit saw and persists it asserverTimestamp(previously never populated β the effective watermark was this device's wall clock stamped at END of cycle, so clock skew or a long push phase silently and permanently skipped rows). -
per-entity push ordering barrier: a retryably-failed operation now blocks later queued operations for the same entity within the cycle β previously newer ops overtook the failed one and its replay next cycle regressed the remote (lost updates) or resurrected deleted entities.
-
batch push failures fall back to per-operation processing: one already-deleted id (
EntityNotFound) or one rejected entity no longer discards sibling operations that were never individually attempted. -
staged pull state can't outlive an aborted cycle: an incremental-pull cursor (and watermark) staged by a pull that failed or was interrupted is discarded, and a push-only cycle never persists staged pull state β the rows behind a stale cursor would never have been fetched again.
-
change-echo dedupe keys on content, not entity id: the manager now fingerprints its own writes (id + version + modifiedAt) so the adapter's echo is dropped (no re-run of pre-save middleware β non-idempotent transforms double-encrypted β and no duplicate queue ops), while a genuine external change arriving within the cache window is applied instead of being discarded as a "duplicate".
-
CRDT convergence hardening:
RgaList.mergeresolves same-id node collisions deterministically (compaction against a stale replica, or two devices editing a document deserialized without their ownreplicaId, previously diverged PERMANENTLY);toMapserializes nodes in canonical order and no longer smuggles the creator'sreplicaId(converged replicas now serialize byte-identically, ending conflict-detection ping-pong);ORSetgains a faithful format-2 wire encoding (the legacy format stringified elements into JSON keys β non-String types crashed or collided; legacy payloads still decode);PNCounter.fromMaptolerates doubles;CRDTResolverresolves deletion conflicts content-preservingly instead of aborting forever, and flags a degraded default merge (=> other) in the resolution message. -
relation stitching integrity:
InMemoryLocalAdapterreturns fresh instances per read, sowithRelatedstitching can no longer write relation state into the stored copy (stale memoizedfetch()lists);RelationLoaderfalls back to the parent entity's userId when the caller passed none, so stitching never attaches other users' rows. -
per-listener watch semantics:
watchAll/watchQuery/watchByIdon the in-memory adapter (and SQLite/Hive β see their changelogs) deliver a current snapshot to EVERY listener, honorincludeInitialData: false, and no longer starve a second concurrent listener; certified by the newrunWatchConformanceTestskit indatum_test. -
cascade planning is user-scoped: relation traversal queried across ALL users, so another user's rows sharing foreign-key values could restrict-block a delete of data they don't own, and entered plans whose execution (correctly user-scoped) then failed spuriously. All four relation branches now scope by the requesting user.
-
CascadeOptions.timeoutactually fires: the elapsed-time check compared a start time captured the same instant inside the loop (always β 0), so the timeout could never trigger. -
in-memory matcher handles
boollike SQL (0/1): Dart'sboolisn'tComparable, so sorting by a bool field silently no-oped and ordering filters (isGreaterThanetc.) excluded every row β while SQLite sorted and compared 0/1. Matcher and SQL paths now agree. -
SQL converter correctness: LIKE values are escaped (
%/_in acontains:value matched as wildcards instead of literals, with anESCAPEclause now declared),OFFSETwithoutLIMITno longer emits invalid SQLite syntax (LIMIT -1is added), and an emptyCompositeFilterrenders1=1/0=1instead of the syntax error(). -
query cache keys are collision-free: the key ignored the query's
logicalOperator, the CONTENTS of nestedCompositeFilters, andnullSortOrderβ withenableQueryCache: true, an OR query could be served the cached results of the AND query over the same filters. -
vector clocks only advance for local edits:
push()incremented this device's clock component even forDataSource.remotesaves (the realtime change-stream path), so merely observing another device's edit claimed a causal step and made every later legitimate remote update look concurrent β spurious conflicts, and lost updates under local-leaning resolvers. -
cascade delete honors
setNullon the plain path:cascadeDelete()hard-deletedHasMany/HasOnechildren markedCascadeDeleteBehavior.setNullinstead of patching their foreign key to null β only the fluentdeleteCascade(...).execute()path handled the update steps. Both paths now detach identically, and detached rows are no longer counted indeletedEntities. -
cascade plans no longer read stale relationship caches: the relationship-query cache can't observe writes made through other managers (its keys don't reference child ids), so a
restrictblocker that was deleted through its own manager still blocked the retry, and cascades could plan against rows that no longer existed. EverybuildCascadeDeletePlannow starts from a fresh view; the cache still dedupes lookups within a single plan build. -
conflict detection: equal-version concurrent edits are now detected. Two devices that each bumped the same ancestor (v1 β v2) produce identical version numbers with divergent content β the most common concurrency signature when entities carry no vector clocks. Previously this returned "no conflict", the pull path kept the local silently, and the LWW winner was stranded on its own device forever (permanent split-brain, found by the
datum_testconvergence fuzz suite). The detector now flags it asbothModified; identical re-delivered rows short-circuit cheaply. -
LastWriteWinsResolver: exact
(version, modifiedAt)ties with divergent content now break by a deterministic payload comparison instead of preferringlocalβ the old bias made each device elect ITSELF, resolution pushes ping-ponged between replicas, and the fleet never converged (also found by the convergence fuzz suite). -
in-memory adapter: fixed a race in the
watch*streams β the change subscription attached only after the awaited initial read, so a write landing in that window was silently missed by new watchers. The subscription now attaches synchronously on listen. -
isolate sync:
useIsolateSync: truenever actually worked β theIsolate.runclosure referenced the manager's class type parameter, and Dart closures capturethisto reach instance type arguments, so the send always failed withArgumentError(unsendable stream controllers) before the isolate spawned. The spawn now goes through a top-level trampoline whose own type parameter carriesT, and isolate sync completes with sendable adapters. Note thatIsolate.rundeep-copies the adapter graph: in-memory adapters' writes stay in the isolate; storage-backed adapters (Hive/SQLite/network) persist normally. -
metadata: replaced the hardcoded
'testhash'placeholder with a real order-independent content hash. The sync-skip check had degraded to a bare count comparison, so a remote content change that kept the entity count identical was never pulled and devices diverged permanently. -
request strategy: rewrote
SequentialRequestStrategywithoutasync_queue. The old implementation assumedqueue.retry()throws on exhaustion (it doesn't), could hangsynchronize()futures forever, shared one global queue across every default-config manager (const canonicalization) and stopped it for everyone ondispose().retryCountnow actually works and defaults to 0 (matching real shipped behavior). -
cancellation: pausing mid-sync now returns
wasCancelled: true, keeps thepausedstatus, and skips the metadata stamp β a truncated cycle was previously reported as a fully successful sync. -
timeout:
config.syncTimeout/options.timeoutis now enforced (it was configured everywhere but never applied); a hung remote surfaces a typedDatumExceptionCode.timeoutand fails the status. -
conflicts:
takeLocal/mergeresolutions now queue a push of the winner so the remote converges (the same conflict previously re-fired forever and merged values never reached other devices);takeRemotehonors a resolver-transformedresolvedData;abort/askUserare no longer counted and reported as resolved; deletion-conflict resolutions incrementconflictsResolved. -
batching: a retryable batch failure re-queues its operations with
retryCount + 1instead of permanently dropping the whole batch (a single offline blip during a batched push previously lost every operation in it). -
isolate sync:
useIsolateSyncconfig sanitization actually clears unsendable callbacks now (copyWith(x: null)keeps the old value) viaDatumConfig.sanitizedForIsolate. -
testing: added a real local HTTP sync-server harness (
test/harness/local_sync_server.dart+ referenceHttpRemoteAdapter) with fault injection (latency, 5xx/4xx, severed sockets, offline, version conflicts, corrupted payloads) and 28 scenarios covering transport edge cases, multi-stream consistency, leak/timer hygiene, cache boundedness, and soak/lifecycle-churn stability. -
sync: the metadata skip pre-check now degrades to a full sync when the remote metadata fetch fails transiently (previously an unprotected read failed the whole cycle before it started).
-
sync: a failed remote metadata beacon write no longer fails an otherwise-successful cycle (it is an optimization rewritten by the next successful sync); a failed local metadata write still surfaces.
-
relations:
HasOne.fetch()now queries the child by its foreign key (mirroringHasManyand the eager-loading stitcher); it previously looked the child up by primary id equal to the parent's key, returning null unless the ids coincidentally matched. -
metadata: a push-only sync no longer stamps the local metadata with its own content hash (which fabricated a "local == remote" match and caused the next sync to skip unpulled remote changes); the remote still receives the change beacon so other devices pull.
-
strategy:
ParallelStrategy(failFast: true)now waits for already-dispatched sibling operations to settle before rethrowing, so no writes continue in the background after a sync reports failure. -
errors: the manager's sync error path delegates to
SyncErrorHandler.handleManagerSyncErrorSync, preserving the original stack trace (previously dropped by a barethrow e.originalError). -
misc: global sync result accumulates pull failures/conflicts uniformly across directions;
DatumSyncStartedEventreports the fresh pending count; global observers receiveonSyncEndon failed syncs;ExponentialBackoffclamps instead of overflowing negative;ParallelStrategy(batchSize <= 0)no longer loops forever; cold-start guard is claimed before its awaited check (TOCTOU);pauseSync/resumeSynchardened;CascadeDeleteResult/ColdStartConfig/ColdStartStrategyare now exported (public API types that were unnameable).
π Bug fixes #
- config:
DatumConfig.copyWith<T>()no longer drops the globaldefaultConflictResolver(anddefaultSyncOptions) when deriving a per-entity config. A base-typed resolver is now adapted viaTypeAdaptedConflictResolverinstead of being silently nulled. - read:
read()no longer caches negative (absent) results, so data that arrives later via sync/realtime is observed instead of returning a stale null. - query: local query caching is now off by default (
enableQueryCache), fixing stale/mutated results and broken reactive updates. Opt in if needed. - sync: the pull phase no longer blindly overwrites local data when a
vectorClockis null β it falls back to version, thenmodifiedAt, avoiding redundant writes and sync noise. - sync:
pauseSync()/resumeSync()reliably restore auto-sync timers (hardened against thestopAutoSync()clear side-effect). - generator: fixed
fromMaptimestamp key mismatch (snake_case with a camelCase fallback) and removed the redundant duplicate key lookup. - generator:
copyWithAllandfromMapnow only pass real constructor parameters, so entities with initializer-derived fields (e.g.: userId = id) generate valid code.
β¨ Features #
Merged from the unpublished 1.0.5 #
-
generator: granular
@DatumIgnoreflags βcopyWith:,equality:,fromMap:/toMap:let runtime-only state (e.g. aValueNotifier) live inside entities without breaking immutability or equality. Backward compatible with bare@DatumIgnore(). -
relations (breaking vs 1.0.4):
ManyToManytakes aTypefor the pivot entity instead of an instance, removing the const zero-argument constructor requirement on pivot entities. -
query:
DataFetchStrategy(localOnly/remoteOnly/localFirst/remoteFirst) viamanager.fetch(...)andmanager.fetchById(...), with an optionalpersistRemoteResultscache-fill. -
relations: nested eager loading with dot notation, e.g.
withRelated: ['posts.author']. -
relations: instance-free relation schema (
DatumRelationSchema+RelationDescriptor) so adapters can traverse relations by type alone. -
relations:
relatedList<R>('posts')/relatedOne<R>('author')typed accessors, andpreserveRelationsFrom(...)to keep in-memory relation references across acopyWith. -
query: adapter-aware raw queries (
DatumRawQuery+RawQueryCapable+manager.rawQuery(...)) for projections/aggregations without hydration. -
manager:
exists(id)andcount({query})convenience methods. -
errors: sealed
DatumErrortype +tryRead/tryPush/tryQuery/tryDelete/trySynchronizeresult-returning API (no try/catch needed). -
query: type-safe field selectors (
DatumQueryField+whereField/orderByField). -
logging: pluggable
DatumLogSink(redirect logs without subclassing). -
adapters: shipped
InMemoryLocalAdapter, a reusableDatumQueryMatcher, and capability markers (WatchableAdapter,TransactionalAdapter,RawQueryCapable, β¦). -
sync: opt-in remote-deletion detection (
detectRemoteDeletions) with aDatumConflictResolution.deleteLocal()resolution. -
sync:
excludedSyncUserIdsto keep local-only/system users out of sync. -
sync:
DatumSyncOperation.entityTablefor deterministic multi-entity pending stores. -
generator:
@DatumSerializable(strictNullChecks: true)opt-in so missing non-nullable primitives surface instead of being silently defaulted;generateMixinnow defaults totrue. -
perf: order-independent
hashEntitiesUnordered+ O(1) incrementalDatumRollingHash(~420Γ faster set-hash updates than a full rehash). -
relations:
withRelatednow eager-loads all four relation kinds (HasOne/ManyToManyadded); reactivewatchAll/watchQueryacceptwithRelated; hand-written entities can use theMemoizedRelationsmixin. -
manager:
exists,count,deleteMany,trySaveManyconveniences;querynow defaults tosource: DataSource.local. -
streams: reactive
watch*methods return non-null streams (empty when the adapter isn't watchable). -
errors:
Datum.initializereturnsDatumEither<DatumError, Datum>(typed failure);DatumError implements Exception;DatumEithergainssuccess/failuregetters. -
debug:
DatumSyncResult.describe()andDatumHealth.describe()for readable logging;DatumConfigPresets.custom(...)exposes the newer flags; typed relation accessorsrelatedList<R>()/relatedOne<R>(). -
manager:
trySwitchUser/tryCascadeDeleteresult-returning variants. -
exports:
CascadeDeleteResult/CascadeResult/CascadeDeleteBuilderare now exported (public return types were previously unnameable). -
docs: new
doc/API_GUIDE.mdcovering querying, relations, results/errors, and configuration.
See doc/ADAPTERS_AND_MIGRATIONS_GUIDE.md (Drift/Isar/migrations) and
doc/API_DESIGN_AND_TESTING_PLAN.md for details.
1.0.4 #
π Bug Fixes #
- auto-sync: fix stopAutoSync() incorrectly clearing _pausedAutoSyncUserIds
- Fixed an issue where
stopAutoSync()was incorrectly clearing the_pausedAutoSyncUserIdsset, which prevented auto-sync restoration after pause/resume cycles - This ensures that paused user IDs are properly maintained when stopping auto-sync, allowing correct restoration of auto-sync state when resumed
- Thanks to @vipwpcom for the bug report and test
- Fixed an issue where
π Documentation #
- dartdoc: fix unresolved documentation references
- Fixed
remoteAdapter.getSyncMetadataβRemoteAdapter.getSyncMetadata - Fixed
resubscribeToRemoteChangesβDatumManager.resubscribeToRemoteChanges - Fixed
unsubscribeFromRemoteChangesβDatumManager.unsubscribeFromRemoteChanges - Fixed
AdapterHealthStatus.okβAdapterHealthStatus.healthy(2 instances) - All dartdoc warnings resolved (0 warnings, 0 errors)
- Fixed
β‘ Improvements #
- dependencies: move flutter_test to dev_dependencies
- Moved
flutter_testfrom dependencies to dev_dependencies in pubspec.yaml - Improves package compatibility and pub.dev score (now 150/160 points)
- Package can now be analyzed without requiring Flutter SDK for consumers
- Moved
1.0.3 #
π Relational Data Enhancements #
- Eager Loading: Support
withRelatedinread()andreadAll()to solve N+1 query problems. - Advanced Cascade Controls: More granular deletion behaviors (e.g.,
SetNull) and visualization of delete plans. - Transactional Relationships: Atomic saves for entities and their pivot/related records.
β‘ Performance & Scaling #
- Batch Operations: Support for batch push/pull in adapters and sync engine (Includes comprehensive test suite with 14 edge case scenarios and 100% pass rate)
- LRU Cache: Size-limited caching in
DatumManagerto prevent memory bloat. - Full Isolate Syncing: Offloading the entire synchronizer to a background Isolate.
π Advanced Sync Logic #
- Conflict Resolution Strategies: Initial support for CRDT-based merging implemented via
VectorClockandDatumEntityInterface.merge(). - Vector Clocks: Implemented for complex multi-device conflict detection and causality tracking (moving beyond simple version numbers).
π Developer Experience (DX) #
- Code Generation: Automated
toDatumMap,fromMap,diff, andcopyWithusingdatum_generator.
1.0.2 #
π Bug Fixes #
-
core: prevent direct usage of DatumEntityInterface
- add checks to prevent using DatumEntityInterface directly in manager and query methods
- throw ArgumentError with a descriptive message if DatumEntityInterface is used directly
- add test cases to ensure ArgumentError is thrown when using DatumEntityInterface directly
-
datum-manager: add error handling to post-fetch transforms
- Prevent entire read/watch operations from failing when individual entity transforms throw errors. Log errors and use original entities instead, improving robustness in DatumManager methods like readAll, watchAll, watchById, and watchQuery.
β¨ Features #
-
core: add refreshStreams method to Datum singleton
- Add refreshStreams() method to Datum.instance that clears caches and forces all reactive streams across all managers to re-evaluate their data. This ensures streams show the most current data after external state changes like user switches. Includes proper logging and error handling.
-
datum-manager: add refreshStreams method to DatumManager
- Add refreshStreams() method to DatumManager that clears internal caches (query, relationship, entity existence) and forces reactive streams to emit fresh data. Useful for cache invalidation when external systems modify data that Datum isn't aware of. Includes proper logging and cache management.
-
core: add userChangeStream to Datum singleton
- Add userChangeStream property to Datum.instance that emits when the active user changes. This enables reactive queries and UI updates when users switch in multi-tenant applications. The stream emits the new user ID or null when logging out.
-
adapter: add realtime watch methods for Supabase adapter
- Implement watchAll and watchById methods to enable real-time data watching via Supabase RealtimeChannel. These methods allow subscribing to changes in the table, fetching initial data, and emitting updates on changes, improving data synchronization for user-specific or all records. Includes proper error handling, logging, and channel management
-
hive_adapter: add reactive user change support to watchAll method
- Add optional userChangeStream parameter to HiveLocalAdapter constructor and enhance watchAll method to emit updated data when the active user changes. This enables reactive queries that filter and refresh data based on user ID switches, improving app responsiveness in multi-user environments. Includes error handling and proper stream management.
1.0.1 #
β¨ Features #
-
core: add connectivity monitoring and auto-sync
- Introduces a new feature that monitors the device's connectivity status and automatically triggers a sync when connectivity is restored.
- This ensures that any pending operations that were queued while offline are automatically synchronized once the device is back online.
- Fixes an issue where users had to manually trigger a sync after regaining connectivity.
- Adds a new deleteBehavior option to the DatumConfig to allow developers to choose between soft and hard deletes. Soft deletes mark items as deleted locally and queue a delete operation, while hard deletes immediately remove the item from local storage.
- Adds a HiveDatumPersistence class to the example app to demonstrate how to use Hive for data persistence.
-
delete: add optional behavior parameter to delete methods
- added DeleteBehavior? behavior parameter to delete and deleteAndSync methods in Datum class, allowing per-operation override of global delete behavior
- updated method documentation to explain the new parameter
- improved dispose method to safely handle instance checks and nullify the singleton instance
- enhanced test setup in background_sync_test.dart with proper mocking of ConnectivityChecker
- added error handling in integration test for Datum.initialize to catch and report failures
π Bug Fixes #
- datum: revert default delete behavior to hard delete
- revert default deleteBehavior to hardDelete in DatumConfig
- update tests to explicitly use soft delete where needed
1.0.0 #
ποΈ Breaking Changes #
- core: Removed deprecated
pause()andresume()methods fromDatumManagerandDatumclasses - useunsubscribeFromRemoteChanges()andresubscribeToRemoteChanges()instead
β¨ Core Library Features #
- Entity System: Enhanced entity definitions with interfaces and mixins for more flexible implementations
- Sync Engine: Added initial sync on user authentication, metadata comparison for optimized syncing, device tracking, and improved error handling
- Auto-sync: Enhanced auto-sync functionality with better scheduling and management
- Configuration: Added default sync options and remote metadata access
- Logging: Advanced logging features with performance monitoring and sampling
- Cold Start Manager: Major architectural improvements to cold start synchronization including per-user state isolation, configurable retry logic with exponential backoff, pluggable persistence interface, enhanced error handling and recovery, and comprehensive testing. Replaced static state with instance-level per-user state management to prevent race conditions and enable proper multi-user support. Added retry policies, error recovery mechanisms, and extensible persistence layer for custom storage solutions.
- Cascading Delete: Major enhancements to cascading delete functionality including dry-run mode, progress callbacks, cancellation support, timeout protection, and improved error handling. Added comprehensive dry-run capabilities for safely previewing deletion operations before execution. Enhanced cascading delete integration tests with 48 total test cases covering complex relationship scenarios, mixin usage patterns, restrict violations, and edge cases.
β»οΈ Refactors #
- Entity Handling: Improved entity mixins and relational detection
- Sync Performance: Batch processing, performance monitoring, and enhanced error boundaries
- Concurrent Operations: Better handling of concurrent sync operations
π Bug Fixes #
- Sync Engine: Fixed return values and unused variables in tests
- Cascading Delete: Removed unused
_CascadeDeleteStepand_CascadeDeletePlanclasses and fixed method call inCascadeDeleteBuilder.execute()
π Documentation #
- API Documentation: Enhanced documentation for Datum singleton API, sync patterns, and troubleshooting guides
Medium Priority (Next Release):
- Parallel execution
- Progress callbacks
- Relationship caching
- Rollback capability
0.0.13 #
- fixed type casting error in
initialize()method in Datum
0.0.12 #
β¨ Features #
-
core: Add stacktrace to DatumEither
- The
Failureclass now includes an optionalStackTraceproperty. - The
foldmethod inDatumEithernow passes theStackTraceto theonFailurecallback. - The
onFailuremethod now accepts aStackTraceparameter. - The
getErrormethod now returns a tuple containing the error value and the stack trace.
- The
-
core: Bring back getSuccess method
- Added the
getSuccessmethod back to theDatumEitherclass. - This method returns the success value if the
DatumEitheris aSuccess, otherwise it throws aStateError.
- Added the
β»οΈ Refactors #
-
core: Remove isSuccess and isFailure methods
- Removed the
isSuccessandisFailuremethods from theDatumEitherclass.
- Removed the
-
core: Use switch statement instead of if statement
- Refactor the
onSuccess,onFailure,getSuccess,getError,successOrNull, anderrorOrNullmethods to use switch statement instead of if statement.
- Refactor the
0.0.11 #
β¨ Features #
- core: introduce DatumEither for initialization result
- Use DatumEither to handle potential errors during Datum initialization
- Return Success or Failure based on the outcome of the initialization process
- Update related code to handle the new DatumEither return type
- Add DatumEither model for typing success or failure.
0.0.10 #
β¨ Features #
- Batch Operations: Added
createManyandupdateManymethods for performing batch create and update operations. - Lifecycle Management: Implemented
DatumProviderWithLifecyclewidget to manage Datum's lifecycle based on app state. - Flexible Entity Implementation: Introduced
DatumEntityMixinandRelationalDatumEntityMixinto allow for more flexible entity implementation without requiring inheritance from a base class. - Schema Versioning: Added
schemaVersionproperty toIsolatedHiveLocalAdapterfor easier schema migration. - Type Comparison: Added a
sameTypesmethod for type comparison. - Dependencies: Added
equatabledependency for easier object comparison.
π Bug Fixes #
- Logging: Removed unnecessary debug logs from
tasksStreamProvider. - Initialization: Ensured managers are initialized before
saveManyoperations. - Memory Leaks: Improved stream handling in
SupabaseRemoteAdapterto prevent memory leaks. - Error Handling: Improved type safety and error handling in
fetchRelatedmethods.
β»οΈ Refactors #
- Background Sync: Enhanced
SupabaseRemoteAdapterwithresubscribeToChangesandunsubscribeFromChangesmethods for better background sync and lifecycle management. - Entity Handling: Updated
DatumEntityBaseand related classes for better sync and versioning. - Adapters: Updated
HiveLocalAdapterandSupabaseRemoteAdapterto useDatumEntityBaseinstead ofDatumEntity. - Task Entity: Refactored the
Taskentity to useDatumEntityMixin. - Sync Execution: Updated the default sync execution strategy to
parallel. - Data Serialization: Enhanced data serialization for local and remote persistence.
π Documentation #
- Datum Class: Enhanced
Datumclass documentation for clarity and improved usage examples. - Sync Options: Enhanced
DatumSyncOptionsdocumentation for better clarity. - General: Improved overall documentation for clarity.
β Tests #
- Background Sync: Added tests for background sync functionality.
0.0.9 #
β¨ Features #
Core #
- Implement Sync Request Strategies: Introduced a new system to control how concurrent calls to the
synchronizemethod are handled, preventing race conditions and improving data consistency.- Added
DatumSyncRequestStrategyas the base for defining execution behavior. - Implemented
SequentialRequestStrategyto queue and process allsynchronizecalls in the order they are received. This is the new default behavior. - Implemented
SkipConcurrentStrategyas an alternative strategy to ignore newsynchronizecalls if a sync is already in progress. - Added
syncRequestStrategytoDatumConfigto allow global configuration of this behavior. - Added an
isSyncinggetter toDatumSyncEngineto check the current sync status.
- Added
π Bug Fixes #
Build #
- Correct Conditional Imports: Fixed conditional imports to ensure compatibility across both
dart:ioanddart:htmlenvironments.
0.0.8 #
- fix conditional import for web and io
0.0.7 #
π Bug Fixes #
- π Isolate Error Handling & Web Compatibility:
- Ensured errors during isolate operations are properly caught and sent back to the main thread.
- Enhanced web compatibility by using
computefunction for isolate operations. - Removed unnecessary newline at end of file for consistency.
- Removed unused import in
supabase_security_dialog.dart.
0.0.6 #
π Features #
- π Isolate Sync Strategy: Introduced a new
IsolateStrategythat runs data synchronization in a background isolate for improved performance and UI responsiveness. This includes platform-specific runners for both mobile/desktop (dart:io) and web (dart:html) via conditional imports, ensuring broad platform support. - β¨ Sealed Class Migration: Migrated
DatumEntityandRelationalDatumEntityto aDatumEntityBasesealed class for enhanced type safety and to remove the need forsampleInstance. - π New Facade Methods: Added a suite of new methods to the global
Datumfacade for easier data interaction:- Reactive Watching:
watchAll,watchById,watchQuery,watchRelated. - One-time Fetching:
query,fetchRelated. - Data & Sync Management:
getPendingCount,getPendingOperations,getStorageSize,watchStorageSize,getLastSyncResult,checkHealth. - Sync Control:
pauseSync,resumeSync.
- Reactive Watching:
β Tests #
- π§ͺ Enhanced Core Tests: Added test cases for uninitialized state errors,
statusForUser,allHealths, and relational method behavior. Introduced aCustomManagerConfigfor easier mock manager injection in tests.
β»οΈ Refactors & π§Ή Chores #
- β»οΈ Isolate Helper Improvements:
- Replaced conditional imports with platform-specific implementations.
- Removed
isolate_helper.dartandisolate_helper_unsupported.dart. - Added
_isolate_helper_io.dartfor IO platforms. - Updated
_isolate_helper_web.dartto use synchronous JSON encoding. - Updated
datum_sync_engine.dartto use the new isolate helper. - Removed unused imports in
test.dart,adapter_test.dart,relational_data_test.dart,relational_data_integration_test.dart,mock_adapters.dart, andtest_entity.dart. - Updated
isolate_helper_test.dartto use the new isolate helper.
- ποΈ Removed
sampleInstance: ThesampleInstanceproperty onLocalAdapteris no longer needed due to the sealed class migration and has been removed. - π©Ί Renamed
AdapterHealthStatus.oktoAdapterHealthStatus.healthyfor better clarity. - π¦ Refactored internal imports to use the
datumpackage consistently. - βοΈ Made
MigrationExecutorgeneric to improve type safety during migrations. - πΊοΈ Added
DataSourceenum to explicitly specify the source for query operations.
0.0.5 #
- Add docs link
0.0.4 #
Features #
- Added support for funding and contributions.
Documentation #
- Added
CONTRIBUTING.mdandCODE_OF_CONDUCT.md. - Updated
README.mdwith funding and contribution sections. - Updated
README.mdto mention future support for multiple adapters for a single entity.
Chores #
- β¨ chore(analysis): apply linter and formatter rules
- enable recommended linter rules for code quality
- set formatter rules for consistent code style
- ignore non_constant_identifier_names error
0.0.3 #
-
π docs(readme): enhance architecture diagrams in README
-
update architecture diagrams for better clarity
-
improve image display using
tag for alignment
0.0.2 #
- Update readme to add images correctly
0.0.1 #
- Initial release π
