pocketbase_drift 0.7.2
pocketbase_drift: ^0.7.2 copied to clipboard
A powerful, offline-first PocketBase client backed by a drift database.
0.7.2 #
Fixes #
- State streams report errors only in
QueryState.error.watchRecordStateandwatchRecordsStateemitted a fetch or cache-query error twice: as a stream error event and in the nextQueryState. WithStreamBuilder, the error event replaced the snapshot's data for one build (AsyncSnapshot.withErrorhas no data), and with Riverpod'sStreamProviderthe value flipped fromAsyncErrortoAsyncData. They now emit only the state witherrorset; the data stays available next to it.watchRecordandwatchRecordsstill emit errors as stream errors.
New #
QueryState.copyWith(clearError: true). Removes the error from the copy;copyWith(error: null)keeps it, as before.
Behaviour changes #
- Code that handled errors of
watchRecordState/watchRecordsStateinonError, or throughAsyncValue.errorof aStreamProvider, must readQueryState.error(state.hasError) instead.
Docs #
- README "Which one to use": state streams for
StreamBuilderand for UI that shows refresh or error state next to cached data; plain streams for composition, non-UI consumers and tests; either with Riverpod.
Dependencies #
sqlite3^3.9.0(was^3.5.2): SQLite 3.54.0, faster opening of IndexedDB-backed web databases with dart2wasm, and memory-safety fixes on the web. The minimum Dart SDK stays 3.10, as with 3.5.2.
0.7.1 #
Fixes #
- An expired session no longer empties the cache or drops queued changes. PocketBase answers a request with an expired token as a guest: owner-scoped list rules (
owner = @request.auth.id) return no rows, and reads, updates and deletes of the account's records get 404. 0.7.0 replaced the cached rows with that empty answer (live-query refreshes after a realtime reconnect,getFullList), removed cached records on the 404 (getOne), and the sync dropped queued updates as "deleted on the server", treated queued deletes as done and counted rejected creates towardslocalSyncRetryCount. While the auth store holds an expired token (client.hasExpiredSession), the package now treats the server like an unreachable one for cache-backed work:- Sync pauses; queued changes stay pending.
pushPendingreturnsPushOutcome.retryLater. A request whose token expired while it was in flight is retried later, not dropped or counted. - Live queries keep the cache and do not refresh.
- Record reads with
cacheFirst,networkFirstorcacheAndNetwork(and collection reads) answer from the cache without a request. A cache miss throwsClientExceptionwith status 401. cacheAndNetworkwrites and batches are queued; the background send ofcacheFirstwrites and batches waits.networkOnlyandnetworkFirstwrites still go to the server, but a 404 no longer removes the cached record.- A login or token refresh that leaves a valid token resumes the work: queued changes sync and the paused live queries refresh. Before, a token refresh of the same account did not start a sync.
- Not detected: a token the server revoked before its expiry. Check the session with
authRefresh()at start and on resume. See "Expired sessions" in the README.
- Sync pauses; queued changes stay pending.
- Realtime no longer shows the previous account's records after a logout or an account switch. PocketBase keeps the auth that a realtime connection had when its subscriptions were sent, and allows no change except guest → account; the SDK sends the subscriptions again only for new topics. With a live query open across a logout or an account switch, the connection kept the previous account's auth, and its events were written to the cache, where the guest or the next account saw them. A login after a realtime reconnect with an expired token left a guest connection that got no events of the account. Now:
- On a login, a logout, an account switch or a login after an expired session, the client ends the realtime connection; the SDK reconnects and sends all subscriptions (the app's own too) with the current token, and the live queries refetch. A token refresh of the same account keeps the connection.
- Until the reconnect, events of a connection that is still authenticated as another account are dropped: they do not reach the cache, the live queries or
collection(...).subscribecallbacks (client.realtimeMatchesSession).
- A killed first start no longer leaves a broken database. The schema is created in one transaction that also writes
user_version. A database that 0.7.0 or earlier left half-created (the process died during the first open; every later open failed withtrigger blob_files_delete already exists) is repaired on the next open: the leftover tables, indexes and triggers of the package are dropped and created again. Such a database holds no data. - A killed upgrade no longer leaves a broken database. Upgrades run in one transaction that also writes
user_version. A v3 → v4 upgrade that 0.7.0 left unfinished (for exampleduplicate column name: cachedAton every later open) finishes on the next open.
New #
client.hasExpiredSession.truewhen the auth store has a token whoseexphas passed (the rule ofauthStore.isValid). An empty token (a guest) is not an expired session.client.onSessionExpired. AStream<void>that emits when the package first pauses work because the session expired, and again only after a valid login. Use it to ask the user to log in again; also checkhasExpiredSessionwhen you start listening.RequestPolicy.fetch(authScoped:). Marks reads whose answer depends on the logged-in account; the package's record and collection reads set it.client.realtimeMatchesSession.falsewhile the realtime connection is still authenticated as an account other than the logged-in one; realtime events are dropped then.
Behaviour changes #
- A token that is not a JWT counts as an expired session, like in
authStore.isValid. Tests with a mock server that log in with a placeholder token ('token') must use a JWT with a futureexp, or sync and live-query refreshes pause. client.httpClientFactoryreturns clients that wrap the clients of the configured factory: the client hands out the realtime (SSE) response through a stream it can end, to reconnect after an account change. Other requests pass through unchanged.
0.7.0 #
Breaking changes #
- Server answers are no longer hidden behind the cache. Reads (
getOne,getList,getFullList,getFirstListItem, cachedsendGETs) fall back to the cache only when the server is unreachable: status 0, timeout, socket error, 408, 429 or 5xx. A 4xx answer (400, 401, 403, 404) is thrown asClientExceptionwith the server status. Callers that relied on cached data after a 403 or 404 must catchClientExceptionand decide themselves.- A
getOne404 removes the stale cached copy, unless the row has unsynced local changes. A record created offline and not yet synced is returned from the cache. - When the server is unreachable and nothing is cached, the read throws
ClientExceptionwith status 0 (was a genericException). Replaceon Exceptionchecks withon ClientException catch (e)and teste.statusCode == 0. Package cache misses use the new exportedCacheMissException.
- A
cacheAndNetworkwrites queue only when the server is unreachable. The rule is the same as for reads. A rejection (400 validation, 403 rule) is thrown and nothing is stored locally. Callers must handle theClientException(for example show the validation errors).networkFirstupdate no longer creates. A 404 or 400 answer is thrown. It was turned into a create. Callcreateyourself when you want an upsert.- Strict writes report an unreachable server as status 0.
networkOnlyandnetworkFirstcreate, update and delete convert connectivity failures (no connection, socket or HTTP client errors,requestTimeoutelapsed) toClientExceptionwith status 0.originalErrorholds the cause. A 502, 503 or 504 answer stays the server'sClientException. Replaceon TimeoutExceptionandon SocketExceptionhandlers with astatusCode == 0check. - Sync sends only the changed fields. For an update, sync sends the fields changed locally (
pendingFields). For a create, it sends the whole record. File uploads are sent once as multipart parts and are not re-uploaded on later edits. Changes from other devices to other fields are kept. A field changed only with+/-modifiers is sent as a modifier, so changes from other devices to that field are kept too (see "Queued modifier edits sync as deltas" in Fixes). No caller change is necessary. - Rejected changes are parked, not deleted. After
localSyncRetryCountrejections, the row stays in the cache with asyncErrormap (status,message,data,at). The failure is reported onclient.syncFailures. Automatic sync skips the row. Retry it withclient.syncPendingChanges(includeFailed: true)orretryLocal(includeFailed: true). Remove it withclient.discardPendingChange(collection, id). An offline update of a record that was deleted on the server is dropped and reported withSyncFailure.dropped == true. Apps that expected the row to disappear must listen tosyncFailuresand discard or retry. retryLocallost its unusedbatchparameter. Remove the argument. The newincludeFailedparameter also retries parked rows.- Logout keeps unsynced changes.
authStore.clear()withclearOnLogout(defaulttrue) deletes the cache except pending rows and their blobs. Pending rows belong to the account that wrote them (pendingOwner). Other accounts do not see them in reads, search or sync. They sync when that account logs in again. Callclient.db.clearAllData()when logout must also discard unsynced changes. - Cache TTL uses the cache time.
runMaintenanceexpires rows bycachedAt(new column, database schema v4), not by the server'supdatedvalue. Rows fetched by list reads now expire too.cleanupExpiredFilesalso removes the blobs of records that are no longer cached. The migration setscachedAtof existing rows to the upgrade time. - Local filters follow the PocketBase quoting rules. Date macros produce the PocketBase format in UTC (
2026-11-04 00:00:00.000Z).@yesterdayand@tomorroware now ± 1 day, like the server. The parser accepts the\",\'and\\escapes.~and!~escape%and_like the server.@request.auth.idon the right-hand side resolves to the logged-in account id (client.authStore.record?.id), or""when nobody is logged in, like the server. Other right-hand sides that the cache cannot evaluate (other@request.*values,@collection.*, field references) and@tokens on the left side throwUnsupportedError. A malformed filter throwsFormatException.FilterTerm,LogicalOperatorandValueTypeare no longer exported.FilterParserhas the newnowandauthIdparameters. UsenetworkOnlyfor filters that the cache cannot evaluate. PocketBaseImageProvidercleanup. Thecolorparameter and the staticgetFilterColorare removed (they had no effect). Remove these arguments. There is no fallback 100×100 decode. Providers with the same inputs are equal, so they share oneImageCacheentry. A failed load is evicted, so the next build retries.getFileBytesdownloads have a timeout and typed errors. The defaulttimeoutis 30 s. When the download fails and no cached copy exists, an unreachable server or a timeout throwsClientExceptionwith status 0 (originalErrorholds the cause). A non-200 answer throws the server'sClientExceptionwith the status, URL and response. AcacheOnlycache miss throwsCacheMissException(was a genericException).cacheFirstnow downloads on a cache miss or an expired copy (it threw). Thumbs (thumb: '100x100') are cached separately from the original file. The file name parameter isfilename.$PocketBase.setSchemarethrows invalid schema JSON. It was logged and swallowed. Catch the error if your schema source can be invalid.networkOnlywatchers stream server data.watchRecord*/watchRecords*withnetworkOnlyemitted an always-empty cache stream. They now emit the server's data and refetch on realtime events. Withlimit,watchRecordsfetches onlylimitrecords from the server.statusStreamemits the current status once per listener. Each new listener gets the current status first. Other listeners no longer receive re-emits.- The database runs in a background isolate. The default (non-web) connection uses
NativeDatabase.createInBackgroundwith WAL.inMemory: truestays on the calling isolate. - Batch changes. Creates and upserts without an
idget a client-generated id. AcacheAndNetworkbatch that the server rejects throws and stores nothing. Only an unreachable server queues the operations. Files passed to batch operations are now sent (they were ignored) and are replayed offline.networkOnlyandnetworkFirstbatches throwClientExceptionwith status 0 when offline. - Example app:
schemaProviderremoved.createPocketBaseClient(prefs, schemaJson)awaitssetSchema. OverridepocketBaseProviderat startup with the returned client. - Local writes never edit another account's pending change. A local update or delete of a record with an unsynced change of another account (kept after logout) throws
StateError. The change syncs when that account logs in again. - Size-limited file cache.
maxFileCacheByteson$PocketBase(...)and$PocketBase.database(...)limits the file blob cache: downloaded files, thumbs and the cached copies of uploads. The default is$PocketBase.defaultMaxFileCacheBytes: 128 MB, 32 MB on the web (the IndexedDB fallback keeps the whole database in memory). SetmaxFileCacheBytes: nullto keep the 0.6.x behaviour (no limit).- After each blob write (download or cached upload copy), eviction runs in the background (one run at a time) and deletes blobs until the cache holds at most 90% of the limit (the eviction target): first the unpinned blobs larger than the target, then the least recently used ones. A cache hit updates the last access of a blob at most once per hour. A file renamed by the server after a push counts as used now.
- Blobs of pending and local-only (
noSync) records are never deleted, also the pending uploads of another account. Blobs of records deleted withcacheOnly(local-only tombstones) can be deleted. When the pinned blobs alone keep the cache above the limit, eviction pauses until the cache grows by 10% of the limit or a sync pass ends. - A file larger than 90% of the limit is returned but not cached: downloads, and the copies of uploads that the server accepted. Unsynced uploads are always stored.
client.fitsFileCache(bytes)tells if a file of that size goes into the cache. - Only blobs written through
client.db.setFile(all package writes) take part in the limit. A blob inserted intoBlobFileswith raw SQL has noBlobFileStatsrow: it is not counted and never evicted. runMaintenance()applies the limit and returns up to 4096 free pages (about 16 MB) to the file system withPRAGMA incremental_vacuum. It does this also when the TTL isnull(it was a no-op then). It never runs a fullVACUUM.- New databases use
auto_vacuum = INCREMENTAL. A database created before 0.7.0 gets smaller only withclient.db.compact(full: true): a fullVACUUMthat can take seconds and needs free disk space of about the database size. expireAfterkeeps its meaning: it sets freshness. An expired copy is downloaded again when the server is reachable and is still used offline, untilrunMaintenance()with a TTL deletes it (as before). The size limit is the retention control.- Schema: the v3 → v4 migration adds the table
BlobFileStats (fileId, size, lastAccess)with the indexidx_blob_file_stats_access (lastAccess, size)and the triggerblob_files_delete, which deletes the stats row of a deleted blob.BlobFilesis unchanged from 0.6.0. The upgrade does not rewrite the cached blobs: it reads only their sizes (time proportional to the number of rows, not the bytes). Blobs cached before the upgrade have nolastAccessand are evicted first. - Native connections set
PRAGMA journal_size_limitto 8 MB, so a large write does not leave a large WAL file.
DataBase.setFilereturnsFuture<void>. It returned the insertedBlobFile, which copied the blob back from the database isolate. Read the blob withclient.db.getFile(recordId, filename)when you need it.
New #
requestTimeout.$PocketBase(requestTimeout:)and$PocketBase.database(requestTimeout:), default 15 s. It limits single-record reads and writes without file uploads. When it elapses, offline-first writes are queued and reads fall back to the cache.nulldisables the limit.client.syncPendingChanges({includeFailed}). One sync pass at a time: a call during a pass returns a future that completes after one more pass. It runs automatically at startup, when the server becomes reachable, on app resume and after login. It never throws. Collections sync parents first (by relation fields), records oldest first (cachedAt).client.syncCompletedalways completes.client.syncFailures. AStream<SyncFailure>withcollection,recordId,operation(SyncOperation.create,update,delete),error,statusCodeanddropped.client.discardPendingChange(collection, id). Removes a pending local change. The next read loads the server's version.client.db.pendingRecords({service}). ASelectable<Service>of the pending rows. Use.watch()for a live queue. It excludes local-only rows and rows of other accounts, and includes parked rows (syncError).db.pendingServices()lists the collections that automatic sync will push.$RecordService.pending()usespendingRecords.RecordMeta(exported). Names of the row bookkeeping keys:synced,isNew,deleted,noSync,retryCount,pendingFields,pendingFiles,pendingOwner,pendingRev,syncError,pendingBase,baseUpdated,pendingDeltas. Helpersflag,isPendingandstrip.DeltaKind(exported) names the values ofpendingDeltas:number,append,prepend,remove. NewpbTimestamp()formats a time like PocketBase.$RecordService.pushPending(id). Pushes one pending record and returns aPushOutcome(synced,skipped,retryLater,rejected).getFirstListItemOrNull(filter). Returnsnullwhen no record matches.connectivity.canReachServerandconnectivity.probeSoon().canReachServeristruewhen a network interface is up and the server is not known to be down.db.clearCache({keepPending = true}). Deletes cached data, responses and blobs. It keeps the schema and, by default, the pending changes.PocketBaseImageProvider(thumb:, timeout:). The constructor isconst. Decode size: withpixelWidth/pixelHeight, the smallest size that covers the box and keeps the aspect ratio, never enlarged. Elsesize× device pixel ratio. Else the full size.$FileService.getFileBytes(thumb:, timeout:). Concurrent requests for the same file and thumb share one download.client.filesis one instance per client.maxFileCacheBytesand the file cache API. See "Size-limited file cache" in Breaking changes.client.enforceFileCacheLimit()applies the limit now (the package calls it after each blob write).client.fitsFileCache(bytes)returnstruewhen a file of that size goes into the cache (at most 90% of the limit, or no limit).client.db.fileCacheBytes()returns the cache size (the sum ofBlobFileStats.size).client.db.evictFiles(maxBytes:, targetBytes:)deletes unpinned blobs (larger than the target first, then least recently used) and returns(count, bytes, remaining).client.db.touchFile(id)marks a blob as used now.client.db.compact({maxPages = 4096, full = false})returns free pages to the file system and returns the number of pages released.MaintenanceResult.evictedFilesandevictedBytescount the blobs that maintenance deleted for the limit;totalDeletedincludesevictedFiles.deletedFilescounts the expired blobs and the blobs of records that are no longer cached.
Fixes #
- Performance. The database runs in a background isolate. New indexes on
services.serviceand on blob(recordId, filename). Expand is resolved for the whole result with one query per relation field and level (it was N+1 per row and re-read every schema). Schema definitions are cached in memory. The FTS trigger re-indexes only when the record JSON changes. - Performance: record watchers re-run only for their collections.
watchRecord(s),watchRecord(s)Stateandclient.db.$query(...).watch()re-run only for writes to their own collection, to the collections they expand, and (withexpand) for schema changes. Before, every write to any collection re-ran every watcher: 1000 writes to another collection with 20 open watchers caused 20,000 re-runs, now none.pendingRecords().watch(),search(...).watch()and app queries withcustomSelect(readsFrom: {client.db.services})still re-run on every write toservices. Raw SQL writes reach record watchers only when drift is told: usecustomUpdate(..., updates: {client.db.services}), or callclient.db.markTablesUpdated({client.db.services})aftercustomStatement(customStatementalone notifies nothing, as before). - Shared realtime subscriptions. All live queries of a collection topic share one realtime subscription. Each server event is written to the cache once (it was once per watcher).
- Refetch after reconnect. Live queries refetch after a realtime reconnect (
PB_CONNECT) and when the server becomes reachable. Changes made while disconnected are not missed. - Pending edits survive refreshes. List refreshes and realtime events do not overwrite a pending offline edit: the locally changed fields win until they are synced. A realtime delete keeps a row with pending edits; sync reports the conflict.
fields:projections. Projected results only merge into existing cached rows. They never replace rows or insert incomplete rows.- Offline
getListtotals.totalItemsrespects the filter. cacheFirstcold cache.getOnewith an empty cache waits for the server instead of throwing.setSchemarace. Reads and writes wait for a pendingsetSchema. An un-awaited..setSchema(...)no longer races the first write.- No uncaught errors. Realtime events for collections missing from the local schema are logged. Bad filters in
watch*Stateare delivered as stream errors and asQueryState.error. - Connectivity accounting. Only connectivity failures (status 0, timeouts, 502, 503, 504) count towards "offline". Any server answer marks the server reachable.
getFirstListItemnow reports failures too. - Timestamps. Local timestamps have millisecond precision in the PocketBase format.
- Offline create then delete. Deleting a record that was created offline and never synced removes it at once. No server delete is queued. When the create is being pushed at that moment, the deletion is queued instead, so the record does not survive on the server.
- Changes during a push. Pushes, online writes and batch sends of one record run one at a time; a second push waits and then sends what is still pending, so a file is uploaded once. A change made while the record's push is in flight stays pending: an edit keeps the server's names of the files just uploaded, a file attached meanwhile is still uploaded, a deletion stays a deletion, and a
cacheOnlychange stays local. A rejection does not count against (or report) an edit made during the push. Local writes of a cached record are atomic (database transactions). - Online writes win over older queued edits. An online update (
networkOnly,networkFirst,cacheAndNetwork, or a batch update/upsert) of a record with a queued edit removes the fields it set from the queue; only the other queued fields still sync. Exception: an online modifier (n+: 1) of a field with a queued modifier edit keeps both changes; an online plain set (n: 5) supersedes the queued edit of that field. Online writes wait (at mostrequestTimeout) for a running push of the same record, so they reach the server after it. When the push takes longer (an upload), acacheAndNetworkwrite is queued behind it;networkOnlyandnetworkFirstwrites throwClientExceptionwith status 0. A timed-out push keeps its record locked until its request settles (at most 3 ×requestTimeoutmore). - Stalled uploads do not block sync. A sync pass waits at most
requestTimeoutfor each record. A slow upload continues in the background and keeps its record locked, but the pass moves on andsyncCompletedcompletes. - Batch updates and deletes of records created offline. The batch first pushes the queued creates it references (the server rejects the whole batch when a record does not exist yet). With
cacheAndNetwork, the batch is queued when such a create cannot be pushed. - Pending edits from 0.6.x. A pending update written by 0.6.x (no
pendingFields) still syncs the whole record after a new offline edit. client.close()closes the package's realtime subscriptions. The SDK closes the realtime connection when no other subscription is left. Events afterclose()are ignored.- Pruning keeps new pending changes. List refreshes and
limitwindow pruning re-check, in the delete transaction, that a row has no pending change. - Lost create response. A create retried after a lost response (the id already exists) becomes an update instead of failing.
$collection()without collections API access. It uses the cached schema when the collections API is not accessible (offline, or a regular account).- Delete of a record that is not cached is queued.
delete()withcacheAndNetworkorcacheFirstof an id that is not in the cache queues the deletion when the server is unreachable (offline, 503, connection refused, timeout) and, withcacheFirst, also when the server is reachable. Before, nothing was sent. The queued deletion is a bare tombstone row: the id and the bookkeeping keys only (deleted,synced: false,isNew: false,noSync: false,pendingRev,pendingOwner).pendingRecords()can return such rows: a pending-queue UI must not expect record fields.writeLocalDelete(sync: true)(onDataBaseand$RecordService) always returnstrue; it returnsfalseonly forsync: falsewithout a cached row. An offline batch delete of an uncached id returns status 204 (queued), not 404. When the server answers 404 (the record is already gone, or the delete rule does not match), the queued deletion ends without aSyncFailure, the same as for cached tombstones. - Queued modifier edits sync as deltas. Offline and
cacheFirstupdates withn+/n-(numbers) andf+/+f/f-(multi-value relation, select and file fields) sync as modifiers relative to the value they were made on. Numbers send the difference (n+: 2; a negative difference asn-). Multi-value fields send the added values asf+(or+f) and the removed values asf-. Concurrent increments, appends, removals and file uploads of other devices are kept. Before, the queued edit sent the absolute local value and overwrote them.- Last write wins for all other edits: a field with a plain set (
n: 5,tags: [...]) anywhere in its pending history sends its absolute value. This also applies to single-value, JSON and text fields. Pending rows written before this change (no base) send absolute values. - New
RecordMetakeys in the pending row (visible inRecordModel.data):pendingBase(the server value of each pending field when it first became pending),baseUpdated(the serverupdatedvalue then) andpendingDeltas(the fields whose every edit was a modifier, with the kind:number,append,prependorremove). - Until the change syncs, list refreshes and realtime events do not overwrite the locally changed fields. A realtime delete keeps a row with pending edits; the sync then gets 404, drops the edit and emits
SyncFailure(dropped: true). - Limits: mixed append and prepend edits are sent as an append. Float differences can have rounding errors.
- Counters: use
+/-modifiers. Queued modifier edits merge with the changes of other devices; plain sets are last-write-wins. - Additive API:
DataBase.completePendingSync(..., sentBody:, partial:);DataBase.applyRequestBodyalso returnsdeltas.
- Last write wins for all other edits: a field with a plain set (
- Batches keep queued changes. A batch update of a record with an unsynced local change no longer marks the whole change as synced. Fields, increments and uploads that the batch did not send still sync. A value that the batch set is not overwritten later by the older queued value (also with
networkOnly). An edit made while an online batch is in flight is kept, also when it restores the old value. - Sent fields are done. A field that a batch or a sync push sent is not sent again when the server stores it in another form (dates) or does not return it (the
passwordof an auth record). With afields:projection, the cached record keeps its other fields and queued increments are not lost. - Open watchers follow an account switch. After a login with another account, open watchers (
watchRecord(s),$query,pendingRecords(),search()) showed the pending changes of the previous account and hid the pending changes of the new account until the caller listened again. They now change to the rows of the new account without a new listen.@request.auth.idfilters also follow the switch.$query(...).watch(),pendingRecords().watch()andsearch(...).watch()complete when the database closes. - Search ignores the sync bookkeeping. The full-text index (
text_entries) leaves outretryCount,pendingFields,pendingFiles,pendingOwner,pendingRev,syncError,pendingBase,baseUpdatedandpendingDeltas(synced,isNew,deletedandnoSyncstay). A record with a pending edit is found by its new value, not by its old value inpendingBase. The v4 migration re-creates the FTS triggers and re-indexes all records.
Repo & tooling #
example/backend/. A Go backend (PocketBase v0.40.6) for the example app, the package tests and the integration suite. It contains the TaskFlow collections, the package test collections (todo,ultimate,active_todo), the lab collections and the/api/lab/*fault-injection routes (chaos modesfail503,drop,hang,latency; stats; server-side mutations). It seeds the superusertest@admin.comfor the tests.test/test-instruction.mdexplains how to run it.test/integration/. Regression suite for the review defects, run against the example backend.dart_test.yamlruns the test files one at a time.tool/device_lab/. Android profile-mode harness. It measures UI-isolate blocking, the image cache and the probes. It is not part of the package API.example/androidregenerated. Gradle 9, AGP 9, Kotlin DSL. The INTERNET permission is in the main manifest. Cleartext HTTP is allowed only in debug and profile builds.- Drift 2.35.
drift: ^2.35.3anddrift_dev: ^2.35.2. The generated code uses$_getPrefetchedDataByColumns, new in drift 2.35.3. - PocketBase Dart SDK 0.25.3.
pocketbase: ^0.25.3. SDK 0.25.2 closes the realtime connection after 315 seconds without messages and reconnects (a workaround for network connections that end without notice). The package refreshes its live queries after each reconnect. PB_URL. All server-backed tests read the server address fromPB_URL(test/test_server.dart, defaulthttp://127.0.0.1:8090).- New tests.
test/services/offline_delete_test.dart,test/services/watch_scope_test.dart,test/services/delta_sync_test.dart,test/files/blob_cache_limit_test.dartandtest/database_migration_test.dart(driftSchemaVerifier, v2 → v4 and v3 → v4; schema helpers intest/generated_migrations/;drift_schemas/drift_schema_v3.jsonadded).
0.6.0 #
Connectivity: Custom Realtime Server Monitor #
- Replaced
internet_connection_checker_pluswith a purpose-built realtime monitor. The newConnectivityServicetracks reachability of your actual PocketBase server instead of polling third-party hosts:- Interface events (
connectivity_plus): losing the last network interface marks the app offline instantly; regaining one triggers an immediate probe. Works on web (navigator.onLine), mobile and desktop. - Server health probes: a lightweight
GET /api/healthprobe is the source of truth. While online a slow keepalive probe runs (connectivityOnlineProbeInterval, default 60s); while offline, probes back off exponentially (connectivityOfflineBackoffMin5s doubling toconnectivityOfflineBackoffMax60s) so restoration is detected within seconds. - Request outcomes: successful CRUD requests flip the status online instantly; consecutive failures (2) mark it offline.
- Connectivity restoration now always triggers the pending-sync retry. Previously the "connected" platform event did not flip the status, so queued changes could sit unsynced until an unrelated request happened to succeed.
ConnectivityServiceis now a per-client instance (not a singleton) with adispose()— no more leaked 10s check timers in widget tests. The test flagenableInternetCheckerwas renamed toenableProbes.
- Interface events (
Sync Engine: Limbo & Tombstone Fixes #
- Fixed offline-delete limbo: a record created offline and deleted before ever syncing (
isNew: true+deleted: true) made the sync engine attempt a doomed server delete (404) on every sync round forever. It is now purged locally, and server 404s on delete/update retries purge the local copy instead of looping (handles deletions that raced another device). - Offline-deleted records no longer appear in local reads. Tombstoned records (
deleted: trueawaiting server sync) were previously returned bygetFullList,getOne,watchRecords,searchand$count, so deletes appeared to "do nothing" in the UI. They are now excluded everywhere while remaining in the pending queue;client.db.$query(..., includeDeleted: true)opts back in for internal/diagnostic reads. mergeLocalno longer resurrects pending deletes: a background list fetch landing before the sync engine can no longer overwrite a tombstone with stale server data.cacheFirstdeletes now queue properly: the old implementation hard-deleted the local row and fired a best-effort background delete — if that failed (e.g. offline), the deletion was silently lost and the record resurrected on the next fetch. It now writes a syncable tombstone first.
Housekeeping #
- Bumped minimum example/tests to run against a live PocketBase; added regression tests for the limbo purge, tombstone filtering, merge protection, watch-stream removal, and connectivity state machine (probe/backoff/request-signal).
0.5.0 #
New Features & Examples #
- Modernized Example Application (TaskFlow) - Replaced legacy example with a feature-first reference app demonstrating offline-first task and project management, Riverpod state management, GoRouter navigation, relation expansion, local full-text search, and live sync diagnostics.
- Added Skills (
skills/pocketbase-drift/) - Added comprehensive, production-grade agent playbook skills and architecture reference guides directly in the repository.
Web & WASM Improvements #
- Web Database Worker Resolution - Updated Web WASM connection to directly resolve
drift_worker.jsandsqlite3.wasmrelative toUri.base, eliminating unnecessary file renaming and URL construction errors in Web Workers. - Web Connectivity CORS Guard - Guarded internet checker execution on web platforms to prevent browser CORS violations on captive portal endpoints.
0.4.4 #
Improvements & Bug Fixes #
- Stream Error Propagation & Lifecycle Safety - Resolved an issue where initial fetch failures in
watchRecords/watchRecordsStatevanished silently rather than surfacing as stream errors. Closed race-condition subscription leak windows during in-flight cancellations, and addedonPause/onResumesupport to underlying Drift queries. Special thanks to @qtpi-bonding-org for identifying and contributing the initial fix for these lifecycle issues. - Enhanced
QueryState<T>Error Handling - AddederrorandhasErrorproperties toQueryState<T>so offline-first Flutter UIs (such asStreamBuilderand RiverpodStreamProvider) can retain and inspect network error states alongside cached data without errors being wiped out by subsequent data events. - Deep Relation Equality in
distinctResults- Expanded relations (data['expand']) are now checked withindistinctResultsso changes to related records properly trigger stream emissions. - Analysis & Try-Catch Await - Resolved an analyzer warning in
getFirstListItemOrNullby awaiting the future inside thetryblock.
Dependencies #
- Removed
sqlite3_flutter_libs- Removed the obsoletesqlite3_flutter_libspackage (now end-of-life and unnecessary withsqlite33.x). - Updated Dependencies - Updated
pocketbaseto^0.25.0,driftto^2.34.3,sqlite3to^3.5.2,internet_connection_checker_plusto^3.1.1, andpath_providerto^2.1.6.
0.4.3 #
Dependencies #
- Updated package dependencies - Updated core dependencies including
pocketbase,sqlite3, andinternet_connection_checker_plusto latest versions.
0.4.2 #
Dependencies #
- Connectivity - Replaced
connectivity_pluswithinternet_connection_checker_plusto verify actual internet access rather than just network interface availability. - Updated package dependencies - Bumped
driftanddrift_devto^2.33.0,sqlite3to^3.3.1, andshared_preferencesto^2.5.5.
Improvements #
- Enhanced
ConnectivityService- Now relies oninternet_connection_checker_plusfor verifying real internet access. It maintains an optimistic "online-by-default" approach that defaults totrueto prevent UI flickering, and falls back rapidly when an actual network request fails.
0.4.1 #
New Features #
- Network Fetching State for Streams - Introduced
QueryState<T>wrapper and two new methodswatchRecordsStateandwatchRecordState. This allows offline-first apps to distinguish between an "empty cache while fetching data" and a "truly empty database result", enabling them to show loading indicators during initial network requests instead of instantly flashing empty states.
Improvements #
- Stream Performance Optimization (Distinct Results) - Added a
distinctResultsparameter (enabled by default) towatchRecord,watchRecords,watchRecordState, andwatchRecordsState(#13). This prevents huge, unnecessary Riverpod/Stream rebuild fan-outs by natively suppressing stream emissions when a SQLite cache update does not actually change the underlying query's effective result set (validated elegantly usingid+updatedfields).
Bug Fixes #
- Local Modification Timestamps - Fixed an issue where offline mutations via
$updatefailed to automatically bump the localupdatedproperty. Local modifications now cleanly generate a new UTC timestamp if one isn't explicitly provided, ensuring cache invalidations and UI streams work smoothly. - Strict UTC Standardization - Fixed and standardized all internally generated timestamps (cache TTLs, expiration dates, schema insertions, local creations, and file caches) to strictly use
DateTime.now().toUtc(). This ensures absolute compatibility with PocketBase's ISO8601 UTC server expectations and prevents time-zone drift synchronization anomalies.
0.4.0 #
BREAKING CHANGES: Method names have been updated for consistency and clarity.
Breaking Changes #
- Renamed
cacheSchematosetSchema: The method used to provide the JSON database schema to the client has been renamed. Changeclient.cacheSchema(schema)toclient.setSchema(schema). - Renamed
getFileDatatogetFileBytes: The method in thefilesservice to retrieve file bytes directly has been renamed. Changeclient.files.getFileData(...)toclient.files.getFileBytes(...).
0.3.17 #
Dependencies #
- Updated package dependencies - Updated core dependencies including
drift,sqlite3, and others.
0.3.16 #
Bug Fixes #
- Fixed realtime subscriptions interfering with each other (#12) - Resolved an issue where multiple
watchRecordsorwatchRecordstreams subscribed to the same collection would break each other's realtime updates. When one stream was cancelled (e.g., navigating away from a tab), it calledunsubscribe('*')which removed all listeners for that topic — including those belonging to other active streams. The fix stores the targetedUnsubscribeFuncreturned bysubscribe()and calls that on cancel instead, ensuring only the specific listener is removed while leaving other subscriptions intact.
0.3.15 #
New Features #
- Configurable Sync Retry Count - Added
localSyncRetryCountparameter to$PocketBaseand$PocketBase.database(default: 5). This allows developers to control how many times a record update/create should be retried before being considered permanently failed.
Bug Fixes #
- Robust Handling of Stuck Records (#10) - Resolved an issue where records suffering from validation errors (including "orphaned" records referencing deleted parents) would retry indefinitely, causing battery drain and blocking the sync queue.
- All
400 Bad Requesterrors (validation failures, orphans, etc.) now increment a retry counter on the local record. - Once the
localSyncRetryCountis exceeded, the local record is automatically deleted to unblock the sync queue. - Useful for cleaning up local state that has become invalid relative to the server (e.g., a local comment on a post that was deleted on the server).
- All
0.3.14 #
Bug Fixes #
-
Fixed
clearAllData()also clearing the schema cache - TheclearAllData()method was incorrectly deleting all records from theservicestable, including the cached collection schema (stored withservice = 'schema'). This caused issues when the app tried to use schema-dependent features after clearing user data. The method now preserves schema records while clearing all other data. -
Improved connectivity restoration detection - The
connectivity_pluspackage doesn't reliably detect when network connectivity is restored, especially on iOS Simulator. The connectivity service now uses a hybrid approach:- Platform detection: Uses
connectivity_plusfor detecting when the device goes offline - Request-based detection: Network request success/failure is now reported to the connectivity service
- When a network request succeeds after being marked offline, connectivity is immediately restored
- After 2 consecutive network failures, the device is marked as offline
- Explicit
checkConnectivity()calls (e.g., on app resume) trust the platform status directly
- Platform detection: Uses
Improvements #
- Added
shouldAttemptNetworkgetter toConnectivityService- Returnstrueif the platform reports a network interface is available, regardless of recent request success. Use this for deciding whether to TRY a network request. UseisConnectedfor UI display or pessimistic decisions. - Added
reportNetworkSuccess()andreportNetworkFailure()methods toConnectivityServicefor request-based connectivity tracking - All network request decisions now use
shouldAttemptNetworkinstead ofisConnected, allowing the app to discover connectivity restoration by actually trying requests when the platform reports a network interface is available
0.3.13 #
Bug Fixes #
-
Fixed file field names being null when saving offline - Resolved a critical issue where creating or updating a record with files while offline (using
cacheAndNetworkpolicy) would result in file field names being saved asnullor empty in the local database. The_prepareCacheOnlyBody()method is now called before network attempts, ensuring file field names and blobs are properly cached regardless of network availability. -
Fixed files not syncing to server on connectivity restore - Files attached to records created offline were not being uploaded when connectivity was restored and
retryLocal()ran. The sync mechanism now:- Retrieves cached file blobs from the local database
- Identifies file fields from the collection schema
- Includes files as
MultipartFileattachments when syncing pending creates/updates to the server - Added
getFilesForRecord()method to the database for retrieving all cached files for a record
-
Improved file retrieval fallback behavior - File retrieval now follows the "stale data is better than no data" principle for offline-first apps:
cacheOnlywith expired cache: Now returns the expired cached file instead of throwingcacheAndNetworkwith expired cache + network failure: Now falls back to returning the expired cached file instead of throwing a network errorcacheOnlywith no cache: Now throws a descriptive error message- Added informative logging for cache expiration and fallback scenarios
Tests #
- Added 3 new tests for offline file creation with network failure simulation
- Added 1 test for file sync on connectivity restore
- Added 3 tests for file retrieval fallback behavior
0.3.12 #
New Features #
- Global Request Policy - Added a
requestPolicyparameter to the$PocketBaseclient that sets a default policy for all service methods. This allows you to configure the offline-first behavior globally instead of passing it to every method call.- Defaults to
RequestPolicy.cacheAndNetwork - Can be overridden per-call by passing an explicit
requestPolicyto any method - All method
requestPolicyparameters are now nullable (omit to use the global default)
- Defaults to
Bug Fixes #
- Fixed duplicate record creation with realtime subscriptions - Resolved a critical issue where creating a record with
RequestPolicy.cacheAndNetworkwhile usingwatchRecords()or other realtime subscriptions could result in duplicate records being created locally. This occurred when PocketBase ignored/replaced the client-provided ID (e.g., due to custom hooks) and generated its own ID. The code now detects ID mismatches between the sent and received ID, and uses the server's ID to prevent duplicates.- Added ID mismatch detection in
_createCacheAndNetwork - Added ID mismatch handling in
_tryCreateOnServerfor background sync - A warning is now logged when the server doesn't use the provided ID
- Added ID mismatch detection in
0.3.11 #
Bug Fixes #
- Fixed single-select validation and expansion - Resolved an issue where
select,file, andrelationfields were incorrectly treated as multi-select (lists) when theirmaxSelectschema option was set to0ornull. Per PocketBase documentation,maxSelect <= 1(including 0/null) indicates a single value (String), whilemaxSelect >= 2indicates multiple values (List). This fix ensures that fields with default or explicit 0/1maxSelectare correctly validated and returned as single strings, preventing "Field must be a list" errors.
Dependencies #
- Updated
pocketbaseto v0.23.1 - Includes a fix for the "all-in-one" OAuth2 flow that prevented successful re-authentication after a failed attempt.
0.3.10 #
New Features #
-
Enhanced Filter Parser - Major improvements to the filter parser for better PocketBase compatibility and new operators:
Literal Type Support:
nullliteral -field = nullnow correctly translates toIS NULLin SQLite- Boolean literals -
field = trueandfield = falseare now supported - Numeric literals - Unquoted numbers like
score > 100orprice = 19.99are handled correctly
"Any-of" Operators for Multi-Value Fields:
?=,?!=- Any element equals/not equals?>,?>=,?<,?<=- Any element comparison?~,?!~- Any element LIKE/NOT LIKE- Example:
tags ?= "flutter"checks if any tag equals "flutter"
DateTime Macros:
@now- Current UTC datetime@todayStart,@todayEnd- Day boundaries@yesterday,@tomorrow- Relative dates@monthStart,@monthEnd,@yearStart,@yearEnd- Period boundaries@second,@minute,@hour,@day,@weekday,@month,@year- Time components- Example:
created >= @todayStart && created <= @todayEnd
Field Modifiers:
:lower- Case-insensitive comparison usingLOWER():length- Array length check usingjson_array_length()- Example:
name:lower = "john"ortags:length > 2
Comment Stripping:
- Single-line comments (
// comment) are now stripped before parsing
Bug Fixes #
- Fixed date format mismatch causing incorrect sorting - Resolved an issue where records stored via
setLocalandmergeLocal(used by realtime subscriptions and batch syncs) would have theircreated/updatedtimestamps re-formatted, replacing the ISO 8601Tseparator with a space. This caused string-based sorting to produce incorrect results when mixed with records stored via$createwhich preserved the original format. All methods now preserve the original timestamp format from the server. (#8)
Improvements #
- Comprehensive test suite - Added 49 new tests covering all filter parser features
0.3.9 #
New Features #
- Batch Requests with Offline Support - Added
$createBatch()method for executing transactional batch operations with full offline-first support.- Queue multiple create, update, delete, and upsert operations across collections
- Execute all operations in a single HTTP request to PocketBase
- Full
RequestPolicysupport for controlling online/offline behavior - When offline or on failure with
cacheAndNetwork, operations are stored locally and retried as individual operations when connectivity is restored
0.3.8 #
New Features #
- Nullable Cache TTL - The
cacheTtlparameter in$PocketBase.database()is now nullable. Setting it tonulldisables automatic cache cleanup, allowing data to be persisted indefinitely. - Configurable Logout Cleanup - Added
clearOnLogoutparameter to$AuthStore(default:true). Developers can now set this tofalseto preserve local data when the user logs out, improving the experience for offline-first applications where re-fetching data might be expensive.
Bug Fixes #
- Fixed Connectivity & Cache Issues on Re-login - Resolved a critical issue where logging out and back in would cause connectivity to report incorrectly and cached streams to stop responding.
ConnectivityServiceis now a true singleton that is never disposed, matching the behavior ofconnectivity_plus's globalConnectivity()singleton. This prevents instability when recreating client instances.- Simplified database connection by removing manual isolate spawning, using Drift's built-in connection handling instead.
- Note for developers: If using Riverpod, invalidate your
pocketBaseProviderafter logout to ensure all dependent providers are refreshed with the new client instance.
0.3.7 #
Bug Fixes #
- Fixed nested expanded records not being cached - When fetching records with nested expand, the related records are now automatically cached to their respective collection tables. This enables local expand to work correctly when those collections are later queried independently.
- Previously,
mergeLocal(used bygetFullList) bypassed the caching logic, causing expanded records to be missing from the local cache. - The fix also applies recursively, so deeply nested expands are properly cached at all levels.
- Previously,
Tests #
- Added
expand_cache_test.dartwith 4 tests validating nested expanded record caching behavior.
0.3.6 #
BREAKING CHANGE: Local relation expansion now matches PocketBase SDK behavior.
Breaking Changes #
- Single relations now return objects directly - Previously, all expanded relations were wrapped in a list, requiring index access (e.g.,
expand.author.0.name). Now, single relations (maxSelect == 1) return the object directly, matching the official PocketBase SDK behavior:- Before:
record.get<String>('expand.author.0.name') - After:
record.get<String>('expand.author.name') - Multi relations (
maxSelect > 1) still return lists and require index access
- Before:
Migration Guide #
If you were accessing single relation expands with .0 index, remove the index:
// Before (0.3.5 and earlier)
final authorName = post.get<String>('expand.author.0.name');
final author = post.get<RecordModel>('expand.author.0');
// After (0.3.6+)
final authorName = post.get<String>('expand.author.name');
final author = post.get<RecordModel>('expand.author');
Multi-relation access remains unchanged:
final firstTag = post.get<String>('expand.tags.0.name'); // Still uses index
0.3.5 #
Documentation #
-
Expanding Relations Guide - Added comprehensive documentation for accessing expanded relations using the PocketBase SDK's
record.get<T>()dot-notation path syntax (e.g.,record.get<String>('expand.author.0.name')). Covers basic expansion, nested (multi-level) expansion, and multi-relation access patterns. -
Advanced: Direct Database Access - Added new documentation section explaining how to access the underlying Drift database directly via
client.dbfor advanced use cases:- Database schema overview (tables, columns, primary keys)
- Running raw SQL queries with
customSelect - Joining data across collections
- Reactive streams with raw SQL using
.watch() - Executing raw statements with
customStatement - Using the built-in
$querymethod
Tests #
- Added
expand_dot_notation_test.dartwith 8 comprehensive tests validating that locally expanded relations work correctly with the PocketBase SDK's dot-notation path access API.
0.3.4 #
New Features #
- Cache TTL & Expiration - Added configurable time-to-live (TTL) for cached data. Old synced records and cached responses are automatically cleaned up when
runMaintenance()is called.- Configurable
cacheTtlparameter in$PocketBase.database()(default: 60 days) - New
runMaintenance()method to clean up expired cache data - Returns
MaintenanceResultwith counts of deleted items - Only removes synced data - unsynced local changes are always preserved
- Cleans up expired file blobs automatically
- Configurable
0.3.3 #
New Features #
- Filter-aware sync for deleted records - Added
syncLocalmethod that intelligently syncs deletions from the server to local cache. When fetching data withgetFullList, the system now detects and removes records that were deleted on the server while offline, even when using filtered queries. This ensures local cache stays in sync with server state.- Works with both filtered and unfiltered queries
- Only deletes records within the filter scope
- Preserves unsynced local changes, local-only records, and pending deletions
- Includes safety check to prevent mass deletion on server errors
Improvements #
- Enhanced
getFullListbehavior - Now automatically callssyncLocalafter fetching all pages, ensuring deleted records are cleaned up from local cache - Added comprehensive test coverage - New test suite (
sync_local_test.dart) with 7 tests covering all edge cases for filter-aware deletion - PocketBase-compatible ID generation - Local IDs are now generated using PocketBase's exact format (
[a-z0-9]{15}). This eliminates the need for ID remapping during sync, simplifying the offline-first flow:- Records created offline now sync with the same ID they were created with
- Removed
shortiddependency in favor of a built-in secure random generator - Exported
newId()function for use by consuming applications
Bug Fixes #
- Fixed partial update validation for offline scenarios - Partial updates now correctly merge with existing record data before validation, allowing updates with only changed fields when using
cacheFirstorcacheOnlypolicies - Fixed SQLite quote semantics in filters - Added automatic quote normalization in filter parser to convert double quotes to single quotes for string literals, preventing SQLite from misinterpreting values as identifiers. Both
'id = "$id"'and"id = '$id'"now produce correct SQL
0.3.2 #
Bug Fixes #
- Critical: Fixed pending sync execution on app restart - Resolved a critical issue where pending changes would not sync when the app was completely closed and reopened. The sync mechanism now queries the database to identify services with pending records instead of relying on an in-memory cache that gets cleared on app restart. This ensures that all offline changes are reliably synced when connectivity is restored, even after a complete app restart.
0.3.1 #
- Resolved pub.dev issue
0.3.0 #
BREAKING CHANGES: Added new RequestPolicy options for more explicit control over caching and network behavior.
New Features #
- New RequestPolicy options: Added
RequestPolicy.cacheFirstandRequestPolicy.networkFirstfor more explicit control over data fetching and synchronization behaviorcacheFirst: Returns cache immediately, fetches network in background to update cachenetworkFirst: Tries network first, falls back to cache on failure (replaces oldcacheAndNetworkbehavior for reads)cacheAndNetwork: Now has distinct behavior - for reads it behaves likenetworkFirst, but for writes it provides resilient offline-first synchronization with automatic retry
Improvements #
- Refactored write operations (create/update/delete): Split monolithic methods into smaller, policy-specific implementations for better maintainability
- Enhanced documentation: Comprehensive guide on choosing the right
RequestPolicyfor different scenarios - Better error messages: More descriptive error messages that indicate which policy was used when operations fail
Write Operation Behavior Changes #
networkFirst(new strict mode): Writes to server first, updates cache on success, throws error on failure (NO cache fallback)cacheFirst(new optimistic mode): Writes to cache first, attempts server sync in backgroundcacheAndNetwork(enhanced): Tries server first, falls back to cache with pending sync on failure (maintains backward compatibility for offline-first apps)
Migration Guide #
Existing code continues to work - the default RequestPolicy.cacheAndNetwork maintains backward compatibility for most use cases.
However, if you were relying on specific behavior:
- If you want strict server-first writes with no offline fallback, use
RequestPolicy.networkFirst - If you want instant UI feedback with background sync, use
RequestPolicy.cacheFirst - If you want resilient offline-first behavior (old default), continue using
RequestPolicy.cacheAndNetwork
0.2.1 #
Exclude certain API paths from caching in send method
- Add a list of non-cacheable path segments to exclude specific API endpoints from being cached.
- Modify the send method to bypass cache if the request path contains any of these segments.
This prevents caching sensitive or system data like backups, settings, and logs.
0.2.0 #
Refactor file download methods to use consistent getFileData & Implement auto-generation of file token
- Change token parameter from bool to String?
- Add autoGenerateToken parameter to automatically generate tokens when needed
- Refactor file download methods to use consistent getFileData API across platforms
- Improve file service architecture to better handle token-based file downloads
0.1.2 #
- Update Documentation.
0.1.1 #
- Resolve pub.dev score issues e.g outdated plugin dependencies.
0.1.0 #
- Initial release.
This is the first version of pocketbase_drift, a powerful offline-first client for the PocketBase backend, built on top of the reactive persistence library Drift.
Features #
- Offline-First Architecture: Read, create, update, and delete records even without a network connection. The client seamlessly uses the local database as the source of truth.
- Automatic Synchronization: Changes made while offline are automatically queued and retried when network connectivity is restored.
- Reactive Data & UI: Build reactive user interfaces with streams that automatically update when underlying data changes, whether from a server push or a local mutation.
- Local Caching with Drift: All collections and records are cached in a local SQLite database, providing fast, offline access to your data.
- Powerful Local Querying: Full support for local querying, mirroring the PocketBase API. This includes:
- Filtering: Complex
filterstrings are parsed into SQLiteWHEREclauses. - Sorting: Sort results by any field with
sort(e.g.,-created,name). - Field Selection: Limit the returned fields with
fieldsfor improved performance. - Pagination:
limitandoffsetare fully supported for local data.
- Filtering: Complex
- Relation Expansion: Support for expanding single and multi-level relations (e.g.,
post.author) directly from the local cache. - Full-Text Search: Integrated Full-Text Search (FTS5) for performing fast, local searches across all your cached record data.
- Authentication Persistence: User and admin authentication state is persisted locally using
shared_preferences, keeping users logged in across app sessions. - Cross-Platform Support: Works across all Flutter-supported platforms, including mobile (iOS, Android), web, and desktop (macOS, Windows, Linux).
- Basic File & Image Caching: Includes a
PocketBaseImageProviderthat caches images in the local database for offline display. - Custom API Route Caching: Added support for offline caching of custom API routes accessed via the
sendmethod. This allowsGETrequests to custom endpoints to be cached and available offline, improving performance and reliability for custom integrations. - Robust & Performant: Includes optimizations for batching queries and file streaming on all platforms to handle large files efficiently.
Improvements #
- Improved maintainability by refactoring the large
createandupdatemethods in the internalServiceMixininto smaller, more manageable helper methods. - Improved web performance by switching from a full
http.get()to a streaming download for file fetching, aligning it with the more memory-efficient native implementation.