π Getting Started with Supastash
Supastash helps you build offline-first apps by syncing local SQLite data with Supabase in the background. This guide walks you through setting it up from scratch.
π¦ Installationβ
1. Install Supastashβ
npm install supastash
2. Install Required Peer Dependenciesβ
These are required and must be installed manually:
npm install @supabase/supabase-js \
@react-native-community/netinfo \
react \
react-native
3. Choose a SQLite Adapter (Only ONE)β
Choose based on your project type:
# For Expo projects
npm install expo-sqlite
# For better performance in bare React Native
npm install react-native-nitro-sqlite
# Or use the classic SQLite option
npm install react-native-sqlite-storage
βοΈ Project Setupβ
1. Create the Supastash Configβ
Setup early in your app β e.g., lib/supastash.ts
import { configureSupastash, defineLocalSchema } from "supastash";
import { supabase } from "./supabase";
import { openDatabaseAsync } from "expo-sqlite"; // or nitro/sqlite-storage client
configureSupastash({
supabaseClient: supabase,
dbName: "supastash_db",
sqliteClient: { openDatabaseAsync },
sqliteClientType: "expo", // or "rn-nitro" / "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",
});
},
debugMode: true,
syncEngine: {
push: true,
pull: false, // Enable if using RLS and want to pull filtered data
},
excludeTables: {
push: ["daily_reminders"],
pull: ["daily_reminders"],
},
});
2. Initialize It Once (in your main layout)β
// App.tsx or _layout.tsx
import "@/lib/supastash"; // Just import to initialize
export default function App() {
return <Stack />; // or your main app entry
}
π‘οΈ Server-Side Setup for RLSβ
Supastash needs access to your table schema. Run this SQL function in the Supabase SQL Editor:
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;
β This works with Row-Level Security (RLS). Without this, Supastash can't sync filtered data.
Important Timestamp Ruleβ
All timestamp columns used for syncing β like created_at
, updated_at
, deleted_at
β must be timestamptz
(timestamp with time zone).
This prevents timezone issues and ensures reliable sync.
βοΈ Bootstrapping the Sync Engineβ
Before rendering your app, make sure the Supastash engine is ready:
// App.tsx or _layout.tsx
import { useSupatash } from "supastash";
const { dbReady } = useSupatash();
if (!dbReady) return null;
return <Stack />;
π§ͺ Basic Hook Usage: useSupatashData
β
This hook gives you live local-first data access.
import { useSupatashData } from "supastash";
type Order = {
id: string;
user_id: string;
deleted_at: string | null;
updated_at: string;
created_at: string;
};
const {
data: orders,
dataMap: ordersMap,
groupedBy,
} = useSupatashData<Order>("orders", { extraMapKeys: ["user_id"] });
You get:
data
β An array of rowsdataMap
β A map keyed byid
for fast lookup
Supastash keeps this in sync with SQLite and Supabase.
π§© extraMapKeys
: Smarter Derived Maps β for Freeβ
Need to group, lookup, or filter your data by a specific column?
Pass any field(s) into extraMapKeys
, and Supastash will automatically generate map structures for you β efficiently and in the background.
const { dataMap, groupedBy } = useSupatashData("orders", {
extraMapKeys: ["user_id", "status"],
});
You get:
dataMap
: Fast lookup byid
groupedBy.user_id
: Grouped orders byuser_id
groupedBy.status
: Grouped orders by status
β
No need to reduce()
or create memoized maps manually
β‘οΈ This is optimized to run in sync with the rest of your hookβs data processing β zero wasted renders.
π‘ Use this when rendering lists by user, status, etc.
π With Filteringβ
Only fetch rows for a specific user:
import { useSupatashData } from "supastash";
const { userId } = useAuth();
const { data: userOrders } = useSupatashData("orders", {
filter: {
column: "user_id",
operator: "eq",
value: userId,
},
shouldFetch: !!userId,
});
This ensures you donβt load data until the userId
is available.
π§ Querying Supabase Directlyβ
Use supastash.from()
for one-off server queries (like supabase.from(...)
but integrated with Supastash):
import { supastash } from "supastash";
useEffect(() => {
const fetchOrders = async () => {
const { data, error } = await supastash.from("orders").select("*").run();
if (error) console.error(error);
else setOrders(data);
};
fetchOrders();
}, []);
π Monitoring & Debuggingβ
Enable debugMode: true
in configureSupastash()
to log:
- When sync starts or fails
- Which rows are retried
- Offline batching behavior
configureSupastash({
...
debugMode: true,
});
This will help during dev to know when things arenβt syncing β especially useful when testing offline.
π§ Next Stepsβ
- Configuration Guide
- Data Access Hook (
useSupatashData
) - Sync Engine Setup (
useSupatash
) - Supastash Query Builder
Thatβs it! Youβre now set up to build offline-first, realtime-ready apps with Supastash.