Changelog — Web

Every release of the @koolbase/js SDK, newest first. Dates are when each version was published to npm.

v12.38.0

Minor1 November 2026
New
  • —Koolbase.initializeForTesting({ fetch, baseUrl?, publicKey? }): the SDK started for a test against a fake server. initialize itself runs, with the given fetch for every request, an in-memory session and platform, and no analytics or messaging, so a test can sign in, have the session end and sign out while the SDK's own auth and session code runs. Same contract as koolbase_flutter 12.51.0.
  • —Koolbase.resetForTesting(): the SDK as before initialize, for the next test.

v12.33.0

Minor21 October 2026
Changes
  • —Search by meaning takes the typed filter (#173). Same contract as koolbase_flutter 12.43.0.
  • —searchSemantic({ filter, timezone }): the same KbWhereNode tree query() takes (#165), applied inside the collection's read rule in semantic, lexical and hybrid modes. The device's zone unless one is named. filter with where throws before anything is sent; existing searches are unchanged.

v12.32.0

Minor20 October 2026
Changes
  • —Typed queries (#165): filters with and/or groups, ranges and dates, and sorts by a field's declared type. Same contract as koolbase_flutter 12.42.0.
  • —where: KbWhere.and([...]) in db.query() options: a tree of conditions. KbWhere.and / KbWhere.or group them, up to 3 deep. KbField(name) gives the operators: eq, neq, gt, gte, lt, lte, contains, startsWith, isIn, notIn, between, before, after, onOrBefore, onOrAfter, on, isEmpty, isNotEmpty, isTrue, isFalse. The server checks each against the field's declared type and runs the filter after the collection's read rule, so a filter only narrows what the caller may read.
  • —Operands the server resolves: KbValue.now(), startOfToday(), startOfTomorrow(), startOfWeek(), startOfNextWeek(), startOfMonth(), with an offset, and the signed-in caller, KbValue.callerUserId() / KbValue.callerClaim(name), read from the session, never from the app.
  • —sort: [KbSort.desc('featured'), KbSort.asc('starts_at')]: up to 3 fields, typed by the declaration, empty values last both ways, ties broken by the record's id.
  • —Time zone: relative times and on resolve in the device's IANA zone (Intl), or the one named with timezone: 'Europe/London'.
  • —Collections: the collection controller takes the same as filter, sort and timezone (its where stays the equality map).
  • —One language per query: combining where/sort/timezone with filters/orderBy/orderDesc throws TypeError before anything is sent. Existing queries are sent exactly as before.
  • —Refusals (query_invalid, query_too_complex, query_operator_invalid, query_field_untyped, query_timezone_invalid, query_caller_path_invalid, query_sort_invalid, query_sort_untyped) arrive as KoolbaseValidationError with the code.
  • —Caching: a typed query, its zone included, is its own cache entry.

v12.31.0

Minor20 October 2026
Changes
  • —A failed Koolbase request leaves a trace in a development build (#169). Same contract as koolbase_flutter 12.41.0.
  • —Development log of failed requests: in a development build (React Native's and Expo's __DEV__, else the bundler's NODE_ENV), every Koolbase request that fails as the caller receives it prints one console.warn line, [Koolbase] POST /v1/sdk/auth/email/code -> 401 invalid_public_key: ...: the method, the path, the status and the server's reason. Never the host, the query, a header, the key, a token or the request body. A 401 that renewing the session answers is not printed. A release build, or unbundled code with neither signal, prints nothing.
  • —Covered: every request through koolbaseFetch and sessionFetch: auth, data, storage, Functions, analytics, flags and messaging.
  • —Tested: the line carries the method, path, status and reason and none of the key, token, query, host or body; a success prints nothing; a 401 renewal answers prints nothing; the caller still reads the whole response.

v12.30.0

Minor20 October 2026
Changes
  • —A refused app key is reported as the setup problem it is, never as a wrong password and never by signing the user out (#168, #169). Same contract as koolbase_flutter 12.40.0.
  • —KoolbaseInvalidPublicKeyError (invalid_public_key, 401): the app's Koolbase key is unknown or revoked, or its project is deleted or not ready. The API answers this on any route since #169. It is deliberately not a KoolbaseUnauthenticatedError, so it never clears the session.
  • —Behaviour change: a sign-in call's 401 without a recognised code used to become InvalidCredentialsError ("Invalid email or password"), whatever the cause. It is now a generic KoolbaseAuthError carrying the server's reason. A refresh's 401 is still a session that expired. The API sends invalid_credentials whenever a password is wrong.
  • —Covered surfaces: auth (with Apple and Google sign-in), data, storage, Functions, fiscal and the offline sync engine.
  • —Tested: the code maps to its error on each surface; a data 401 for the key does not sign anyone out, and any other data 401 still does.

v12.9.0

Minor1 October 2026
Changes
  • —Storage: publicUrlFor({ bucket, path, transform }), the CDN URL for a file in a public bucket using the project identity the SDK learned at bootstrap -- no projectId argument, as Flutter's publicUrlFor. While identity is unavailable (the first bootstrap has not completed) it throws KoolbaseStorageProjectIdentityError (code project_identity_unavailable) and nudges a background bootstrap refresh, so a later call in the same session succeeds.
  • —Bootstrap at initialize: initialize now fetches the bootstrap payload (flags, remote config, version policy, project identity) in the background, as the Flutter SDK does. It never blocks startup. Flags expose projectId() and refresh().

v12.8.0

Minor30 September 2026
Changes
  • —Conditional writes. update(id, data, { expectedRevision }) and delete(id, { expectedRevision }) apply only if the record still carries that revision; otherwise they throw KoolbaseRevisionMismatchError, as the Flutter SDK's update and delete do (expected_revision in the body and in the URL respectively). Without the option both behave exactly as before.
  • —A write refused as stale refreshes the live lists. On a revision mismatch the collection's open queries re-run (every one when the collection is not known), so a list already shows the newer data; the error is thrown unchanged.
  • —Post-write refreshes run in the background, as the Flutter SDK's do. 12.7.0 awaited them, so every write waited for every live list on its collection to reload before resolving.

v12.7.0

Minor30 September 2026
Changes
  • —Live lists refresh after a write. Every open collection query (useCollection, KoolbaseCollectionController) re-runs after a successful insert, update, delete, upsert, batch or delete-where on its collection -- as the Flutter SDK's post-write refresh does. Silent (no refreshing), each query with its own filters, ordering and limit; a failure in one never stops the others. A delete of a record never read on this device refreshes every live query rather than guessing its collection. Until now a list kept what it loaded until the app was reloaded.

v12.6.0

Minor26 September 2026
New
  • —KoolbaseRecordController: one record by id, with loading, loaded, notFound and error states and refresh(). notFound covers a missing id, a record that does not exist, one this user may not read and a record from a different collection

v12.5.1

Patch26 September 2026
Changed
  • —No changes to @koolbase/js: released alongside @koolbase/react-native 12.5.1, which accepts async-storage 2.2 for Expo SDK 57

v12.5.0

Minor26 September 2026
New
  • —KoolbaseCollectionController: a collection read as a list, with loading, loaded and error states, refresh() and loadMore(). It shows the cached result first and replaces it when the server answers. Filters are equality only for now
  • —db.query(collection, { onRefresh }): when a query is answered from the cache, onRefresh receives the server's result once the background refresh lands
Changed
  • —Query cache keys no longer depend on the order of keys in the options, so { a, b } and { b, a } share one cache entry. Entries cached by an earlier version are missed once and fetched again

v12.4.0

Minor25 September 2026
Changed
  • —Analytics is off by default. Pass analyticsEnabled: true to initialize to send events. While it is off, Koolbase.analytics calls do nothing, and the first logs how to turn it on
  • —KoolbaseNetworkError.userMessage: a short message that is safe to show to people, alongside the detailed message for developers

v12.3.0

Minor25 September 2026
New
  • —db.query(collection, { cache: 'network-only' }) skips the local cache and waits for the server; the result still refreshes the cache. Use it for data changed by a Function, a server or another user
  • —db.invalidate(collection) forgets this client's cached query results for a collection, so the next query waits for the server
Changed
  • —Network failures throw KoolbaseNetworkError instead of a bare 'Failed to fetch'. It is still a TypeError, names the host that could not be reached and, in a browser, the page's own address with a pointer to the project's Trusted Origins
  • —The browser adapter no longer warns about missing persistent storage outside a browser (Node, server rendering), where memory storage is expected

v12.2.0

Minor23 September 2026
New
  • —Two-step sign-in (MFA): every sign-in method now throws MfaRequiredError carrying a challengeToken when the person's account has an authenticator on, including phone, Google and Apple, whose error parsers all map it. verifyMfa and verifyRecoveryCode finish sign-in and store the session, the latter returning { session, recoveryCodesRemaining }
  • —enrollMfa, confirmMfaEnrollment, mfaStatus, stepUpMfa, disableMfa and regenerateRecoveryCodes manage it. Six new error classes in the mapping table and fidelity list

v12.1.0

Minor23 September 2026
New
  • —Sign in with an emailed code: requestEmailCode sends a six-digit code, and signing in with it stores the session exactly as password login does. A new address becomes an account only while sign-ups are open, checked at sign-in. Each code works once and allows three attempts, and the request answers the same whether or not the address has an account. Projects can switch it off, which surfaces as its own error class
  • —Fiscal: KoolbaseFiscal with submit() and status(). A timeout on submit means poll status() with the same clientRef — do not submit again, or the sale may be recorded twice
  • —Reference errors are catchable: KoolbaseReferenceInvalidError, KoolbaseReferenceInUseError, KoolbaseDanglingReferencesError (carrying the offending records) and KoolbaseCollectionReferencedError
Changed
  • —oauthLogin() now points to signInWithGoogle() and signInWithApple(). Its deprecation said OAuth was not yet shipped and to use email and password, which had long stopped being true

v12.0.0

Major20 September 2026
Breaking
  • —Fourteen error classes now report the code the server actually sends. error.code is a public field and on these classes it carried a value the API has never emitted — EmailAlreadyInUseError said email_taken for a server that says email_in_use, and the Apple and Google classes each reported a provider-split code where the server sends one unified code. Catching by type has always worked and is unaffected: no class, constructor or export changed
  • —If you compare error.code against a literal, or assert on it in tests, update those comparisons. The full old-to-new table is in the package CHANGELOG. A comparison against an old value could never have matched a real response, but it can match a mock, which is why this is a major release
New
  • —Account settings: auth.getCurrentUser(), auth.updateProfile(), auth.changePassword() and auth.deleteAccount(). These existed on the server and in the Flutter SDK and not here, so a web application could not offer a settings screen at all. changePassword requires the current password — a stolen session should not be enough to lock the real owner out
  • —Session management: auth.listSessions(), auth.revokeSession(id) and auth.revokeAllOtherSessions(). Three endpoints had been live on the server the whole time and no SDK exposed any of them. Each session carries its device label, IP, user agent and timestamps, with isCurrent marking this device; token hashes are never included
  • —auth.auditLog(). The account's own security history for a recent-activity screen: sign-ins, failures, lockouts, password changes, verification. The server sanitizes each event against a per-type field allowlist
  • —db.aggregate(). Count and total over a whole collection, with the read rule applied inside the query — not a paged query you add up in the client. And never a bare number: collections are schemaless, so every result carries its accounting, saying how many records contributed to each measure and how many were skipped. A calendar bucket requires a timezone and the types enforce it
Changed
  • —x-koolbase-platform-version falls back to unknown rather than being sent empty. The browser version is read from the user agent, which recognises Chrome, Firefox, Safari and Edge — so Brave, Opera, Samsung Internet and in-app webviews reported nothing. An empty header stored against a session is indistinguishable from an SDK that never reported itself

v11.4.0

Minor20 September 2026
New
  • —auth.resendVerificationEmailToAddress(email) — ask for a new verification email with no session. A project requiring verified contact issues none until the account verifies and refuses login until then, so a user whose email went to spam or who waited past the 24-hour link expiry had no way back except contacting the developer
  • —It returns nothing and reveals nothing: the server answers identically whether the address has an account, has none, or is already verified, because anything else would let anyone discover who has signed up. Show the same check-your-email message either way. resendVerificationEmail() is unchanged, for a signed-in user who can safely be told more

v11.3.0

Minor20 September 2026
Fixed
  • —Thirty error codes the API emits were not mapped, so they arrived as generic errors and an app's instanceof check silently never ran
  • —Two mappings were for codes the API does not emit — session_expired and token_revoked. The server sends invalid_refresh_token; branching on the other two could never have matched
  • —restoreSession no longer clears the stored session on InvalidCredentialsError. During a restore that error points at the project key, not the user's session, so deleting the refresh token signed people out with no way back

v11.2.0

Minor20 September 2026
Fixed
  • —A function that timed out was indistinguishable from one that threw: a 504 arrived as a generic execution failure. A timeout means retry or raise the limit; an exception means fix the code
  • —upload_expired and cap_below_usage were unmapped in storage, and four database codes with them — ambiguous_match among them
  • —Errors report the code the server sent rather than their category, so a collection_not_found response no longer produces an error saying not_found

v11.1.0

Minor20 September 2026
Fixed
  • —Fourteen error codes the API emits were not mapped, arriving as generic errors
  • —WeakPasswordError carries the server's message when there is one, so a project with a stricter rule than the SDK's can say what it requires

v11.0.0

Major20 September 2026
Breaking
  • —register() returns a discriminated result rather than a user. Under a verified-contact policy the server issues no session, and the SDK was fabricating one from a response with no tokens — so an app believed it had signed someone in who could not make a single authenticated request. The result is now either authenticated with a session, or verification_required with none, and the type makes the difference impossible to ignore
  • —A pending signup persists nothing, fires no auth-state change, and does not destroy an existing session. Signing up a second account while signed in no longer signs the first one out on a project that requires verification

v10.4.0

Minor19 September 2026
New
  • —auth.verifyEmail(token) — complete email verification from a token, so an app can handle its own verification link rather than sending users to a hosted page
  • —auth.resendVerificationEmail() — re-send to a signed-in user, with the cooldown and daily cap reported as typed errors rather than a generic refusal

v10.3.0

Minor19 September 2026
Fixed
  • —Analytics events carried no user unless the app called identify() — so every event from a signed-in user was attributed to nobody, and funnels that crossed the sign-in boundary broke. Events now carry the signed-in user automatically

v10.2.0

Minor19 September 2026
Added
  • —Multiple tabs are coordinated. Tabs on one origin share one IndexedDB and one offline queue; two locks through the Web Locks API keep them honest. A short exclusive lock guards every read-modify-write of the offline state, so two tabs enqueueing at once cannot overwrite each other. A lease on replaying the queue is held for a whole flush including its HTTP calls, so two tabs coming online together send each queued write once rather than once each.
  • —A tab that cannot take the lease skips its pass and rechecks, so a write queued during another tab's flush is not stranded. A tab that closes mid-flush releases its lock automatically. In a browser without Web Locks — none current — offline queueing refuses with an explicit error rather than risking a double send.

v10.1.0

Minor19 September 2026
Fixed
  • —Uploads could not work from a browser. upload() required { uri, name, type } — a React Native shape — and fetched that URI to get the bytes. A browser File has no uri, so every upload from the web failed before it reached the network, while the docs showed a file input as if it worked. file now accepts a Blob or File directly.
Added
  • —storageTier() on the browser adapter — 'indexeddb', 'localstorage' or 'memory'. Storage is now chosen by trying each store rather than checking whether the API exists: Safari in private browsing exposes indexedDB and may refuse to open it, and a browser with site data blocked exposes localStorage and throws on write. A failure at any tier falls to the next, ending in memory with one console warning.

v10.0.2

Patch19 September 2026
Fixed
  • —A Node process that initialised the SDK never exited: the analytics flush interval and a pending realtime reconnect counted as work on Node's event loop, so a CLI, a test runner or an SSR build step hung after its last line. Both timers are now unref'd where the runtime supports it. A browser is unaffected.

v10.0.1

Patch19 September 2026
Fixed
  • —restoreSession() threw in Node. With no indexedDB the adapter fell through to localStorage, which does not exist on a server either, so a Next.js server render crashed with ReferenceError: localStorage is not defined. Storage now falls back to an in-memory store and a server render reports NoSession rather than failing.
  • —Note what this is not: server-side authentication. This package holds one session per process, which is correct for a browser tab and wrong for a server handling many users. It renders without crashing; it is not a way to sign users in on a server.

v10.0.0

Major19 September 2026
Added
  • —The first release. Numbered to match @koolbase/react-native and @koolbase/core, which share one version and one core: this package is that core composed for a browser, with sixty behavioural tests run against the browser adapter before it shipped.
  • —Auth — email and password, phone and OTP, session persistence across reloads, restoreSession(), onAuthStateChange, password reset, email verification. Google and Apple sign-in through their web OAuth flows.
  • —Database — insert, query, update, delete, upsert, bulk delete, atomic batches, populate, and semantic / lexical / hybrid search.
  • —Offline — cached reads, a durable write queue with baselines, pendingWrites(), and conflicts you resolve four ways. The same semantics as React Native, proven by the same tests.
  • —Storage, realtime, functions, feature flags, remote config, version enforcement and analytics.
Not included, by design
  • —Code push — a native-bundle concept; the web ships on deploy.
  • —Push messaging — device tokens come from a native module. Use Web Push through a service worker and your backend.
  • —The native Google and Apple sign-in libraries — the methods are here and work with a token from a web OAuth flow.