koolbase_flutter
Flutter SDK for Koolbase — feature flags, remote config, version enforcement, authentication, storage, database, realtime, OTA updates, and code push for mobile apps.
11.2.1
2.5K downloads/mo
#4965 most downloaded on pub.dev
kennedyowusu/koolbase_flutter
What this package is like to depend on
Last release 4 days ago
19 Aug 2026
Ships fairly regularly
a new release about every 1 weeks
Most releases are documented
notes for 56 of 76 stable releases
Nothing withdrawn
no release was ever pulled
5 months old
76 releases · first in 2026
76 releases in the last 12 months
see the full history below
Release timeline
76 releases · Mar 2026 to Aug 2026Releases
latest 60 of 76-
11.2.119 Aug 2026Release notes
Open source →- Analytics events now carry the signed-in user automatically.
identify()existed, but nothing errored when an app never called it — so every event landed anonymous, and retention, funnels and any per-user analysis were quietly worthless. Found by looking at a real project: 53 events, 8 registered users, zero events carrying a user id. The SDK already knows who is signed in; it should say so. identify()still wins where an app has its own identity system, and the existingreset()releases that override — without it, events keep carrying the previous user after they sign out.
- Analytics events now carry the signed-in user automatically.
-
11.2.019 Aug 2026Release notes
Open source →Koolbase.fiscal— authority-grade sales recording.submit()records a transaction and drives fiscalization;status()follows it and returns the certification the tax authority granted (in Ghana: the GRA signature, receipt number and verification QR a compliant receipt must carry). Live for Ghana's GRA E-VAT; the reference adapter runs the same machine — gapless numbering, sealed immutable documents, full evidence — anywhere an authority integration doesn't exist yet.- The
clientRefyou pass is idempotent: resubmitting the same reference always returns the same intent, never a second fiscal number. A timed-out submit may still have fiscalized, so retrying or polling both converge on the truth — the exception message says so rather than leaving you guessing. - Fiscal deliberately does NOT use the offline write queue. A register needs
a real answer or an explicit "not yet"; silently queueing a fiscalization
would let a cashier believe a receipt is legal when the authority has never
seen it. Offline-capable point of sale should record the commercial sale
locally and submit fiscally on reconnect — the idempotent
clientRefmakes that replay a single call. FiscalIntentResultexposesstatus,numbers,blockedReason,certification,isFiscalizedandisPending. Ablockedresult means nothing was consumed — no fiscal number was spent — so fixing the cause and resubmitting the same reference resumes that exact transaction. Afiscalizedresult with a null certification is valid for document kinds the authority acknowledges without certifying (purchase records); it is never an error.
-
11.1.116 Aug 2026Release notes
Open source →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.1Release notes
Open source →- Offline writes now refuse with
KoolbaseUnauthenticatedExceptionwhen no user is signed in, instead of enqueueing under a null owner. A null-owner queue row is invisible to every per-user read and replay — the write neither fails nor succeeds; it vanishes. Found on device as a parked ticket that never reached the server. Signed-in offline behavior is unchanged. Mirrors the React Native SDK's fix of the same bug.
- Offline writes now refuse with
-
11.1.016 Aug 2026Release notes
Open source →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.0Release notes
Open source →KoolbaseQuery.get()gainsfresh: true— skips the cache and returns the network's answer directly. Required for read-after-write (verifying the effect of a write you just made), reconciliation, and local projections that must only ingest server-provenance data. A fresh read still updates the cache, so SWR callers benefit from it. Plainget()behavior is unchanged.
-
11.0.014 Aug 2026Release notes
Open source →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. -
10.5.011 Aug 2026Release notes
Open source →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. -
10.4.007 Aug 2026Release notes
Open source →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. -
10.3.005 Aug 2026Release notes
Open source →KoolbaseAuthGate, KoolbaseAuthScope, KoolbaseCollectionList, and
KoolbaseCollectionController ship as the first behavioral widgets: the
SDK now encodes auth branching and the stale-while-revalidate list
contract as composable components rather than patterns every app
hand-writes. Query refresh streams are now keyed by query identity
rather than collection name, matching their documented contract — see
the changelog behavior note. README gains a Widgets section and catches
up two versions of version-string drift (was still ^10.1.2). -
10.2.004 Aug 2026Release notes
Open source →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.
-
10.1.203 Aug 2026Release notes
Open source →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. -
10.1.102 Aug 2026Nothing published for this version
-
10.1.002 Aug 2026Nothing published for this version
-
10.0.001 Aug 2026Nothing published for this version
-
9.9.001 Aug 2026Nothing published for this version
-
9.8.001 Aug 2026Nothing published for this version
-
9.7.029 Jul 2026Nothing published for this version
-
9.6.026 Jul 2026Nothing published for this version
-
9.5.025 Jul 2026Release notes
Open source →Breaking:
Koolbase.messaging.send()removed. Sending push notifications is server-initiated only — it requires a secretkb_live_key and must run on your backend or in a Koolbase Function, never in the app. The publishable key the SDK holds ships inside your app binary; allowing it to send would let anyone who extracts it push to your users. The API already rejects publishable-key sends with 401. Device registration (Koolbase.messaging.registerToken) is unchanged. See docs: /sdk/messaging.Fixed: in-app
functions.deploy()now uses the auto-refreshing session token. It previously read a manually-set static token that expired after 15 minutes whileinvoke()(which already auto-refreshed) kept working — so a long-lived app could see deploys fail while invocations succeeded.deploy()now sharesinvoke()'s token path. The removedsetAuthToken()method is no longer needed; the SDK manages the session token itself. -
9.4.122 Jul 2026Release notes
Open source →Fix: package failed to compile on stock Flutter (all platforms). 9.4.0 unconditionally imported
dart:_internal(VM code-push bindings that resolve only against the Koolbase-patched engine). The stock Dart frontend rejects platform-private imports at kernel compile, so every build failed with "Can't access platform private library" — analyzer-clean, caught only at build time. The bindings are now a stock-safe stub: on a stock engine, code push reports "engine not present" through existing failure paths (koolbaseBuildId()returns'',applyKoolbasePatchreturns sentinel-990). No API changes; upgrading from 9.4.0 requires no code changes. -
9.4.012 Jul 2026Release notes
Open source →- Code Push (VM-level, iOS): flash-free boot apply.
Koolbase.initializenow completes the iOS boot-time patch apply (local disk only, ~5ms measured on device) before returning, so the app's first frame already runs the patched code — the brief v1→v2 flash on cold launch is gone, with zero configuration. The network check/download remains fully asynchronous and can never block startup. - Code Push (VM-level): device outcome events. The client reports
patch_downloaded(on successful stage),patch_activated(on the boot that promotes a new patch, withpatch_number), andpatch_failed(on rejection, with the rejection code and whether the artifact was newly staged or the durable copy) to/v1/code-push/patch-events. Dashboards and rollout decisions can now count real device activations instead of inferring installs from patch-check serves. Fire-and-forget: event reporting never delays boot and failures are silent. - Code Push (VM-level, iOS): correct patch bookkeeping after rejections. The boot apply now records what actually happened (new patch applied / durable re-applied / clean base boot) and reconciliation consumes that record. Previously, a rejected download that fell back to the existing patch could mark the rejected patch as current — the device then reported a patch it wasn't running, suppressing future update offers. A clean base boot (e.g. after an app-store update invalidates a persisted patch) now also resets the reported patch to 0.
- Code Push (VM-level, iOS): rejected patches are quarantined, not retried. A persisted patch that fails verification (for example, stale after an app update) is moved aside on first rejection instead of being re-read and re-rejected on every subsequent launch. Quarantined artifacts are removed once a healthy boot completes.
- Code Push (VM-level, iOS): flash-free boot apply.
-
9.3.109 Jul 2026Nothing published for this version
-
9.3.026 Jun 2026Release notes
Open source →- Code Push (VM-level): the client now reports
flutter_versionon patch-check so the resolver can refuse a patch built on a different Flutter engine version.- Reads the CLI-stamped
assets/koolbase_flutter_versionasset (written bykoolbase build/koolbase release) and sends it alongsidebuild_id/release_version. - Pairs with the server-side resolver guard that constrains matching on
flutter_version, closing two cross-engine mis-serve cases (collidingbuild_idacross engine versions;release_versionmatching on app version alone). - Fully backward-compatible: an app built without the asset sends no
flutter_versionand the server falls back to legacy matching — no change for apps already in the field.
- Reads the CLI-stamped
- Code Push (VM-level): the client now reports
-
9.2.121 Jun 2026Release notes
Open source →- Widen
package_info_plusto>=8.0.0 <10.0.0andflutter_secure_storageto>=9.0.0 <11.0.0. The SDK only uses the stable surface of both (PackageInfo version/buildNumber; SecureStorage read/write/delete with default AndroidOptions and standard KeychainAccessibility), so the previous latest-major pins needlessly blocked — and for secure_storage risked force-migrating — host apps on the prior major.
- Widen
-
9.2.020 Jun 2026Release notes
Open source →- Code Push (bundle): recall/rollback now actually reverts a recalled bundle on device.
- The runtime resolver persists a pending-revert marker when the server issues a rollback and consumes it at the start of the next cold launch, before re-applying any stored bundle. Previously the rollback was logged but never persisted, so a recalled bundle kept re-applying on every launch.
- Pairs with the server-side resolver fix returning rollback/revert_to:0 when a device runs a bundle that has been recalled with no published replacement.
- Affects iOS and Android bundle recall.
- Code Push (bundle): recall/rollback now actually reverts a recalled bundle on device.
-
9.1.020 Jun 2026Release notes
Open source →- Code Push (VM-level):
KoolbaseVmPatchClient— over-the-air Dart code updates for Android.- Self-contained: no MainActivity or platform-channel wiring required.
- Checks in with the Koolbase resolver, downloads and stages patches; the Koolbase engine applies them on next launch with signature + build_id verification and automatic crash-revert.
- Per-ABI build_id resolution for multi-ABI app bundles (arm64-v8a / armeabi-v7a).
- Reports the running build's build_id and current patch number on check-in.
- Code Push (VM-level):
-
9.0.008 Jun 2026Release notes
Open source →Breaking changes
- None.
modeandminSimilarityare both optional; existingsearchSemanticcallers continue to work unchanged. Major bump reflects the conceptual expansion of the search contract (three retrieval modes instead of one), not API-breaking removals.
- None.
-
8.0.007 Jun 2026Nothing published for this version
-
7.0.006 Jun 2026Nothing published for this version
-
6.5.005 Jun 2026Release notes
Open source →Object versioning support — full read + write surface against versioned buckets. None of this changes existing behavior on non-versioned buckets.
New
KoolbaseObjectVersionmodel — one entry in a path's version timeline. CarriesversionId,size,metadata,isDeleteMarker,isCurrent,createdAtand the rest of the version-row shape.KoolbaseStorageClient.listVersions({bucket, path})— returns the full timeline newest-first, current + history mixed.KoolbaseStorageClient.getVersion({bucket, path, versionId})— metadata for one specific version.KoolbaseStorageClient.restoreVersion({bucket, path, versionId})— brings a history version back to current; the previously-current row is snapshotted to history first, so the restore is itself a versioned event.KoolbaseStorageClient.purgeVersion({bucket, path, versionId})— hard removes a single history version (row + R2 bytes).
Extended
KoolbaseStorageClient.getDownloadUrl(...)accepts an optionalversionId— when present, the returned URL points to that specific version's bytes from.versions/.KoolbaseStorageClient.delete(...)accepts an optionalforcePurge—truewipes the entire timeline for the path (all history rows, all.versions/R2 keys, canonical, and the current row).
-
6.4.005 Jun 2026Release notes
Open source →- feat(storage): edge image transforms (Gap #8).
- New
KoolbaseImageTransformvalue class — width, height, format, quality, fit, dpr, gravity. Pair with the newKoolbaseImageFormat,KoolbaseImageFit, andKoolbaseImageGravityenums for type-safe option construction. Out-of-range numeric values clamp silently to Cloudflare's valid ranges (width/height 1–2000, quality 1–100, dpr 1–3). KoolbaseStorageClient.publicUrl({transform})andKoolbaseObject.publicUrl(bucket, {transform})accept an optional transform; the resulting URL hits Cloudflare's image pipeline atcdn.koolbase.com/cdn-cgi/image/<opts>/...and serves a resized, re-encoded copy of the source. Original URL behavior unchanged whentransformis omitted.KoolbaseStorageClient.publicUrlWithPreset({projectId, presetName, bucket, path})andKoolbaseObject.publicUrlWithPreset(bucket, presetName)resolve a named preset stored server-side (managed via the dashboard or REST API) atcdn.koolbase.com/p/{project_id}/ {preset_name}/{bucket}/{path}. Edit the preset once on the server and every URL using it updates as the edge cache rolls over.
- New
- Cloudflare bills unique transformations per calendar month; every Koolbase account includes 5,000 free. Transformed responses are edge-cached for 4 hours.
- No breaking changes. All new APIs are additive; existing
publicUrlcalls withouttransformproduce the exact same URL they did in 6.3.0.
- feat(storage): edge image transforms (Gap #8).
-
6.3.002 Jun 2026Release notes
Open source →- feat(storage): public bucket CDN URLs (Gap #2 SDK polish).
KoolbaseObjectgains anr2Bucket: Stringfield identifying which physical R2 bucket holds the object's bytes. Always populated.'koolbase-storage-public'means the object has a stable CDN URL; anything else (typically'koolbase-storage') means it's in private storage and reads go through a presigned URL viagetDownloadUrl.KoolbaseObject.publicUrl(String bucketName)returns the stablehttps://cdn.koolbase.com/...URL for the object when it lives in the public R2 bucket,nullotherwise. Use this when you have an object instance and want a safe URL — returnsnullrather than a URL that 404s for private or legacy public-bucket files.KoolbaseStorageClient.publicUrl({projectId, bucket, path})— static helper that builds the CDN URL pattern unconditionally. Use for build-time URL generation where you have the inputs but don't need (or want) a check that the file is actually in a public bucket.
- No breaking changes.
getDownloadUrlalready returns the CDN URL for objects in public buckets since the server-side Gap #2 deploy on Jun 2 2026 — this release just makes that URL constructible without a network round-trip.
- feat(storage): public bucket CDN URLs (Gap #2 SDK polish).
-
6.2.002 Jun 2026Release notes
Open source →- feat(storage): custom object metadata. Attach arbitrary key/value
pairs to stored objects at upload time, mutate via merge semantics
post-upload, read alongside any
KoolbaseObject.KoolbaseStorageClient.upload()gains an optionalmetadata: Map<String, String>named param. Set at confirm time; REPLACES prior metadata on theoverwrite: truepath (matches GCS semantics — a new upload at a path produces a new object, not a patch of the old).- New
KoolbaseStorageClient.updateMetadata()method with merge semantics: keys with a non-null value are set/updated, keys withnullare deleted, keys absent from the payload are untouched. One call handles add, update, and delete atomically. KoolbaseObjectgains ametadata: Map<String, String>field. Always non-null —{}when empty, nevernull— so callers can treat it as a guaranteed map without nil checks.- New
KoolbaseStorageMetadataInvalidException(extendsKoolbaseStorageException) thrown for server-side validation failures. Itsdetailfield names the failing key and rule (e.g.key "bad key": must match [a-z0-9_]+,exceeds 50 keys (got 53)) so callers can surface actionable errors without guessing what shape rule was violated.
- Validation rules (server-side, recreated for SDK doc convenience):
≤50 keys, ≤8KB total, keys 1–64 chars
[a-z0-9_]+, values ≤1024 chars, leading underscore reserved for system keys.
- feat(storage): custom object metadata. Attach arbitrary key/value
pairs to stored objects at upload time, mutate via merge semantics
post-upload, read alongside any
-
6.1.101 Jun 2026Release notes
Open source →Fixed
- Storage error mapper switches on lowercase wire codes (
path_conflict,quota_exceeded,file_too_large,mime_not_allowed) after the server normalized storage codes to lowercase snake_case. Without this patch, v6.1.0 customers see genericKoolbaseStorageExceptioninstead of the typed subclass for storage limit errors. No semantic changes beyond the case match.
- Storage error mapper switches on lowercase wire codes (
-
6.1.001 Jun 2026Release notes
Open source →- feat(storage): three new typed exceptions for bucket-limit failures
introduced server-side in Storage #2. All extend
KoolbaseStorageExceptionso existing catch-all blocks continue to work; catch the specifics to branch on the kind of limit hit.KoolbaseStorageQuotaExceededException— 409 +QUOTA_EXCEEDED, thrown when an upload would push the bucket past itsmax_size_bytescap.KoolbaseStorageFileTooLargeException— 413 +FILE_TOO_LARGE, thrown when a single file exceeds the bucket'smax_file_size_bytescap.KoolbaseStorageMimeTypeException— 415 +MIME_NOT_ALLOWED, thrown when an upload's content-type isn't in the bucket'sallowed_mime_typesallowlist (supportstype/*wildcards).
- Mapper (
koolbaseStorageError/koolbaseStorageErrorFromResponse) recognizes the new codes and the new HTTP statuses (413, 415). - Backwards-compatible: existing callers using
on KoolbaseStorageExceptionkeep working; the new types let callers surface clearer messages or prompt the user to delete files / pick a smaller file / pick a different file type.
- feat(storage): three new typed exceptions for bucket-limit failures
introduced server-side in Storage #2. All extend
-
6.0.001 Jun 2026Release notes
Open source →Breaking — realtime
Koolbase.realtime.on/onRecordCreated/onRecordUpdated/onRecordDeletedno longer take aprojectId— the project is derived from your session token, matching the React Native SDK. Migrateon(projectId: ..., collection: 'x')toon(collection: 'x').
Breaking — storage
KoolbaseStorageClient.upload()is now safe-by-default. Uploads to a path where an object already exists are rejected with a newKoolbaseStorageConflictExceptioninstead of silently overwriting the existing object. Passoverwrite: trueto opt into the previous replacing behavior.- Storage operations now throw typed
KoolbaseStorageExceptionsubtypes instead of genericException— catchingExceptionstill works but catching the specific subtypes (or theKoolbaseStorageExceptionbase) gives you cleaner branching.
Added
KoolbaseStorageException— base class for all storage failures, mirroring theKoolbaseDataExceptionpattern from the database layer.KoolbaseStorageConflictException(code: PATH_CONFLICT) — thrown when an upload would replace an existing object andoverwrite: false. Exposes the collidingpathfrom the server response.KoolbaseStorageNotFoundException,KoolbaseStorageValidationException,KoolbaseStoragePermissionException— typed exceptions for the other storage error classes (404, 400, 403). Storage operations now throw these instead of a genericException.koolbaseStorageError(statusCode, body)andkoolbaseStorageErrorFromResponse(res)— code-first response-to-exception mappers, matching the database layer's pattern.
Migration — storage uploads
If your app uploads to deterministic paths (e.g.
avatars/{user_id}.png) and relied on the upload silently replacing the previous file:// Before — silent overwrite await Koolbase.storage.upload( bucket: 'avatars', path: 'me.png', file: file, ); // After — explicit overwrite await Koolbase.storage.upload( bucket: 'avatars', path: 'me.png', file: file, overwrite: true, );If you want a conflict prompt (recommended for user-supplied filenames):
try { await Koolbase.storage.upload( bucket: 'documents', path: filename, file: file, ); } on KoolbaseStorageConflictException catch (e) { final ok = await showConfirm('${e.path} already exists. Overwrite?'); if (ok) { await Koolbase.storage.upload( bucket: 'documents', path: filename, file: file, overwrite: true, ); } }If you catch generic exceptions from storage operations, consider catching
KoolbaseStorageException(or specific subtypes) for cleaner error handling:try { await Koolbase.storage.upload(...); } on KoolbaseStorageConflictException { // Path already exists — prompt user } on KoolbaseStorageNotFoundException { // Bucket missing or deleted } on KoolbaseStoragePermissionException { // Caller not authorized } on KoolbaseStorageException catch (e) { // Any other storage error showError(e.message); }Server requirements
- Requires a Koolbase server build with
PATH_CONFLICT409 support (shipped alongside this release).
5.1.0
Fixed
- Realtime now connects. The client was protocol-correct but was never handed an access token (the push-based
setTokenwas never wired), so it never connected. Switched to the same token-provider model as the other clients; it now authenticates with the user session and streamscreated/updated/deletedevents.
Removed
KoolbaseRealtimeClient.setToken— dead push-model plumbing that was never wired. The token now flows from the SDK automatically.
5.0.0
BREAKING — security
- Data-plane requests (database, storage, functions, offline sync) authenticate with the signed-in user's access token (Authorization: Bearer) instead of the x-user-id header. The header is no longer sent or trusted. Requires the matching Koolbase server build.
- KoolbaseStorageClient.upload() no longer accepts a
userIdparameter — identity comes from the active session automatically. - End-user identity now flows automatically from Koolbase.auth; manual Koolbase.db.setUserId(...) is no longer needed for auth (it remains only for tagging offline-cached records).
Added
- KoolbaseAuthClient.validAccessToken() — returns a currently-valid token, refreshing (single-flight) near expiry; data-plane clients pull from it per request so identity follows the live session.
-
5.1.028 May 2026Nothing published for this version
-
5.0.026 May 2026Nothing published for this version
-
4.1.025 May 2026Release notes
Open source →- Code Push — mandatory bundles. The SDK now honors a bundle's
mandatoryflag (set from the dashboard or viaPATCH /mandatory). When a mandatory bundle is staged for the next launch:Koolbase.codePush.hasMandatoryUpdatereturnstrue— poll it on resume to gate your UI.- The optional
onMandatoryUpdatecallback onKoolbaseConfigfires immediately withMandatoryUpdateInfo(version, bundleId), so you can prompt the user to restart and apply the required update.
- No breaking changes.
- Code Push — mandatory bundles. The SDK now honors a bundle's
-
4.0.025 May 2026Release notes
Open source →Breaking
- Removed the
Koolbase.otaclient (KoolbaseOtaClient) and its models (OtaCheckResult,OtaProgress,OtaDownloadState). UseKoolbase.codePushfor config/flag/directive overrides, or Koolbase Storage for shipping and reading raw files. This consolidates onto a single bundle client — matching the React Native SDK and the server, which already use code push exclusively. - Dropped the
sign_in_with_appledependency — it was only used by the removedKoolbaseAppleAuth. The currentsignInWithApple({identityToken})is library-agnostic, so the SDK no longer pulls it. If your app uses Apple Sign-In, declaresign_in_with_applein your ownpubspec.yaml.
3.3.0
- Auth exceptions are now selected from the server's stable error
code(with status/message fallback for older servers), retiring brittle message string-matching. - New typed data-layer exceptions —
KoolbaseNotFoundException,KoolbaseValidationException,KoolbasePermissionException,KoolbaseRateLimitException— plus a sharedKoolbaseDataExceptionbase. Database operations now throw these (code-first) instead of a genericException. KoolbaseConflictExceptionnow exposes the collidedfieldwhen the server reports it.- Fix:
insertno longer queues a server-rejected write (e.g. a unique conflict) as an offline write — 4xx rejections surface immediately; only genuine network failures are queued.
- Removed the
-
3.3.024 May 2026Nothing published for this version
-
3.2.024 May 2026Release notes
Open source →- Added
KoolbaseConflictException, thrown byinsert,update, andupsertwhen a write violates a collection's unique constraint (HTTP 409). Catch it to handle duplicates.
- Added
-
3.1.123 May 2026 -
3.1.023 May 2026Release notes
Open source →- Added
Koolbase.db.upsert(collection:, match:, data:)— insert-or-update by a match filter; returnsKoolbaseUpsertResult { record, created }. Online-only. - Added
Koolbase.db.deleteWhere(collection:, filters:)— bulk delete by filter; returns the number of records deleted. Online-only.
- Added
-
3.0.022 May 2026Release notes
Open source →Breaking
- Flat record shape.
KoolbaseRecordno longer wraps your fields under adataenvelope. Your fields are now top-level, with system metadata in a reserved$-prefixed namespace:$id,$createdAt,$updatedAt,$collection, and$createdBy(when set). - Removed
KoolbaseRecord.projectIdandKoolbaseRecord.collectionId— internal identifiers are no longer exposed on records. - Requires a Koolbase server on the flat record contract (shipped alongside this release). Older servers return the legacy envelope and will not parse.
Added
record['field']— direct field access, shorthand forrecord.data['field'].record.collection— the record's collection name.
Changed
- Populated/related records (via
populate()) and realtime payloads now use the same flat$-shape as direct reads. - Offline cache (Drift) bumped to schema v2: stale read caches are cleared on upgrade so they refetch in the new shape; pending offline writes are preserved.
Migration
- Remove any
record.projectId/record.collectionIdusage — those fields are gone. record.data['field']still works;record['field']is the new shorthand.
- Flat record shape.
-
2.11.019 May 2026Release notes
Open source →Added
- Sign in with Google — production-ready end-user OAuth via
Koolbase.auth.signInWithGoogle(idToken: ..., nonce: ...). Routes to the server endpoint at/v1/sdk/auth/oauth/googlewith RS256-only JWKS verification against Google's certs endpoint, multi-audience support (iOS / Android / web client IDs configured per environment), 15-minute replay defense, and optional nonce check. - Three new typed exceptions in
auth_exceptions.dart:GoogleSignInNotConfiguredException,InvalidGoogleTokenException,GoogleEmailRequiredException. Reuses existingOAuthEmailConflictExceptionandUserDisabledException.
Example with the
google_sign_inpackageimport 'package:google_sign_in/google_sign_in.dart'; final googleUser = await GoogleSignIn().signIn(); final googleAuth = await googleUser?.authentication; final user = await Koolbase.auth.signInWithGoogle( idToken: googleAuth!.idToken!, );Auto-link policy
Same as Apple Sign-In (v2.10.0). A new Google identity attaches to an existing user only when BOTH the Google email AND the existing user's email are verified, AND emails match (case-insensitive). Otherwise sign-in either creates a new user (no email collision) or surfaces
OAuthEmailConflictException.Configuration required
Before users can sign in with Google, configure the provider for your environment. Run this against your Koolbase project's
project_oauth_configs(dashboard UI for OAuth config lands in a later release):UPDATE project_oauth_configs SET google_client_ids = ARRAY[ '<your-ios-client-id>.apps.googleusercontent.com', '<your-android-client-id>.apps.googleusercontent.com', '<your-web-client-id>.apps.googleusercontent.com' ], enabled = true WHERE environment_id = '<your-env-id>' AND provider = 'google';Get the client IDs from Google Cloud Console under Credentials → OAuth 2.0 Client IDs. You'll need one per platform (iOS, Android, web).
Coming next
- React Native SDK v1.11.0 — same surface
- Dashboard UI for OAuth config — replaces the SQL workflow
Documentation
- README rewritten to accurately reflect the v2.10.0 SDK surface. No SDK code changes; this release exists to refresh the README rendered on the pub.dev package page.
- Removed fictional
Koolbase.auth.signInWithGooglereference. Google Sign-In is planned for v2.11.0 — noted explicitly in the OAuth section. - Replaced the deprecated
KoolbaseAppleAuth.signIn()example with the newKoolbase.auth.signInWithApple(identityToken: ..., nonce: ..., fullName: ...)v2.10.0 API using thesign_in_with_applepackage. - Added
Koolbase.auth.authStateChanges.listen()example. - Replaced the Firebase/Supabase comparison table with a Koolbase-only feature inventory.
- Bumped install snippet from
^2.8.0to^2.10.0.
2.10.0
- Sign in with Google — production-ready end-user OAuth via
-
2.10.119 May 2026Nothing published for this version
-
2.10.019 May 2026Nothing published for this version
-
2.9.118 May 2026Nothing published for this version
-
2.9.018 May 2026Nothing published for this version
-
2.8.012 May 2026Nothing published for this version
-
2.7.009 May 2026Release notes
Open source →Phone + OTP authentication
Sign users in with their phone number — for emerging markets and apps where email isn't the primary identifier.
New methods on
Koolbase.auth:sendOtp({required String phoneNumber})— sends a 6-digit OTP to an E.164 phone number, returns the expiry timestamp.verifyOtp({required String phoneNumber, required String code})— verifies the code and signs the user in (creates the account if new). ReturnsPhoneVerifyResultwith anisNewUserflag for routing first-time users to onboarding.linkPhone({required String phoneNumber, required String code})— links a phone number to an already-authenticated user.
New types:
OtpSendResult,PhoneVerifyResult.KoolbaseUsernow exposesphoneNumberandphoneVerifiedfields.New exceptions:
InvalidPhoneNumberException,OtpExpiredException,OtpInvalidException,OtpMaxAttemptsException,OtpRateLimitException,PhoneAlreadyLinkedException,SmsConfigMissingException.Phone numbers must be in E.164 format (e.g.
+233244000000). Configure your SMS provider (Twilio, Africa's Talking, or Hubtel) in the Koolbase dashboard before using. -
2.6.411 Apr 2026 -
2.6.309 Apr 2026 -
2.6.209 Apr 2026Release notes
Open source →- Updated dependencies to latest versions
- Fixed static analysis warnings
- Removed deprecated encryptedSharedPreferences parameter
-
2.6.109 Apr 2026 -
2.6.009 Apr 2026Release notes
Open source →Logic Engine v2 — Richer conditions
New operators:
gte— greater than or equalslte— less than or equalscontains— string or list contains valuestarts_with— string starts withends_with— string ends within_list— value is in a listnot_in_list— value is not in a listbetween— numeric value in range [min, max]is_true— value is boolean trueis_false— value is boolean falsenot_exists— value is null or missing
All operators work with AND/OR condition groups.
Example
{ "op": "and", "conditions": [ { "op": "gte", "left": { "from": "context.usage" }, "right": 5 }, { "op": "in_list", "left": { "from": "context.plan" }, "right": ["free", "trial"] } ] } -
2.5.109 Apr 2026 -
2.5.009 Apr 2026Release notes
Open source →Sign in with Apple
- Added
KoolbaseAppleAuth.signIn()— Sign in with Apple for Flutter - Added
KoolbaseAuthClient.oauthLogin()— unified OAuth login method - Added
AuthApi.oauthLogin()— server-side Apple identity token verification - Apple identity token verified server-side using Apple's JWKS endpoint
- Supports email relay addresses from Apple private email relay
Usage
import 'package:koolbase_flutter/koolbase_flutter.dart'; final session = await KoolbaseAppleAuth.signIn(); if (session != null) { print('Signed in: \${session['user']['email']}'); }Setup required
Add
sign_in_with_appleto your pubspec.yaml and configure your App ID in the Apple Developer portal. - Added
-
2.4.005 Apr 2026Release notes
Open source →Koolbase Cloud Messaging
- Added
KoolbaseMessaging— push notification delivery via FCM - Added
Koolbase.messaging.registerToken(token, platform)— register FCM device token with Koolbase - Added
Koolbase.messaging.send(to, title, body, data)— send push notification to a specific device KoolbaseConfigextended withmessagingEnabledparameter (default: true)- Device ID automatically attached to token registration
Usage
// After obtaining FCM token from firebase_messaging final fcmToken = await FirebaseMessaging.instance.getToken(); await Koolbase.messaging.registerToken( token: fcmToken!, platform: 'android', // or 'ios' ); // Send to a specific device await Koolbase.messaging.send( to: deviceToken, title: 'Your order is ready', body: 'Pick up at counter 3', data: {'order_id': '123'}, );Setup required
Add your FCM server key as a project secret named
FCM_SERVER_KEYin the Koolbase dashboard. - Added
-
2.3.104 Apr 2026Release notes
Open source →- Updated README — added Code Push, Analytics, Logic Engine sections, comparison table, clearer get started guide