next-shared-state
Version:
Enhanced state sharing for Next.js with IndexedDB persistence and URL data transfer between pages and components.
189 lines (188 loc) • 7.33 kB
JavaScript
export class EnhancedIndexedDB {
constructor() {
this.dbName = "NextSharedStore";
this.version = 1;
this.db = null;
this.initPromise = null;
}
async init() {
if (this.db)
return this.db;
if (this.initPromise)
return this.initPromise;
this.initPromise = new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => {
this.initPromise = null;
reject(request.error);
};
request.onsuccess = () => {
this.db = request.result;
this.initPromise = null;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("sharedData")) {
const store = db.createObjectStore("sharedData", { keyPath: "key" });
store.createIndex("timestamp", "timestamp");
store.createIndex("expires", "expires");
store.createIndex("category", "category");
}
};
});
return this.initPromise;
}
async set(key, value, options = {}) {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readwrite");
const store = transaction.objectStore("sharedData");
const record = {
key,
value,
timestamp: Date.now(),
category: options.category || "default",
expires: options.ttl ? Date.now() + options.ttl : null,
};
const request = store.put(record);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
this.cleanExpired().catch(() => { });
});
}
async get(key) {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readonly");
const store = transaction.objectStore("sharedData");
const request = store.get(key);
request.onsuccess = () => {
const result = request.result;
if (!result) {
resolve(null);
return;
}
if (result.expires && Date.now() > result.expires) {
this.remove(key);
resolve(null);
return;
}
resolve(result.value);
};
request.onerror = () => reject(request.error);
});
}
async getMany(keys) {
const results = {};
const promises = keys.map(async (key) => {
const value = await this.get(key);
if (value !== null) {
results[key] = value;
}
});
await Promise.all(promises);
return results;
}
async remove(key) {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readwrite");
const store = transaction.objectStore("sharedData");
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async clear(category) {
const db = await this.init();
if (category) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readwrite");
const store = transaction.objectStore("sharedData");
const index = store.index("category");
const request = index.openCursor(IDBKeyRange.only(category));
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
else {
resolve();
}
};
request.onerror = () => reject(request.error);
});
}
else {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readwrite");
const store = transaction.objectStore("sharedData");
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
async getKeys() {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readonly");
const store = transaction.objectStore("sharedData");
const request = store.getAllKeys();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async cleanExpired() {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readwrite");
const store = transaction.objectStore("sharedData");
const index = store.index("expires");
const range = IDBKeyRange.upperBound(Date.now());
const request = index.openCursor(range);
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
cursor.delete();
cursor.continue();
}
else {
resolve();
}
};
request.onerror = () => reject(request.error);
});
}
async exportData() {
const db = await this.init();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["sharedData"], "readonly");
const store = transaction.objectStore("sharedData");
const request = store.getAll();
request.onsuccess = () => {
const data = request.result
.filter((record) => !record.expires || Date.now() <= record.expires)
.map((record) => ({
key: record.key,
value: record.value,
category: record.category,
ttl: record.expires ? record.expires - Date.now() : null,
}));
resolve(JSON.stringify(data));
};
request.onerror = () => reject(request.error);
});
}
async importData(jsonData) {
const data = JSON.parse(jsonData);
const importPromises = data.map((item) => this.set(item.key, item.value, {
ttl: item.ttl || undefined,
category: item.category,
}));
await Promise.all(importPromises);
}
}
export const enhancedIndexedDB = new EnhancedIndexedDB();