UNPKG

supastash

Version:

Offline-first sync engine for Supabase in React Native using SQLite

85 lines (84 loc) 3.37 kB
import { useEffect } from "react"; import { filterTracker, tableFilters, tableFiltersUsed, } from "../../store/tableFilters"; import { logWarn } from "../../utils/logs"; import isValidFilter, { warnOnMisMatch, } from "../../utils/sync/pullFromRemote/validateFilters"; import { checkIfTableExist } from "../../utils/tableValidator"; /** * useSupastashFilters * * @description * Registers filters used for Supastash pull sync logic. * * This hook should be called at app startup or anywhere your filter configuration might change. * You should provide filters for **all tables** unless your Supabase project uses strict SELECT RLS (Row Level Security) policies. * * The hook validates filter structure, warns about invalid filters, and detects any mismatch in column/operator structure * to prevent sync inconsistencies. * * 🚨 Incorrect or mismatched filters can lead to unreliable syncing behavior. * * @example * ```ts * useSupastashFilters({ * orders: [ * { column: "shop_id", operator: "eq", value: currentShopId }, * { column: "status", operator: "in", value: ["pending", "completed"] }, * ], * inventory: [ * { column: "location_id", operator: "eq", value: activeLocationId }, * ], * }); * ``` * * @param {SupastashFilter} filters - An object where each key is a table name, and its value is * an array of `RealtimeFilter` objects that define the filter criteria for that table's pull sync. * * @note This hook does not re-run unless the `filters` object reference changes. * To force re-evaluation, pass a fresh object (not just mutated data). */ export function useSupastashFilters(filters) { useEffect(() => { let cancelled = false; async function run() { if (!filters) return; const incoming = Object.keys(filters); if (!incoming.length) return; // Remove stale tables for (const t of Array.from(tableFilters.keys()) || []) { if (!incoming.includes(t)) { tableFilters.delete(t); tableFiltersUsed.delete(t); filterTracker.delete(t); } } // Existence check + per-table registration const existence = await Promise.all(incoming.map(async (t) => [t, await checkIfTableExist(t)])); if (cancelled) return; for (const [table, exists] of existence) { if (!exists) { logWarn(`Table '${table}' does not exist; skipping filters`); continue; } const raw = filters[table] ?? []; const valid = raw.filter((f) => isValidFilter([f])); if (!valid.length) { tableFilters.delete(table); tableFiltersUsed.delete(table); filterTracker.delete(table); continue; } // Warn on signature change and store a cloned array warnOnMisMatch(table, valid); tableFilters.set(table, valid.map((v) => ({ ...v }))); tableFiltersUsed.add(table); } } void run(); return () => { cancelled = true; }; }, [filters]); }