supastash
Version:
Offline-first sync engine for Supabase in React Native using SQLite
105 lines (104 loc) • 3.9 kB
JavaScript
import { getSupastashConfig } from "../../../core/config";
import { syncCalls } from "../../../store/syncCalls";
import { isOnline } from "../../../utils/connection";
import log from "../../../utils/logs";
import { getAllTables } from "../../../utils/sync/getAllTables";
import { runLimitedConcurrency } from "../../../utils/sync/pullFromRemote/runLimitedConcurrency";
import { pushLocalDataToRemote } from "../../../utils/sync/pushLocal/sendUnsyncedToSupabase";
import { SyncInfoUpdater } from "../../../utils/sync/queryStatus";
let emptyPassCount = 0;
let lastEmptyPassAt = 0;
const tablePushLock = new Map();
/**
* Pushes the local data to the remote database
*/
export async function pushLocalData() {
let tablesCompleted = 0;
let numberOfTables = 0;
try {
const tables = await getAllTables();
if (!tables) {
log("[Supastash] No tables found");
return;
}
if (!(await isOnline()))
return;
const excludeTables = getSupastashConfig()?.excludeTables?.push || [];
const tablesToPush = tables.filter((table) => !excludeTables?.includes(table));
numberOfTables = tablesToPush.length;
SyncInfoUpdater.setInProgress({
action: "start",
type: "push",
});
SyncInfoUpdater.setNumberOfTables({
amount: numberOfTables,
type: "push",
});
const results = [];
const jobs = tablesToPush.map((table) => async () => {
SyncInfoUpdater.setCurrentTable({
table,
type: "push",
});
if (tablePushLock.get(table)) {
results.push({ table, hadWork: false });
return;
}
tablePushLock.set(table, true);
try {
const onPush = syncCalls.get(table)?.push;
SyncInfoUpdater.markLogStart({
type: "push",
table,
});
const hadWork = await pushLocalDataToRemote(table, onPush);
results.push({ table, hadWork: !!hadWork });
SyncInfoUpdater.markLogSuccess({
type: "push",
table,
});
}
catch (e) {
const msg = e?.code ?? e?.name ?? String(e);
SyncInfoUpdater.markLogError({
type: "push",
table,
lastError: e,
errorCount: 1,
});
results.push({ table, hadWork: false, error: msg });
log(`[Supastash] Push table failed: ${table} — ${msg}`);
}
finally {
tablesCompleted++;
SyncInfoUpdater.setTablesCompleted({
amount: tablesCompleted,
type: "push",
});
tablePushLock.set(table, false);
}
});
await runLimitedConcurrency(jobs, 3);
const hadAnyWork = results.some((r) => r.hadWork);
if (!hadAnyWork) {
emptyPassCount += 1;
if (emptyPassCount % 150 === 0) {
const now = Date.now();
const gap = lastEmptyPassAt ? now - lastEmptyPassAt : 0;
lastEmptyPassAt = now;
const noSyncTables = results.map((r) => r.table).join(", ");
log(`[Supastash] No pushable data for: ${noSyncTables} (empty passes: ${emptyPassCount})${gap ? ` in the last ${gap}ms` : ""}`);
}
}
else {
emptyPassCount = 0;
lastEmptyPassAt = Date.now();
}
}
catch (error) {
log(`[Supastash] Error pushing local data to remote database: ${error}`);
}
finally {
SyncInfoUpdater.reset({ type: "push" });
}
}