Skip to content

Releases: nesfeder/supabase-offline-kt

Release list

0.1.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 21:58
Immutable release. Only release title and notes can be modified.
7420995

The first release.

Breaking

  • The library is a supabase-kt plugin. Install it with install(Offline) { … } in
    createSupabaseClient and reach it as supabase.offline, where createSupabaseOfflineClient(…)
    returned a client; the settings of SupabaseOfflineConfig are the plugin's Offline.Config
    (offlineStoragePath is dataDirectory, a String). The old names are gone; see Removed.
    supabase-kt closes the plugin with the client, once it has begun closing its HTTP client: a call
    made in between, or one whose request had not gone out, is still sent (Ktor can send while its
    client finishes closing), or ends as it would offline (a read the cache can answer is answered
    from it, a write the mutation queue holds is Queued) or with the closed error, never with a
    cancellation; a request already on the wire still answers. Call supabase.offline.close() first
    to let the calls not yet sent go out.
  • A flow being collected when the client closes (selectAsFlow, pendingMutations,
    pendingMutationCountFlow, rejectedMutations) completes, where it failed with the client's
    closed error, so a screen torn down at logout sees its flow end, not an error. A flow collected
    after close() still fails with the closed error, as every other call does.
  • Logging goes through supabase-kt's own: the plugin logs to the client's logging processor
    (defaultLoggingFactory) with the tag Supabase-Offline, at Offline.Config.logLevel or the
    client's defaultLogLevel. OfflineLogger, OfflineLogLevel and the logger and minLogLevel
    settings are gone: configure logging once, in createSupabaseClient.
  • MutationOutcome.Queued is a class that carries the queued write as a PendingMutation: match it
    with is MutationOutcome.Queued, not MutationOutcome.Queued ->. MutationOutcome.Applied and
    Queued have internal constructors.
  • OfflinePostgrestQueryBuilder is a final class, as supabase-kt's builder is, and Offline and
    OfflineBucketApi need the OfflineInternal opt-in to implement. A test double comes from
    supabase-offline-kt-testing.
  • RefreshState.InFlight is RefreshState.Refreshing, and RefreshState.Paused is new: an
    exhaustive when over RefreshState needs a branch for it.
  • ReadStrategy.NetworkFirst, CacheFirst and CacheOnly take maxAge, not cacheDuration, and
    are no longer data classes. FlowReadStrategy and MutationStrategy are sealed interfaces: their
    cases are written as before, but entries, values() and valueOf are gone.
  • A CacheOnly read that finds nothing it may serve throws CacheMissException, not
    NoSuchElementException. A QueueIfOffline write the queue cannot hold (a dry run, or one whose
    request could not be inspected to tell; a filter it cannot record; an unknown user; another
    schema; a write on its way into the queue while local data was cleared) that cannot be sent for
    want of a connection throws MutationNotQueueableException, with that failure as its cause,
    instead of the failure itself or an IOException. isNetworkOrConnectivityFailure() is still
    true for it.
  • The testing hook's opt-in marker InternalOfflineTestingApi is OfflineInternal.
  • syncPendingMutations(table: String? = null) returns a SyncReport (what it sent, what the
    server refused, what is still queued) in place of a count, and replaces
    syncAllPendingMutations(). It no longer throws a failure that stopped the pass: the report says
    what is still queued and syncStatus.value.lastSyncError why. When the library is sending the
    same writes already, it waits for that and then sends what is left.
  • pendingMutationCount and pendingMutationCountFlow each take one table: String? = null in
    place of two overloads: calls compile as before, compiled callers must be rebuilt. They, and the
    new queue and rejected-list members, show the current user's writes only, and none while the
    stored session cannot be read (they showed every user's then).
  • selectAsFlow emits a QuerySnapshot in place of OfflineReadResult, with isFromCache,
    fetchedAt and hasPendingMutations beside result and refresh; it is no longer constructed
    by an app.
  • autoSync is on by default and does what it says: the library sends queued writes by itself
    when the session settles into a user with writes queued, and when the network comes back. Set
    autoSync = false to leave those passes to the app; a write queued behind an earlier one is
    sent by itself either way.
  • On Android the library's manifest declares android.permission.ACCESS_NETWORK_STATE, for its
    connectivity monitor: it appears in the merged manifest of every app that uses the library.
  • A test builds its client as an app does, with install(Offline) { inMemory(clock = …) } from
    supabase-offline-kt-testing; buildInMemorySupabaseOfflineClient is gone (see Removed). The
    client no longer runs in the test's own scope: its background work runs on the library's, and a
    failure there is logged, never thrown into the test.
  • ReadStrategy.NetworkFirst, CacheFirst and CacheOnly take a Duration only: the
    constructors taking milliseconds are gone, and NetworkFirst(null) now compiles.
  • The Storage file helpers extend OfflineBucketApi, the bucket offline.storage.from(…) returns,
    in place of supabase-kt's BucketApi: on Android ensureLocalFile returns a File and
    ensureLocalUri a Uri, on iOS ensureLocalPath a String, none of them nullable any more, and
    each takes path as the bucket's own members do. localUriIfCached and localPathIfCached are
    null only when nothing is cached. On supabase-kt's own bucket they no longer compile, where
    they returned null.
  • A read, an update or a delete with two top-level filters on one column, such as
    filter { gte("age", 1); lte("age", 10) }, throws IllegalArgumentException before anything is
    sent or queued. supabase-kt sends only the first of them, so the read showed rows outside the
    window and the write reached them, and a queued write replayed that way later. Write the two as
    one and { } group, which sends both.
  • A read, an update or a delete whose pattern list (likeAll, likeAny, ilikeAll, ilikeAny)
    is empty, or holds a pattern that is empty, NULL, edged with white space or holding a brace
    (and none of ,.:()), throws IllegalArgumentException before anything is sent or queued, at
    the top level or in a group. supabase-kt sends such a pattern unquoted, so the server read another
    list: likeAll("code", listOf("")) went out as like(all).{}, which matches every row.
  • On iOS the database and the file cache live in Library/Application Support by default
    (supabase_offline.db and supabase_offline/), not in Documents, which an app with file
    sharing on shows in the Files app. A client that finds a database in Documents moves it, with
    its unsent writes and its cached files, when it opens; while another client holds it, it stays
    there and that client's claim fails the open, and when both places hold a database the one in
    Documents is left and a warning logged.
  • The iOS framework carries no Swift-only helpers and no SKIE-generated Swift (see Removed). The
    Swift package reaches it through a bridge of its own.
  • On Android, an interface member with a body compiles to a JVM default method only, so the
    $DefaultImpls classes are gone (-jvm-default=no-compatibility).
  • SyncStatus.lastSyncError is a SyncFailedException?, which is all it ever held: read its
    httpStatus and code without a cast. The testing artifact's syncStatus(lastSyncError = …)
    takes one.
  • syncPendingMutations(table) no longer sends a table's writes ahead of an earlier write of the
    same user to another table: it stops at the first write with an earlier one still queued
    elsewhere, and SyncReport.heldBehindOtherTablesCount counts what it held back. Sync every table
    to send them in order.
  • On Swift, the requirement of OfflineFilterValue is offlineFilterText, where it was
    filterText, a name that showed in completion on String, Int, Date, UUID and every other
    standard type that conforms, and could collide with one an app declares. A type of your own that
    conforms declares var offlineFilterText: String.

Added

  • Built and tested against supabase-kt 3.6 (supabase-bom in the version catalog), with Ktor 3.4.3
    and kotlinx.coroutines 1.10.2, the versions it was released with. Not 3.8: supabase-kt 3.7.0 and
    3.8.0 carry a Realtime crash and a protocol-V2 leave defect, fixed upstream but not yet released,
    and this library's pin sets an app's Realtime too; it moves to the first release with both fixes.
    On 3.6 there is no dryRun(), maybeSingle(), isdistinct filter or purgeCache (write a dry
    run by adding Prefer: tx=rollback to the request), and a Storage object name with %, ? or
    # is downloaded as supabase-kt downloads it but never cached, since supabase-kt 3.6 puts it in
    the URL unencoded and can fetch a different object (see the compatibility page). Each minor
    release of this library names exactly one supabase-kt minor: the artifact compiles supabase-kt's
    inline functions into itself and implements its BucketApi, which gains members in minor
    releases, so a range cannot be promised. When an app resolves another supabase-kt minor, the
    plugin logs one warning when it is installed, naming both versions, and carries on; a patch
    difference is no warning.
  • The Offline plugin for supabase-kt: install(Offline) { … } and supabase.offline, with its
    settings in Offline.Config: the two cache sizes, dataDirectory, primaryKeys (each table's
    key, id unless set; primaryKey(table, columns)), responseTrust (ResponseTrust, with the
    earlier rule as AuthenticatedSessionOnly), logLevel, networkAvailability, the app's own
    connectivity signal in place of the library's...
Read more