https://github.com/kennedyowusu/koolbase-react-native
React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions for mobile apps.
https://github.com/kennedyowusu/koolbase-react-native
baas backend firebase-alternative open-source react-native typescript
Last synced: 26 days ago
JSON representation
React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions for mobile apps.
- Host: GitHub
- URL: https://github.com/kennedyowusu/koolbase-react-native
- Owner: kennedyowusu
- Created: 2026-03-26T20:33:52.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-28T02:19:31.000Z (about 1 month ago)
- Last Synced: 2026-05-28T03:09:44.668Z (about 1 month ago)
- Topics: baas, backend, firebase-alternative, open-source, react-native, typescript
- Language: TypeScript
- Homepage: https://koolbase.com/
- Size: 371 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# @techfinityedge/koolbase-react-native
[](https://www.npmjs.com/package/@techfinityedge/koolbase-react-native)
[](https://opensource.org/licenses/MIT)
React Native SDK for [Koolbase](https://koolbase.com) — Backend as a Service built for mobile developers.
Auth, database, storage, realtime, functions, feature flags, remote config, version enforcement, code push, logic engine, analytics, and cloud messaging — one SDK, one `initialize()` call.
---
## Get started in 2 minutes
1. Create a free account at [app.koolbase.com](https://app.koolbase.com)
2. Create a project and copy your public key from Environments
3. Add the SDK:
```bash
npm install @techfinityedge/koolbase-react-native
# or
yarn add @techfinityedge/koolbase-react-native
# or
pnpm add @techfinityedge/koolbase-react-native
# or
bun add @techfinityedge/koolbase-react-native
```
4. Initialize at app startup:
```typescript
import { Koolbase } from '@techfinityedge/koolbase-react-native';
await Koolbase.initialize({
publicKey: 'pk_live_xxxx',
baseUrl: 'https://api.koolbase.com',
});
```
That's it. Every feature below is now available via `Koolbase.*`.
---
> **Auth is automatic (v3+).** Database, storage, and functions calls
> authenticate as the currently signed-in user — nothing to pass, no manual
> wiring. Log in (or restore a session) and every request carries that
> identity. `owner`/`authenticated` collections require an active session.
---
## Authentication
Email + password, Apple Sign-In, Google Sign-In, and phone + OTP — out of the box.
```typescript
// Register
await Koolbase.auth.register({ email: 'user@example.com', password: 'password' });
// Login
const session = await Koolbase.auth.login({ email: 'user@example.com', password: 'password' });
// Current user
const me = Koolbase.auth.currentUser;
// Logout
await Koolbase.auth.logout();
// Password reset
await Koolbase.auth.forgotPassword('user@example.com');
// Listen to auth state changes (fires immediately with current state)
const unsubscribe = Koolbase.auth.onAuthStateChange((user) => {
console.log(user ? 'signed in' : 'signed out');
});
```
---
### OAuth — Apple
Apple Sign-In uses the native authentication flow via `@invertase/react-native-apple-authentication` as a peer dependency:
```typescript
import appleAuth from '@invertase/react-native-apple-authentication';
import { Koolbase } from '@techfinityedge/koolbase-react-native';
const response = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
});
const session = await Koolbase.auth.signInWithApple({
identityToken: response.identityToken!,
nonce: response.nonce,
fullName: response.fullName
? {
givenName: response.fullName.givenName ?? undefined,
familyName: response.fullName.familyName ?? undefined,
}
: undefined,
});
```
Configure Apple Sign-In for your environment with your iOS app's Bundle ID. Full setup guide at [docs.koolbase.com/auth/oauth](https://docs.koolbase.com/auth/oauth).
---
### OAuth — Google
Google Sign-In uses the native authentication flow via `@react-native-google-signin/google-signin` as a peer dependency:
```typescript
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { Koolbase } from '@techfinityedge/koolbase-react-native';
GoogleSignin.configure({
webClientId: '.apps.googleusercontent.com',
});
const userInfo = await GoogleSignin.signIn();
const session = await Koolbase.auth.signInWithGoogle({
idToken: userInfo.idToken!,
});
```
Configure Google Sign-In for your environment with the OAuth client IDs from Google Cloud Console (typically one each for iOS, Android, and web). Full setup guide at [docs.koolbase.com/auth/oauth](https://docs.koolbase.com/auth/oauth).
---
### Phone + OTP
```typescript
// Send a one-time code
await Koolbase.auth.sendOtp({ phoneNumber: '+233200000000' });
// Verify and sign in
await Koolbase.auth.verifyOtp({
phoneNumber: '+233200000000',
code: '123456',
});
// Or link a phone to an existing account
await Koolbase.auth.linkPhone({
phoneNumber: '+233200000000',
code: '123456',
});
```
Configure your SMS provider (Twilio, Africa's Talking, or Hubtel) in the dashboard under Phone Auth.
---
## Database
```typescript
// Insert
await Koolbase.db.insert('posts', { title: 'Hello', published: true });
// Query
const { records } = await Koolbase.db.query('posts', {
filters: { published: true },
limit: 10,
orderBy: 'created_at',
orderDesc: true,
});
// Read fields off a record
const post = records[0];
console.log(post.data.title); // your fields live under .data
console.log(post.id, post.collection); // metadata
// Populate related records
const { records: postsWithAuthor } = await Koolbase.db.query('posts', {
populate: ['author_id:users'],
});
// Update / Delete
await Koolbase.db.update('record-id', { title: 'Updated' });
await Koolbase.db.delete('record-id');
```
---
### Handling unique-constraint conflicts
A write that would violate a unique constraint throws `KoolbaseConflictError`:
```ts
try {
await Koolbase.db.upsert('users', { email }, { name });
} catch (e) {
if (e instanceof KoolbaseConflictError) {
showError('That email is already registered.');
}
}
```
---
### Public bucket URLs
For files in public buckets, you can construct the stable CDN URL directly — no
network call, no expiry, embeddable anywhere a browser fetches a URL.
```typescript
import { KoolbaseStorage } from '@techfinityedge/koolbase-react-native';
// From a KoolbaseObject you already have (e.g. from upload() or another read)
const { object } = await Koolbase.storage.upload({
bucket: 'avatars',
path: `user-${userId}.jpg`,
file: { uri: imageUri, name: 'avatar.jpg', type: 'image/jpeg' },
});
const url = KoolbaseStorage.publicUrlForObject(object, 'avatars');
// url is null for private-bucket objects; the CDN URL for public-bucket ones.
if (url) {
// Safe to use — file lives in the public R2 bucket
return ;
}
// For build-time URL construction (no Object on hand)
const url = KoolbaseStorage.publicUrl({
projectId: 'proj_abc',
bucket: 'avatars',
path: 'user-123.jpg',
});
// Always returns the URL pattern; caller is responsible for knowing
// the file lives in a public bucket. For files in private buckets,
// the resulting URL will 404.
```
URLs follow the pattern `https://cdn.koolbase.com/{project_id}/{bucket}/{path}` — long-lived, edge-cached, no authentication. For files in private buckets, use `getDownloadUrl` instead, which returns a 1-hour presigned URL.
---
### Image transforms
Public bucket URLs can be transformed at the edge — resize, reformat,
optimize — without any preprocessing. Two ways:
**Direct transforms** — pass a `transform` option to `publicUrl`:
```ts
const url = KoolbaseStorage.publicUrl({
projectId: 'proj_abc',
bucket: 'avatars',
path: 'user-123.jpg',
transform: {
width: 200,
height: 200,
fit: 'cover',
format: 'auto',
quality: 85,
},
});
```
**Named presets** — store an option set server-side (via the dashboard or
REST API), reference it by name:
```ts
const url = KoolbaseStorage.publicUrlWithPreset({
projectId: 'proj_abc',
presetName: 'thumbnail',
bucket: 'avatars',
path: 'user-123.jpg',
});
// Or from a KoolbaseObject instance:
const url = KoolbaseStorage.publicUrlForObjectWithPreset(object, 'avatars', 'thumbnail');
```
Available options: `width` and `height` (1–2000), `format`
(`auto`/`webp`/`avif`/`jpeg`/`png`), `quality` (1–100), `fit`
(`scale-down`/`contain`/`cover`/`crop`/`pad`), `dpr` (1–3), `gravity`
(`auto`/`center`/`top`/`bottom`/`left`/`right`/`top-left`/`top-right`/
`bottom-left`/`bottom-right`). Transformed responses are edge-cached for 4
hours; Cloudflare includes 5,000 unique transformations/month free per
account.
See the [Image Transforms docs](https://docs.koolbase.com/storage/image-transforms)
for the full reference.
---
### Upsert
Insert a record, or update the existing one matching a filter.
```ts
const result = await Koolbase.db.upsert(
'profiles',
{ user_id: userId },
{ weightKg: 70 }
);
console.log(result.created); // true if inserted, false if updated
console.log(result.record.id);
```
> Online-only: needs the server's view to decide insert vs update, so unlike
> `insert` it isn't queued offline and throws on network failure.
### Delete where
Bulk-delete every record matching a filter. Returns the number deleted.
```ts
const deleted = await Koolbase.db.deleteWhere('sessions', {
user_id: userId,
status: 'expired',
});
```
> A non-empty filter is required. The collection's delete rule applies; for
> `owner`/`scoped` rules the delete is scoped to your own records. Online-only.
---
### Offline-first
```typescript
const { records, isFromCache } = await Koolbase.db.query('posts', { limit: 20 });
if (isFromCache) console.log('Served from local cache');
await Koolbase.db.syncPendingWrites();
```
---
### Atomic batch writes
Run multiple writes in a single server-side transaction. All operations commit together or none are applied — any failure rolls back the entire batch.
```ts
import { Koolbase, BatchOp } from '@techfinityedge/koolbase-react-native';
const results = await Koolbase.db.batch([
BatchOp.insert('orders', { total: 50, customer_id: customerId }),
BatchOp.update(inventoryId, { stock: 9 }),
BatchOp.upsert('counters', {
match: { name: 'orders' },
data: { value: 1 },
}),
BatchOp.delete(cartItemId),
]);
// results[i] corresponds to operations[i]:
// - insert / update: { type, record }
// - upsert: { type, record, created } // created = true if inserted
// - delete: { type, deleted: true }
```
**Online-only by design.** Atomicity needs the server's authoritative view, so `batch()` is never queued offline — it throws on network failure (like `upsert` and `deleteWhere`). A server-side rejection throws a `KoolbaseDataError` with the failing operation's details; nothing was persisted.
---
### Handling write conflicts
`insert`, `update`, and `upsert` are online-first: when the server is reachable they throw a typed error on rejection. Catch `KoolbaseConflictError` to handle unique-constraint violations (e.g. a duplicate email):
```ts
import { KoolbaseConflictError } from '@techfinityedge/koolbase-react-native';
try {
await Koolbase.db.insert('users', { email, name });
} catch (e) {
if (e instanceof KoolbaseConflictError) {
showError(`That ${e.field ?? 'value'} is already in use.`);
} else {
throw e;
}
}
```
When the device is offline, these writes are queued and synced automatically when connectivity returns.
---
## Storage
Upload and serve files via presigned URLs to Cloudflare R2. Uploads are
**safe-by-default** (v5+) — uploading to a path that's already taken throws
`KoolbaseStorageConflictError` instead of silently replacing the existing
file. Pass `overwrite: true` for true upsert semantics.
```typescript
// Upload — rejects if `user-${userId}.jpg` already exists
const { object, downloadUrl } = await Koolbase.storage.upload({
bucket: 'avatars',
path: `user-${userId}.jpg`,
file: { uri: imageUri, name: 'avatar.jpg', type: 'image/jpeg' },
});
// Upload — silently replaces any existing object at this path
await Koolbase.storage.upload({
bucket: 'avatars',
path: `user-${userId}.jpg`,
file: { uri: imageUri, name: 'avatar.jpg', type: 'image/jpeg' },
overwrite: true,
});
// Get download URL
const url = await Koolbase.storage.getDownloadUrl('avatars', `user-${userId}.jpg`);
// Delete
await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
```
---
### Handling upload conflicts
For user-supplied filenames, prompt the user before overwriting:
```typescript
import { KoolbaseStorageConflictError } from '@techfinityedge/koolbase-react-native';
try {
await Koolbase.storage.upload({
bucket: 'documents',
path: filename,
file: { uri, name: filename, type: mimeType },
});
} catch (e) catch (e) {
if (e instanceof KoolbaseStorageConflictError) {
const ok = await confirm(${e.path} already exists. Overwrite?);
if (ok) {
await Koolbase.storage.upload({
bucket: 'documents',
path: filename,
file: { uri, name: filename, type: mimeType },
overwrite: true,
});
}
} else {
throw e;
}
}
```
See [Error handling](#error-handling) for the full set of storage errors.
---
### Handling bucket limits
Buckets can be configured at creation time with a total size cap
(`max_size_bytes`), a per-file cap (`max_file_size_bytes`), and a
content-type allowlist (`allowed_mime_types`, supports `image/*`-style
wildcards). The server surfaces violations as typed errors:
````typescript
import {
KoolbaseStorageQuotaError,
KoolbaseStorageFileTooLargeError,
KoolbaseStorageMimeTypeError,
} from '@techfinityedge/koolbase-react-native';
try {
await Koolbase.storage.upload({
bucket: 'user-photos',
path: filename,
file: { uri, name: filename, type: mimeType },
});
} catch (e) {
if (e instanceof KoolbaseStorageMimeTypeError) {
showError('That file type is not allowed in this bucket.');
} else if (e instanceof KoolbaseStorageFileTooLargeError) {
showError('That file is too big — pick a smaller one.');
} else if (e instanceof KoolbaseStorageQuotaError) {
showError('This bucket is full — delete some files and try again.');
} else {
throw e;
}
}
````
MIME enforcement runs at presign time — no bytes are transferred before
rejection. File-size and quota enforcement run at confirm time; the
server cleans up the underlying R2 object before returning the error,
so nothing leaks.
---
## Realtime
Subscribe to live changes on a collection. Uses the signed-in user's session, so
subscribe after login. Streams `created`, `updated`, and `deleted` events for
collections whose read rule is `public` or `authenticated`.
```ts
const unsubscribe = Koolbase.realtime.subscribe('messages', (event) => {
// event.type -> 'created' | 'updated' | 'deleted'
if (event.type === 'deleted') {
console.log('deleted', event.recordId); // recordId on deletes
} else {
console.log(event.type, event.record!.data); // record on created/updated
}
});
unsubscribe();
```
The socket opens lazily, is shared, and reconnects automatically. The project is
taken from the user's session.
---
## Functions
Invoke deployed serverless functions. When a user is signed in via `Koolbase.auth`, their access token is automatically forwarded — the function receives the caller's identity via `ctx.auth`. No token handling on the client side.
```typescript
// Invoke a deployed function
const result = await Koolbase.functions.invoke('send-welcome-email', {
userId: '123',
});
if (result.success) console.log(result.data);
```
Inside the function, read the caller:
```typescript
export async function handler(ctx) {
const userId = ctx.auth?.user_id;
if (!userId) {
return { error: { code: 'AUTH_REQUIRED' }, status: 401 };
}
// Authenticated logic here
return { ok: true };
}
```
Token refresh is transparent — the SDK reads the current token fresh on every invoke. Full docs at [docs.koolbase.com/functions/authentication](https://docs.koolbase.com/functions/authentication).
---
## Feature Flags & Remote Config
```typescript
if (Koolbase.isEnabled('new_checkout')) { /* ... */ }
const timeout = Koolbase.configNumber('timeout_seconds', 30);
const apiUrl = Koolbase.configString('api_url', 'https://api.myapp.com');
const dark = Koolbase.configBool('force_dark_mode', false);
```
---
## Version Enforcement
```typescript
const result = Koolbase.checkVersion('1.2.3');
if (result.status === 'force_update') {
// block and show update screen
}
```
---
## Code Push
Push config overrides, feature flag overrides, and directive-driven behaviour without a store release.
```typescript
await Koolbase.initialize({
publicKey: 'pk_live_xxxx',
baseUrl: 'https://api.koolbase.com',
codePushChannel: 'stable',
});
// Bundle values override Remote Config + Feature Flags transparently
const timeout = Koolbase.configNumber('api_timeout_ms', 3000);
// Directive handlers
Koolbase.codePush.onDirective('force_logout_all', (value) => {
if (value) Koolbase.auth.logout();
});
Koolbase.codePush.applyDirectives();
```
---
### Mandatory updates
Mark a bundle **mandatory** in the dashboard (or via `PATCH /mandatory`) when every device must apply it before continuing — surfaced as a push callback and a pollable flag:
```typescript
await Koolbase.initialize({
publicKey: 'pk_live_xxxx',
baseUrl: 'https://api.koolbase.com',
// Fires the moment a mandatory bundle is staged for the next launch
onMandatoryUpdate: ({ version }) => {
showRestartRequiredDialog(version);
},
});
// Or poll it — e.g. on app resume — before letting the user proceed
if (Koolbase.codePush.hasMandatoryUpdate) {
showRestartRequiredDialog();
}
```
A mandatory bundle still activates on the next cold launch like any other; the callback and flag just let you prompt the user to restart now instead of waiting.
---
## Logic Engine
Define conditional app behavior as data in your Runtime Bundle — no code changes required.
```typescript
const result = Koolbase.executeFlow('on_checkout_tap', {
plan: user.plan,
usage: user.usage,
});
if (result.hasEvent) {
switch (result.eventName) {
case 'show_upgrade': navigation.navigate('Upgrade'); break;
case 'go_checkout': navigation.navigate('Checkout'); break;
}
}
```
**v2 operators:** `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains`, `starts_with`, `ends_with`, `in_list`, `not_in_list`, `between`, `is_true`, `is_false`, `exists`, `not_exists`, `and`, `or`
Full docs at [docs.koolbase.com/sdk/logic-engine](https://docs.koolbase.com/sdk/logic-engine).
---
## Analytics
Track screen views, custom events, and user behaviour. View DAU, WAU, MAU, funnels, and retention in the Koolbase dashboard.
```typescript
await Koolbase.initialize({
publicKey: 'pk_live_xxxx',
baseUrl: 'https://api.koolbase.com',
analyticsEnabled: true,
appVersion: '1.0.0',
});
// Custom events
Koolbase.analytics.track('purchase', { value: 1200, currency: 'GHS' });
// Screen views
Koolbase.analytics.screenView('checkout');
// User identity
Koolbase.analytics.identify(user.id);
Koolbase.analytics.setUserProperty('plan', 'pro');
// On logout
Koolbase.analytics.reset();
```
---
## Cloud Messaging
```typescript
await Koolbase.initialize({
publicKey: 'pk_live_xxxx',
baseUrl: 'https://api.koolbase.com',
messagingEnabled: true,
});
// Register FCM token (after obtaining from @react-native-firebase/messaging)
const fcmToken = await messaging().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' },
});
```
---
## Error handling
Koolbase throws typed errors selected from the server's stable error `code`, so
handling doesn't depend on message text.
### Database errors
All data-layer failures extend `KoolbaseDataError` (which extends `Error`):
| Error | When |
| `KoolbaseStorageConflictError` | An upload targets a path that's already taken and `overwrite: false` (409, code `PATH_CONFLICT`). Exposes `.path` — the colliding path. |
| `KoolbaseStorageNotFoundError` | The bucket or object doesn't exist (404). |
| `KoolbaseStorageValidationError` | The request was rejected as invalid — bad path, missing field (400). |
| `KoolbaseStoragePermissionError` | The caller is not allowed to perform the operation (403). |
| `KoolbaseStorageQuotaError` | An upload would push the bucket past its `max_size_bytes` cap (409, code `QUOTA_EXCEEDED`). |
| `KoolbaseStorageFileTooLargeError` | A single file exceeds the bucket's `max_file_size_bytes` cap (413, code `FILE_TOO_LARGE`). |
| `KoolbaseStorageMimeTypeError` | The upload's content-type isn't in the bucket's `allowed_mime_types` allowlist (415, code `MIME_NOT_ALLOWED`). |
```ts
import { KoolbaseConflictError, KoolbaseDataError } from '@techfinityedge/koolbase-react-native';
try {
await Koolbase.db.upsert('users', { email }, { name });
} catch (e) {
if (e instanceof KoolbaseConflictError) {
showError(`That ${e.field ?? 'value'} is already taken.`);
} else if (e instanceof KoolbaseDataError) {
showError(e.message);
}
}
```
> `query`, `get`, `upsert`, and `deleteWhere` throw these typed errors. `insert`,
> `update`, and `delete` are optimistic/offline-first — they queue and sync in
> the background, so their conflicts surface via the sync engine, not as a
> thrown error.
---
### Storage errors
All storage failures extend `KoolbaseStorageError` (which extends `Error`):
| Error | When |
|---|---|
| `KoolbaseStorageConflictError` | An upload targets a path that's already taken and `overwrite: false` (409, code `PATH_CONFLICT`). Exposes `.path` — the colliding path. |
| `KoolbaseStorageNotFoundError` | The bucket or object doesn't exist (404). |
| `KoolbaseStorageValidationError` | The request was rejected as invalid — bad path, missing field (400). |
| `KoolbaseStoragePermissionError` | The caller is not allowed to perform the operation (403). |
```ts
import {
KoolbaseStorageConflictError,
KoolbaseStorageError,
KoolbaseStoragePermissionError,
} from '@techfinityedge/koolbase-react-native';
try {
await Koolbase.storage.upload({
bucket: 'avatars',
path: 'me.png',
file: { uri, name: 'me.png', type: 'image/png' },
});
} catch (e) {
if (e instanceof KoolbaseStorageConflictError) {
// Already exists — prompt user to confirm overwrite
promptOverwrite(e.path);
} else if (e instanceof KoolbaseStoragePermissionError) {
showError('You do not have permission to upload here.');
} else if (e instanceof KoolbaseStorageError) {
// Catch-all for any other storage error
showError(e.message);
} else {
throw e;
}
}
```
---
## What's included
- Authentication: email + password, Apple Sign-In, Google Sign-In, phone + OTP
- Database with offline-first cache, realtime subscriptions, and populate
- Storage with presigned uploads and downloads, safe-by-default conflict handling
- Realtime subscriptions over WebSocket
- Authenticated functions (`ctx.auth` exposes the caller automatically)
- Feature flags and remote config
- Version enforcement
- Code push (config + flag overrides + directives, no store release)
- Logic engine (conditional flows as data, updatable OTA)
- Analytics (DAU/WAU/MAU, funnels, retention)
- Cloud Messaging (FCM token registration, targeted send, broadcast)
- TypeScript-native with full type definitions
---
## Documentation
Full documentation at [docs.koolbase.com](https://docs.koolbase.com)
## Dashboard
Manage your projects at [app.koolbase.com](https://app.koolbase.com)
## Support
- [GitHub Issues](https://github.com/kennedyowusu/koolbase-react-native/issues)
- [docs.koolbase.com](https://docs.koolbase.com)
- Email:
## License
MIT