zeno-db
Version:
A lightweight, offline-first client-side database with automatic sync capabilities
225 lines (224 loc) • 9.03 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexedDBAdapter = void 0;
class IndexedDBAdapter {
constructor(config) {
this.db = null;
this.subscribers = [];
this.changes = [];
this.dbName = config.name || 'zenodb';
this.storeName = config.storeName || 'store';
this.version = config.version || 1;
}
init() {
return __awaiter(this, void 0, void 0, function* () {
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;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
});
});
}
get(key) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
});
}
set(key, value) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(value, key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.notifySubscribers(key, value);
this.queueChange({
key,
value,
timestamp: Date.now(),
clientId: 'local',
operation: 'set'
});
resolve();
};
});
});
}
delete(key) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.delete(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.notifySubscribers(key, undefined);
this.queueChange({
key,
value: undefined,
timestamp: Date.now(),
clientId: 'local',
operation: 'delete'
});
resolve();
};
});
});
}
keys() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!this.db) {
reject(new Error('Database not initialized'));
return;
}
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.getAllKeys();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
});
}
getAll() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.db) {
throw new Error('Database not initialized');
}
const db = this.db; // Create a local reference to avoid null checks
return new Promise((resolve, reject) => {
const request = db.transaction(this.storeName, 'readonly')
.objectStore(this.storeName)
.getAll();
request.onsuccess = () => {
const result = {};
request.result.forEach((item) => {
result[item.key] = item.value;
});
resolve(result);
};
request.onerror = () => {
reject(request.error);
};
});
});
}
getMany(keys) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.db) {
throw new Error('Database not initialized');
}
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const results = [];
let completed = 0;
keys.forEach((key, idx) => {
const request = store.get(key);
request.onsuccess = () => {
results[idx] = request.result;
completed++;
if (completed === keys.length) {
resolve(results);
}
};
request.onerror = () => {
results[idx] = undefined;
completed++;
if (completed === keys.length) {
resolve(results);
}
};
});
});
});
}
setMany(items) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.db) {
throw new Error('Database not initialized');
}
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
let completed = 0;
items.forEach((item) => {
const request = store.put(item, item.id);
request.onsuccess = () => {
this.notifySubscribers(item.id, item);
this.queueChange({
key: item.id,
value: item,
timestamp: Date.now(),
clientId: 'local',
operation: 'set',
});
completed++;
if (completed === items.length) {
resolve();
}
};
request.onerror = () => {
completed++;
if (completed === items.length) {
resolve(); // Ignore errors for now, could be improved
}
};
});
});
});
}
subscribe(callback) {
this.subscribers.push(callback);
}
notifySubscribers(key, value) {
this.subscribers.forEach(callback => callback(key, value));
}
getChanges() {
return this.changes;
}
clearChanges() {
this.changes = [];
}
queueChange(change) {
this.changes.push(change);
}
}
exports.IndexedDBAdapter = IndexedDBAdapter;