π Getting Started with Supastash
Supastash helps you build offline-first apps by syncing local SQLite with Supabase β all in the background. Whether you're building a point-of-sale, chat, delivery, or CRM app, Supastash gives you control, performance, and reliability even when users are offline.
This guide walks you through setting it up from scratch.
π¦ Installationβ
1. Install Supastashβ
npm install supastash
2. Install Required Peer Dependenciesβ
These must be installed manually:
npm install @supabase/supabase-js \
@react-native-community/netinfo \
react \
react-native
3. Choose ONE SQLite Adapterβ
Pick the adapter based on your project setup:
# For Expo projects
npm install expo-sqlite
# For bare React Native (better performance)
npm install react-native-nitro-sqlite
# Classic RN SQLite adapter
npm install react-native-sqlite-storage
βοΈ Project Setupβ
1. Configure Supastashβ
Set this up early β e.g., lib/supastash.ts
import { configureSupastash, defineLocalSchema } from "supastash";
import { supabase } from "./supabase";
import { openDatabaseAsync } from "expo-sqlite"; // or your adapter
configureSupastash({
supabaseClient: supabase,
dbName: "supastash_db",
sqliteClient: { openDatabaseAsync },
sqliteClientType: "expo", // "rn-nitro" or "rn-storage"
onSchemaInit: () => {
defineLocalSchema("users", {
id: "TEXT PRIMARY KEY",
name: "TEXT",
email: "TEXT",
created_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
updated_at: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP",
__indices: ["email", "user_id"],
});
},
debugMode: true,
syncEngine: {
push: true,
pull: false, // Enable if you're using RLS and filters
},
excludeTables: {
push: ["daily_reminders"],
pull: ["daily_reminders"],
},
});
2. Initialize Once in Your Appβ
// App.tsx or _layout.tsx
import "@/lib/supastash"; // Triggers initialization
export default function App() {
return <Stack />; // or your app shell
}
π‘οΈ Server-Side Setup (for Filtered Pulls)β
To enable safe, filtered data pulling from Supabase, run this SQL function:
create or replace function get_table_schema(table_name text)
returns table(column_name text, data_type text, is_nullable text)
security definer
as $$
select column_name, data_type, is_nullable
from information_schema.columns
where table_schema = 'public' and table_name = $1;
$$ language sql;
grant execute on function get_table_schema(text) to anon, authenticated;
β
Required if using Row Level Security (RLS)
β οΈ Make sure all sync-related timestamps (created_at
, updated_at
, deleted_at
) use timestamptz
β not plain timestamp
.
βοΈ Wait for Initialization Before Renderingβ
import { useSupatash } from "supastash";
const { dbReady } = useSupatash();
if (!dbReady) return null;
return <AppRoutes />;
π‘ Data Fetching Optionsβ
Supastash gives you two main hooks for fetching and syncing local data:
π§ useSupastashData
β Full Sync, Realtime-Awareβ
const { data, dataMap, groupedBy } = useSupastashData("orders", {
filter: { column: "user_id", operator: "eq", value: userId },
extraMapKeys: ["status"],
});
- Syncs with Supabase in realtime
- Uses global cache
- Automatically keeps state updated across screens
- Best for dashboards, shared state, live data
π§© Dynamic Filtering (All Hooks)β
Supastash lets you filter synced data based on user, shop, etc.
const { data } = useSupastashData("orders", {
filter: {
column: "user_id",
operator: "eq",
value: currentUserId,
}, // RealtimeFilter
sqlFilter: [{ column: "user_id", operator: "eq", value: currentUserId }], //sql (optional)
});
π‘
filter
= for Supabase realtime π‘sqlFilter
= for actual query filtering
π‘οΈ Registering Table Filters: useSupastashFilters
β
If youβre pulling data (i.e., using pull: true
in configureSupastash
), always call this hook at startup:
useSupastashFilters({
orders: [{ column: "shop_id", operator: "eq", value: activeShopId }],
inventory: [
{ column: "location_id", operator: "eq", value: selectedLocation },
],
});
- Ensures only scoped rows are pulled from Supabase
- Prevents unnecessary or insecure full-table syncs
- Validates your filters and warns you if anythingβs wrong
π One-Off Queries with Supabaseβ
Use Supastash's built-in wrapper for direct Supabase access:
import { supastash } from "supastash";
const { data, error } = await supastash.from("orders").select("*").run();
It works just like supabase.from(...)
, but ensures it respects your Supastash config.
π§ Debuggingβ
Enable debugMode: true
to log sync events, retries, or failures:
configureSupastash({
...,
debugMode: true,
});
β Next Stepsβ
Thatβs it β you're now ready to build offline-first, scalable, and Supabase-powered apps using Supastash. Whether you're going full realtime or keeping it lite, youβre in control.