sveltekit-sync
Version:
Local-first sync engine for SvelteKit
109 lines (108 loc) • 3.55 kB
JavaScript
import { openDB } from 'idb';
import { browser } from '$app/environment';
export class IDBAdapter {
db = null;
dbName;
version;
constructor(dbName = 'sync-db', version = 1) {
this.dbName = dbName;
this.version = version;
if (!this.db && browser)
this.init();
}
ensureInitialized() {
if (!this.db) {
throw new Error('Database not initialized. Call adapter.init() first.');
}
}
async init(schema) {
if (this.db)
return; // Already initialized
this.db = await openDB(this.dbName, this.version, {
upgrade(db) {
// Create default stores
if (!db.objectStoreNames.contains('sync_queue')) {
db.createObjectStore('sync_queue', { keyPath: 'id' });
}
if (!db.objectStoreNames.contains('sync_meta')) {
db.createObjectStore('sync_meta', { keyPath: 'key' });
}
// Create user-defined stores
if (schema) {
Object.keys(schema).forEach(table => {
if (!db.objectStoreNames.contains(table)) {
db.createObjectStore(table, { keyPath: 'id' });
}
});
}
},
});
}
async insert(table, data) {
if (!this.db)
throw new Error('Database not initialized');
await this.db.add(table, data);
return data;
}
async update(table, id, data) {
if (!this.db)
throw new Error('Database not initialized');
const updated = { ...data, id };
await this.db.put(table, updated);
return updated;
}
async delete(table, id) {
if (!this.db)
throw new Error('Database not initialized');
await this.db.delete(table, id);
}
async find(table, query) {
if (!this.db)
throw new Error('Database not initialized');
return this.db.getAll(table);
}
async findOne(table, id) {
if (!this.db)
throw new Error('Database not initialized');
const result = await this.db.get(table, id);
return result || null;
}
async addToQueue(op) {
await this.insert('sync_queue', op);
}
async getQueue() {
return this.find('sync_queue');
}
async removeFromQueue(ids) {
if (!this.db)
throw new Error('Database not initialized');
const tx = this.db.transaction('sync_queue', 'readwrite');
await Promise.all(ids.map(id => tx.store.delete(id)));
await tx.done;
}
async updateQueueStatus(id, status, error) {
const op = await this.findOne('sync_queue', id);
if (op) {
await this.update('sync_queue', id, { ...op, status, error });
}
}
async getLastSync() {
const meta = await this.findOne('sync_meta', 'lastSync');
return meta?.value || 0;
}
async setLastSync(timestamp) {
await this.update('sync_meta', 'lastSync', {
key: 'lastSync',
value: timestamp,
});
}
async getClientId() {
let meta = await this.findOne('sync_meta', 'clientId');
if (!meta) {
const clientId = crypto.randomUUID();
await this.insert('sync_meta', { key: 'clientId', value: clientId });
return clientId;
}
return meta.value;
}
}