sveltekit-sync
Version:
Local-first sync engine for SvelteKit
141 lines (140 loc) • 5.08 kB
JavaScript
export class IndexedDBAdapter {
db = null;
dbName;
version;
constructor(dbName = 'sync-db', version = 1) {
this.dbName = dbName;
this.version = version;
}
ensureInitialized() {
if (!this.db) {
throw new Error('Database not initialized. Call adapter.init() first.');
}
}
async init(schema) {
if (this.db)
return; // Already initialized
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 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' });
}
});
}
};
});
}
getStore(table, mode = 'readonly') {
this.ensureInitialized();
return this.db.transaction(table, mode).objectStore(table);
}
async insert(table, data) {
const store = this.getStore(table, 'readwrite');
return new Promise((resolve, reject) => {
const request = store.add(data);
request.onsuccess = () => resolve(data);
request.onerror = () => reject(request.error);
});
}
async update(table, id, data) {
const store = this.getStore(table, 'readwrite');
return new Promise((resolve, reject) => {
const request = store.put({ ...data, id });
request.onsuccess = () => resolve(data);
request.onerror = () => reject(request.error);
});
}
async delete(table, id) {
const store = this.getStore(table, 'readwrite');
return new Promise((resolve, reject) => {
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async find(table, query) {
const store = this.getStore(table);
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async findOne(table, id) {
const store = this.getStore(table);
return new Promise((resolve, reject) => {
const request = store.get(id);
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
}
async addToQueue(op) {
await this.insert('sync_queue', op);
}
async getQueue() {
return this.find('sync_queue');
}
async removeFromQueue(ids) {
for (const id of ids) {
await this.delete('sync_queue', id);
}
}
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) {
const existing = await this.findOne('sync_meta', 'lastSync');
if (existing) {
await this.update('sync_meta', 'lastSync', { key: 'lastSync', value: timestamp });
}
else {
await this.insert('sync_meta', { 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;
}
async isInitialized() {
const meta = await this.findOne('sync_meta', 'isInitialized');
return meta?.value || false;
}
async setInitialized(value) {
const existing = await this.findOne('sync_meta', 'isInitialized');
if (existing) {
await this.update('sync_meta', 'isInitialized', { key: 'isInitialized', value });
}
else {
await this.insert('sync_meta', { key: 'isInitialized', value });
}
}
}