sveltekit-sync
Version:
Local-first sync engine for SvelteKit
122 lines (121 loc) • 4.73 kB
TypeScript
export type SyncStatus = 'idle' | 'syncing' | 'error' | 'conflict' | 'offline';
export interface SyncOperation<T = any> {
id: string;
table: string;
operation: 'insert' | 'update' | 'delete';
data: T;
timestamp: number;
clientId: string;
version: number;
status: 'pending' | 'synced' | 'error';
error?: string;
userId?: string;
}
export interface SyncResult {
success: boolean;
synced: string[];
conflicts: Conflict[];
errors: Array<{
id: string;
error: string;
}>;
}
export interface Conflict<T = any> {
operation: SyncOperation<T>;
serverData: T;
clientData: T;
resolution?: 'client-wins' | 'server-wins' | 'merged';
}
export interface SyncConfig<TLocalDB = any, TRemoteDB = any> {
local: {
db: TLocalDB;
adapter: LocalAdapter<TLocalDB>;
};
remote: {
push: (ops: SyncOperation[]) => Promise<SyncResult>;
pull: (lastSync: number, clientId: string) => Promise<SyncOperation[]>;
resolve?: (conflict: Conflict) => Promise<SyncOperation>;
};
syncInterval?: number;
batchSize?: number;
conflictResolution?: 'client-wins' | 'server-wins' | 'manual' | 'last-write-wins';
retryAttempts?: number;
retryDelay?: number;
realtime?: RealtimeClientConfig;
onSync?: (status: SyncStatus) => void;
onConflict?: (conflict: Conflict) => void;
onError?: (error: Error) => void;
}
export interface ClientState {
clientId: string;
userId: string;
lastSync: Date;
lastActive: Date;
}
export interface ServerAdapter<TDB = any> {
insert(table: string, data: any): Promise<any>;
update(table: string, id: string, data: any, version: number): Promise<any>;
delete(table: string, id: string): Promise<void>;
findOne(table: string, id: string): Promise<any | null>;
find(table: string, filter?: QueryFilter): Promise<any[]>;
getChangesSince(table: string, timestamp: number, userId?: string, excludeClientId?: string): Promise<SyncOperation[]>;
applyOperation(op: SyncOperation, userId?: string): Promise<void>;
batchInsert(table: string, records: any[]): Promise<any[]>;
batchUpdate(table: string, updates: Array<{
id: string;
data: any;
}>): Promise<any[]>;
checkConflict(table: string, id: string, expectedVersion: number): Promise<boolean>;
logSyncOperation(op: SyncOperation, userId: string): Promise<void>;
updateClientState(clientId: string, userId: string): Promise<void>;
getClientState(clientId: string): Promise<ClientState | null>;
subscribe?(tables: string[], userId: string, callback: (ops: SyncOperation[]) => void): Promise<() => void>;
transaction?<T>(fn: (adapter: ServerAdapter<TDB>) => Promise<T>): Promise<T>;
}
export interface QueryFilter {
where?: Record<string, any>;
orderBy?: {
field: string;
direction: 'asc' | 'desc';
}[];
limit?: number;
offset?: number;
}
export interface ClientAdapter<TDB = any> {
insert(table: string, data: any): Promise<any>;
update(table: string, id: string, data: any): Promise<any>;
delete(table: string, id: string): Promise<void>;
find(table: string, query?: any): Promise<any[]>;
findOne(table: string, id: string): Promise<any | null>;
addToQueue(op: SyncOperation): Promise<void>;
getQueue(): Promise<SyncOperation[]>;
removeFromQueue(ids: string[]): Promise<void>;
updateQueueStatus(id: string, status: SyncOperation['status'], error?: string): Promise<void>;
getLastSync(): Promise<number>;
setLastSync(timestamp: number): Promise<void>;
getClientId(): Promise<string>;
batchInsert?(table: string, records: any[]): Promise<any[]>;
batchDelete?(table: string, ids: string[]): Promise<void>;
clear?(): Promise<void>;
}
export interface LocalAdapter<TDB = any> extends ClientAdapter<TDB> {
isInitialized(): Promise<boolean>;
setInitialized(value: boolean): Promise<void>;
}
export type RealtimeStatus = 'connected' | 'connecting' | 'disconnected' | 'fallback';
export interface RealtimeClientConfig {
/** Enable realtime sync (default: true) */
enabled?: boolean;
/** SSE endpoint URL (default: '/api/sync/realtime') */
endpoint?: string;
/** Tables to subscribe to (default: [] = all tables) */
tables?: string[];
/** Reconnect interval in ms (default: 1000) */
reconnectInterval?: number;
/** Maximum reconnect interval in ms (default: 30000) */
maxReconnectInterval?: number;
/** Max reconnect attempts before fallback to polling (default: 5) */
maxReconnectAttempts?: number;
/** Heartbeat timeout in ms (default: 45000) */
heartbeatTimeout?: number;
}