Releases: koolbase/koolbase-flutter
Releases · koolbase/koolbase-flutter
Release list
v11.3.0
KoolbaseError: one canonical error type across the whole SDK Catch anything, call KoolbaseError.from(e), branch on code. The 44 typed exceptions are unchanged; this sits above them so an app can branch on what a failure MEANS without knowing the SDK's internal taxonomy. Found by measuring: across 14,732 lines, four places in the entire SDK catch a transport failure. A database read that loses connection has been throwing a raw SocketException straight through to the caller — on a phone with patchy signal, the most common failure there is, and the one with no typed home. Normalization is total: an unmapped server code, a bare string, an integer all become a KoolbaseError rather than escaping. This runs in error paths, where a second failure has nowhere to go. Eleven codes. The bar for adding one is that an application could reasonably do something DIFFERENT because of it — rateLimited because waiting is not retrying, contactNotVerified because it routes to "resend verification" not "check your password". retryable is derived from code, never stored, so the two cannot disagree. details carries structured context through rather than flattening it, including the server's current record on a revision mismatch. message is developer diagnostic data and must never reach an end user. Written into the doc comment, not just the changelog. Also fixes the README's first auth example, which called a method that does not exist (Koolbase.auth.register). signUp appeared nowhere in 1382 lines, so the front page had no correct path to creating a user — including no mention of SignUpResult.verificationRequired, where the account exists but no session was issued. Additive only. Nothing throws differently, no signature moved.
v11.2.0
chore: update koolbase_flutter dependency to version 11.2.0
v11.1.1
fix: offline writes refuse signed-out — never enqueue under a null owner A ticket parked on-device while the SDK session was dead was enqueued with userId null. Every per-user surface then did its job correctly — pendingWrites() throws signed-out, reads and replay filter by owner — so the null-owner row matched nothing, forever: physically present, invisible, unreplayable. The write neither failed nor succeeded; it vanished into a bucket nobody owns. All three offline enqueue sites (insert fallback, queued update, queued delete) now throw KoolbaseUnauthenticatedException signed-out. Signed-in behavior byte-for-byte unchanged. Mutation-pinned: removing the insert guard fails 'writes to NO bucket'. Mirrors the RN SDK's Aug 4 fix of the same bug family. v11.1.1
v11.1.0
feat: get(fresh: true) — cache-skipping network read SWR cannot express read-after-write: a cached answer is the state before your write, and awaiting the background-refresh stream proved racy on-device (a local projection tracked the server exactly one sale behind). get(fresh: true) skips the cache and returns the network's answer; the read still updates the cache, so SWR callers benefit. Plain get() unchanged. Fake queries in tests updated to the new signature. v11.1.0
v11.0.0
feat(auth)!: SignUpResult + ContactNotVerifiedException (11.0.0) BREAKING: signUp returns SignUpResult, not KoolbaseUser. The server can now withhold a session at registration when a project requires a verified contact channel. The old return type could not express 'account created, not signed in' — the session-less 201 reached AuthSession.fromJson and threw 'type Null is not a subtype of type String' on the absent access_token. Observed on device before this fix. SignUpResult carries the user in both cases and a verificationRequired flag, so consumers must branch. Deliberately not a nullable user or nullable session on the public surface: that would push the same null-check onto every caller and reproduce the crash one layer up. The session is persisted only when one was issued. Calling _setSession with null would leave the SDK half-authenticated — reporting a signed-in user with no token. signUp no longer routes through _parseSession. That parser assumes tokens exist, which is correct for login and refresh and wrong for registration under this policy; loosening it would have weakened login's contract for no reason. ContactNotVerifiedException maps the server's contact_not_verified code on login. Credentials were correct and the policy refused, so it is distinct from InvalidCredentialsException — apps route to resend-verification rather than telling the user to re-check a password that was right. Added only to _checkError, not to the Apple/Google parsers: OAuth satisfies the requirement by definition (a provider attestation IS a verified contact), so the server never emits this code on those paths. Enforcement is off for every project that existed before the server-side release, so verificationRequired is false and behaviour is unchanged unless a customer opts in.
v10.5.0
feat: KoolbaseCollectionGrid (10.5.0) The same collection laid out as a grid. Deliberately thin: it shares KoolbaseCollectionController with KoolbaseCollectionList, so stale-while-revalidate, pull-to-refresh and the loading/empty/error slots behave identically — the two differ only in layout, and sharing the controller is what keeps them from drifting on the hard parts. Fixed crossAxisCount rather than responsive reflow: a caller who needs reflow can vary it from a LayoutBuilder, and building it in would have meant guessing at tile sizing for everyone else. Tests are narrow on purpose — the controller's properties are already covered by the list's tests, so re-testing them here would test the same code twice. The query fake moves to test/support so two copies can't drift. README's 'for grids, drive the controller directly' is no longer true and now documents the widget instead.
v10.4.0
10.4.0: streams fetch on listen, and writes refresh them Two gaps that both surfaced as "the UI just does not update" — nothing errored, nothing logged, the data was simply absent. Both were found by building an app against the SDK rather than by reading it. .stream was a bare relay off a broadcast controller: get() performed the fetch and pushed refreshes into it, so a stream-only listener waited forever on a collection nothing else had read. It now fetches on first listen, cache-first exactly as get() is. Every write invalidated the collection cache and stopped, which only affects the NEXT query. A listener already watching sat unchanged until something happened to re-fetch — a message sent into a chat thread did not appear in that thread. insert, upsert, deleteWhere, batch, and conflict resolution now refresh open queries on the collection. Each query re-runs ITSELF, so a stream only receives records matching its own filters. The refresh is a registered closure per stream rather than one rebuilt from the stream key: a key carries collection, filters, and user but not ordering, limit, or populated fields, so a reconstructed query would push the wrong records into a stream that never asked. Mutation-verified: dropping the collection filter refreshes every query in the process, failing three tests. README gains a Live queries section — .stream was undocumented — and its where() example is corrected to the real named-argument signature.
v10.2.0
database: insert-conflicts are real, and resolvable (10.2.0) The Flutter twin of RN 94d8a21, closing the cross-SDK batch. Unique constraints made insert-conflicts a genuine third kind: a queued insert refused as a duplicate is held like any terminal refusal — but the client coerced its operation to update (ConflictOperation admitted only two members), and resolving one issued a PATCH against a record id that exists nowhere. Storage never lied; only the mapper did. ConflictOperation gains insert. Resolving a rejected insert IS the insert, retried: resolveWithMerge carries amended data (the fix-the-colliding-title path), unconditional — no record, no revision to be conditional against — with the conflict's id as the idempotency key, so a resolution whose response is lost returns the original on retry rather than duplicating. Wire-proven on the exact route. resolveWithServer means the colliding row stands: clears with zero requests, asserted by request count. Seeded through the production path (enqueue → moveToRejected). Mutation- verified: deleting the insert branch resurfaces the pre-fix wrong-verb PATCH, caught by name by a scripted client that refuses unknown routes loudly. README documents the insert-conflict resolution semantics in the same change.
v10.1.2
database: a refused resolution teaches the stored conflict (10.1.2) The Flutter twin of RN fb4480d, live in a published SDK until now. Resolution was conditional on the revision the ORIGINAL refusal reported; when the record moved again mid-decision, the 409 — carrying current_revision and the record — was correctly refused and entirely discarded by _resolveWrite. Every retry replayed the stale condition; abandon was the only exit. Device-proven on RN (three identical refusals against an unchanged server). Flutter's factory already parsed the 409 fully into KoolbaseRevisionMismatchException — the information died one method later. The fix is one catch: refreshConflict absorbs currentRevision and currentRecord into the Drift row (honoring serverState's own documented contract — 'as returned with the refusal, so resolving does not need a fetch' — which the CREATING refusal honored and the resolution refusal violated), then rethrows with review-and-retry. Proven by the revision sequence [8, 9]: the second attempt is conditional against the LEARNED revision and succeeds without the server moving again. Seeded through the production path (enqueue → moveToConflict). Mutation- verified: removing the refreshConflict call fails the storage assertion. The resolution HTTP path gained an injectable client (httpClient param) — an unmockable resolution path is why no test ever caught this.
v10.1.1
database: signed out is a refusal, not an empty list (10.1.1)
The Flutter half of tonight's device finding. A signed-out pendingWrites()
filtered on a null identity — silently empty, indistinguishable from 'all
synced', and worse: null == null matches legacy no-owner writes, so a
signed-out display could show someone else's rows. The RN session watched the
RN variant of this lie for an evening ('pending: 0', restart-proven, two
writes unsent).
Both methods now throw KoolbaseUnauthenticatedException without a user; the
stream emits it as an error event per emission, so a sign-out under a live
badge becomes an error the moment the session dies. The prior test expecting
isEmpty is updated to the new contract with a note explaining why it changed.
Mutation-verified.
RN shipped the same contract tonight (signed-out refusal + enqueue guard).
Both SDKs now refuse to fake a zero.