@tanstack/offline-transactions
Version:
Offline-first transaction capabilities for TanStack DB
144 lines (143 loc) • 4.51 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const StorageAdapter = require("./StorageAdapter.cjs");
class IndexedDBAdapter extends StorageAdapter.BaseStorageAdapter {
constructor(dbName = `offline-transactions`, storeName = `transactions`) {
super();
this.db = null;
this.dbName = dbName;
this.storeName = storeName;
}
/**
* Probe IndexedDB availability by attempting to open a test database.
* This catches private mode and other restrictions that block IndexedDB.
*/
static async probe() {
if (typeof indexedDB === `undefined`) {
return {
available: false,
error: new Error(`IndexedDB is not available in this environment`)
};
}
try {
const testDbName = `__offline-tx-probe__`;
const request = indexedDB.open(testDbName, 1);
return new Promise((resolve) => {
request.onerror = () => {
const error = request.error || new Error(`IndexedDB open failed`);
resolve({ available: false, error });
};
request.onsuccess = () => {
const db = request.result;
db.close();
indexedDB.deleteDatabase(testDbName);
resolve({ available: true });
};
request.onblocked = () => {
resolve({
available: false,
error: new Error(`IndexedDB is blocked`)
});
};
});
} catch (error) {
return {
available: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
}
async openDB() {
if (this.db) {
return this.db;
}
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
});
}
async getStore(mode = `readonly`) {
const db = await this.openDB();
const transaction = db.transaction([this.storeName], mode);
return transaction.objectStore(this.storeName);
}
async get(key) {
try {
const store = await this.getStore(`readonly`);
return new Promise((resolve, reject) => {
const request = store.get(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result ?? null);
});
} catch (error) {
console.warn(`IndexedDB get failed:`, error);
return null;
}
}
async set(key, value) {
try {
const store = await this.getStore(`readwrite`);
return new Promise((resolve, reject) => {
const request = store.put(value, key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
} catch (error) {
if (error instanceof DOMException && error.name === `QuotaExceededError`) {
throw new Error(
`Storage quota exceeded. Consider clearing old transactions.`
);
}
throw error;
}
}
async delete(key) {
try {
const store = await this.getStore(`readwrite`);
return new Promise((resolve, reject) => {
const request = store.delete(key);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
} catch (error) {
console.warn(`IndexedDB delete failed:`, error);
}
}
async keys() {
try {
const store = await this.getStore(`readonly`);
return new Promise((resolve, reject) => {
const request = store.getAllKeys();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
} catch (error) {
console.warn(`IndexedDB keys failed:`, error);
return [];
}
}
async clear() {
try {
const store = await this.getStore(`readwrite`);
return new Promise((resolve, reject) => {
const request = store.clear();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve();
});
} catch (error) {
console.warn(`IndexedDB clear failed:`, error);
}
}
}
exports.IndexedDBAdapter = IndexedDBAdapter;
//# sourceMappingURL=IndexedDBAdapter.cjs.map