use-idb-store
Version:
A React state hook that syncs state to IndexedDB for persistent, offline-friendly, and large-scale data storage.
930 lines (915 loc) • 44.2 kB
JavaScript
'use strict';
var react = require('react');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(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());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* Converts an IndexedDB request into a Promise
* @template T The type of the result that will be returned by the IndexedDB request
* @param request The IndexedDB request to convert to a Promise
* @returns A Promise that resolves with the request result or rejects with an error message
*/
function idbRequestToPromise(request) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve, reject) {
request.addEventListener("success", function () {
resolve(request.result);
});
request.addEventListener("error", function (event) {
// Extract more detailed error information
var target = event.target;
var error = target.error;
if (error) {
var detailedError = new Error("IndexedDB Error: ".concat(error.name, " - ").concat(error.message));
detailedError.originalError = error;
detailedError.event = event;
reject(detailedError);
}
else {
reject(new Error("IndexedDB operation failed with unknown error"));
}
});
})];
});
});
}
/**
* Safely executes an async operation with error handling
* @template T The type of the result that will be returned by the operation
* @param fn The async function to execute
* @returns A Promise that resolves with the result or null if an error occurs
*/
function safeCall(fn) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
return [4 /*yield*/, fn()];
case 1: return [2 /*return*/, _b.sent()];
case 2:
_b.sent();
return [2 /*return*/, null];
case 3: return [2 /*return*/];
}
});
});
}
/**
* Safely executes an async operation that returns void with error handling
* @param fn The async function to execute
* @returns A Promise that resolves to void, silently handling any errors
*/
function safeVoidCall(fn) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
return [4 /*yield*/, fn()];
case 1:
_b.sent();
return [3 /*break*/, 3];
case 2:
_b.sent();
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
});
}
var DB = /** @class */ (function () {
function DB() {
this.dbName = "use-idb-store";
this.indexedDB = null;
this.stores = new Map();
this.version = undefined;
this.dbInitPromise = null;
this.isClosing = false;
}
DB.getInstance = function () {
if (!DB.instance) {
DB.instance = new DB();
}
return DB.instance;
};
/**
* Sets up event handlers for multi-tab coordination
*/
DB.prototype.setupDatabaseEventHandlers = function (db) {
var _this = this;
// Handle version change - fired when another tab wants to upgrade the database
db.onversionchange = function () {
_this.isClosing = true;
db.close();
_this.indexedDB = null;
_this.version = undefined;
_this.isClosing = false;
};
// Handle unexpected close
db.onclose = function () {
if (!_this.isClosing) {
_this.indexedDB = null;
_this.version = undefined;
}
};
};
DB.prototype.getCurrentDatabaseVersion = function () {
return __awaiter(this, void 0, void 0, function () {
var request, db, currentVersion;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
request = indexedDB.open(this.dbName);
return [4 /*yield*/, idbRequestToPromise(request)];
case 1:
db = _b.sent();
currentVersion = db.version;
db.close();
return [2 /*return*/, currentVersion];
case 2:
_b.sent();
// Database doesn't exist yet, start with version 1
return [2 /*return*/, 1];
case 3: return [2 /*return*/];
}
});
});
};
DB.prototype.sync = function () {
return __awaiter(this, void 0, void 0, function () {
var _a, missingStores, missingStores, db, error_1;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
// If there's already an initialization in progress, wait for it
if (this.dbInitPromise) {
return [2 /*return*/, this.dbInitPromise];
}
if (!(this.version === undefined)) return [3 /*break*/, 2];
_a = this;
return [4 /*yield*/, this.getCurrentDatabaseVersion()];
case 1:
_a.version = _b.sent();
_b.label = 2;
case 2:
if (this.indexedDB) {
missingStores = Array.from(this.stores.keys()).filter(function (name) { return !_this.indexedDB.objectStoreNames.contains(name); });
if (missingStores.length === 0) {
return [2 /*return*/, this.indexedDB];
}
this.indexedDB.close();
this.indexedDB = null;
this.version++;
}
else {
missingStores = Array.from(this.stores.keys());
if (missingStores.length > 0) {
this.version++;
}
}
this.dbInitPromise = this.initializeDatabase();
_b.label = 3;
case 3:
_b.trys.push([3, 5, , 6]);
return [4 /*yield*/, this.dbInitPromise];
case 4:
db = _b.sent();
this.dbInitPromise = null;
return [2 /*return*/, db];
case 5:
error_1 = _b.sent();
this.dbInitPromise = null;
throw error_1;
case 6: return [2 /*return*/];
}
});
});
};
DB.prototype.initializeDatabase = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve, reject) {
var request = indexedDB.open(_this.dbName, _this.version);
request.onupgradeneeded = function (event) {
var db = event.target.result;
// Create all registered stores.
_this.stores.forEach(function (schema, name) {
if (!db.objectStoreNames.contains(name)) {
try {
db.createObjectStore(name, schema);
}
catch (error) {
console.error("Error creating store ".concat(name, ":"), error);
throw error;
}
}
});
};
request.onblocked = function () {
console.warn("Database upgrade blocked. Other tabs have open connections. Please close them or they will be closed automatically.");
};
request.onsuccess = function (event) {
var db = event.target.result;
_this.indexedDB = db;
// Set up multi-tab coordination handlers.
_this.setupDatabaseEventHandlers(db);
resolve(db);
};
request.onerror = function () {
console.error("Database opening failed:", request.error);
reject(request.error);
};
})];
});
});
};
DB.prototype.createStore = function (name, schema) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// Register the store
this.stores.set(name, schema);
return [4 /*yield*/, this.sync()];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
DB.prototype.getStore = function (name) {
return __awaiter(this, void 0, void 0, function () {
var db;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.sync()];
case 1:
db = _a.sent();
if (!db.objectStoreNames.contains(name)) {
throw new Error("Store \"".concat(name, "\" not found in database. Available stores: ").concat(Array.from(db.objectStoreNames).join(", ")));
}
return [2 /*return*/, db.transaction(name, "readwrite").objectStore(name)];
}
});
});
};
DB.prototype.clearStore = function (name) {
return __awaiter(this, void 0, void 0, function () {
var store;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getStore(name)];
case 1:
store = _a.sent();
return [4 /*yield*/, idbRequestToPromise(store.clear())];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
};
DB.prototype.deleteStore = function (name) {
return __awaiter(this, void 0, void 0, function () {
var _a;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
// Remove from registry
this.stores.delete(name);
if (!this.indexedDB) return [3 /*break*/, 3];
this.isClosing = true;
this.indexedDB.close();
this.indexedDB = null;
this.isClosing = false;
if (!(this.version === undefined)) return [3 /*break*/, 2];
_a = this;
return [4 /*yield*/, this.getCurrentDatabaseVersion()];
case 1:
_a.version = _b.sent();
_b.label = 2;
case 2:
this.version++;
return [2 /*return*/, new Promise(function (resolve, reject) {
var request = indexedDB.open(_this.dbName, _this.version);
request.onupgradeneeded = function (event) {
var db = event.target.result;
// Delete the store if it exists
if (db.objectStoreNames.contains(name)) {
db.deleteObjectStore(name);
}
// Recreate remaining stores that should exist
_this.stores.forEach(function (schema, storeName) {
if (!db.objectStoreNames.contains(storeName)) {
try {
db.createObjectStore(storeName, schema);
}
catch (error) {
console.error("Error recreating store ".concat(storeName, ":"), error);
throw error;
}
}
});
};
request.onblocked = function () {
console.warn("Database upgrade blocked during store deletion. Waiting for other tabs to close connections.");
};
request.onsuccess = function (event) {
var db = event.target.result;
_this.indexedDB = db;
// Set up multi-tab coordination handlers
_this.setupDatabaseEventHandlers(db);
resolve();
};
request.onerror = function () {
console.error("Database update failed during store deletion:", request.error);
reject(request.error);
};
})];
case 3: return [2 /*return*/];
}
});
});
};
return DB;
}());
var Store = /** @class */ (function () {
function Store(name, schema) {
this.isInitialized = false;
this.initPromise = null;
this.eventListeners = {
change: [],
error: [],
};
this.MAX_RETRIES = 3;
this.RETRY_DELAY = 100; // ms
this.db = DB.getInstance();
this.name = name;
this.schema = schema;
this.initPromise = this.setup();
}
Store.getInstance = function (name, schema) {
if (!Store.instance[name]) {
Store.instance[name] = new Store(name, schema);
}
return Store.instance[name];
};
Store.prototype.setup = function () {
return __awaiter(this, void 0, void 0, function () {
var error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.db.createStore(this.name, this.schema)];
case 1:
_a.sent();
this.isInitialized = true;
return [3 /*break*/, 3];
case 2:
error_1 = _a.sent();
throw error_1;
case 3: return [2 /*return*/];
}
});
});
};
Store.prototype.ensureInitialized = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!this.isInitialized) return [3 /*break*/, 4];
if (!this.initPromise) return [3 /*break*/, 2];
return [4 /*yield*/, this.initPromise];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
this.initPromise = this.setup();
return [4 /*yield*/, this.initPromise];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
Store.prototype.getStore = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.ensureInitialized()];
case 1:
_a.sent();
return [2 /*return*/, this.db.getStore(this.name)];
}
});
});
};
/**
* Retries an operation if it fails due to database version changes in other tabs
*/
Store.prototype.retryOperation = function (operation_1) {
return __awaiter(this, arguments, void 0, function (operation, retries) {
var error_2, isVersionError;
var _this = this;
var _a, _b;
if (retries === void 0) { retries = this.MAX_RETRIES; }
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_c.trys.push([0, 2, , 5]);
return [4 /*yield*/, operation()];
case 1: return [2 /*return*/, _c.sent()];
case 2:
error_2 = _c.sent();
isVersionError = (error_2 === null || error_2 === void 0 ? void 0 : error_2.name) === "InvalidStateError" ||
(error_2 === null || error_2 === void 0 ? void 0 : error_2.name) === "VersionError" ||
((_a = error_2 === null || error_2 === void 0 ? void 0 : error_2.message) === null || _a === void 0 ? void 0 : _a.includes("version")) ||
((_b = error_2 === null || error_2 === void 0 ? void 0 : error_2.message) === null || _b === void 0 ? void 0 : _b.includes("connection"));
if (!(isVersionError && retries > 0)) return [3 /*break*/, 4];
// Wait a bit before retrying
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, _this.RETRY_DELAY); })];
case 3:
// Wait a bit before retrying
_c.sent();
// Reset initialization state to force reconnection
this.isInitialized = false;
this.initPromise = null;
return [2 /*return*/, this.retryOperation(operation, retries - 1)];
case 4: throw error_2;
case 5: return [2 /*return*/];
}
});
});
};
Store.prototype.getItem = function (key) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request = store.get(key);
return [2 /*return*/, idbRequestToPromise(request)];
case 2:
error_3 = _a.sent();
this.emit("error", error_3);
return [2 /*return*/, null];
case 3: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.prototype.getAllItems = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request_1, result_1, error_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request_1 = store.openCursor();
result_1 = {};
return [2 /*return*/, new Promise(function (resolve, reject) {
request_1.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
result_1[cursor.key.toString()] = cursor.value;
cursor.continue();
}
else {
resolve(result_1);
}
};
request_1.onerror = function () { return reject(request_1.error); };
})];
case 2:
error_4 = _a.sent();
this.emit("error", error_4);
return [2 /*return*/, {}];
case 3: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.prototype.addItem = function (key, value) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request, result, error_5;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request = store.add(value, key);
return [4 /*yield*/, idbRequestToPromise(request)];
case 2:
result = _a.sent();
this.emit("change", { add: { key: key, value: value } });
return [2 /*return*/, result];
case 3:
error_5 = _a.sent();
this.emit("error", error_5);
throw error_5;
case 4: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.prototype.addOrUpdateItem = function (key, value) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request, result, error_6;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request = store.put(value, key);
return [4 /*yield*/, idbRequestToPromise(request)];
case 2:
result = _a.sent();
this.emit("change", { addOrUpdate: { key: key, value: value } });
return [2 /*return*/, result];
case 3:
error_6 = _a.sent();
throw error_6;
case 4: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.prototype.updateItem = function (key, value) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request_2, error_7;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request_2 = store.openCursor(key);
return [2 /*return*/, new Promise(function (resolve, reject) {
request_2.onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
var existingItem = cursor.value;
var updatedItem = __assign(__assign({}, existingItem), value);
var updateRequest_1 = cursor.update(updatedItem);
updateRequest_1.onsuccess = function () {
resolve(key);
_this.emit("change", { update: key });
};
updateRequest_1.onerror = function () {
reject(updateRequest_1.error);
_this.emit("error", updateRequest_1.error);
};
}
else {
reject(new Error("Item with key ".concat(key, " not found in store ").concat(_this.name)));
_this.emit("error", new Error("Item with key ".concat(key, " not found in store ").concat(_this.name)));
}
};
request_2.onerror = function () { return reject(request_2.error); };
})];
case 2:
error_7 = _a.sent();
this.emit("error", error_7);
throw error_7;
case 3: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.prototype.deleteItem = function (key) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request, error_8;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request = store.delete(key);
return [4 /*yield*/, idbRequestToPromise(request)];
case 2:
_a.sent();
this.emit("change", { delete: { key: key } });
return [3 /*break*/, 4];
case 3:
error_8 = _a.sent();
this.emit("error", error_8);
throw error_8;
case 4: return [2 /*return*/];
}
});
}); })];
});
});
};
/**
* Emits an event to all registered listeners for a specific event type
* @param event - The type of event to emit ('change' or 'error')
* @param detail - The detail to emit with the event
*/
Store.prototype.emit = function (event, detail) {
var listeners = this.eventListeners[event];
if (listeners && listeners.length > 0) {
// Create a custom event with the data
var customEvent_1 = new CustomEvent(event, { detail: detail });
listeners.forEach(function (callback) { return callback(customEvent_1); });
}
};
/**
* Registers an event listener for the specified store event
*/
Store.prototype.on = function (event, callback) {
this.eventListeners[event].push(callback);
};
/**
* Removes an event listener from the store
*/
Store.prototype.off = function (event, callback) {
this.eventListeners[event] = this.eventListeners[event].filter(function (listener) { return listener !== callback; });
};
/**
* Clears all items from the store.
*/
Store.prototype.clear = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var store, request, error_9;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.getStore()];
case 1:
store = _a.sent();
request = store.clear();
return [4 /*yield*/, idbRequestToPromise(request)];
case 2:
_a.sent();
this.emit("change", { clear: true });
return [3 /*break*/, 4];
case 3:
error_9 = _a.sent();
this.emit("error", error_9);
throw error_9;
case 4: return [2 /*return*/];
}
});
}); })];
});
});
};
/**
* Destroys the store and deletes it from the database.
*/
Store.prototype.destroy = function () {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
return [2 /*return*/, this.retryOperation(function () { return __awaiter(_this, void 0, void 0, function () {
var error_10;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.db.deleteStore(this.name)];
case 1:
_a.sent();
this.emit("change", { destroy: true });
return [3 /*break*/, 3];
case 2:
error_10 = _a.sent();
this.emit("error", error_10);
throw error_10;
case 3: return [2 /*return*/];
}
});
}); })];
});
});
};
Store.instance = {};
return Store;
}());
var useIndexedDbStore = function (name, _a) {
var _b = _a === void 0 ? {} : _a, schema = _b.schema;
// States.
// HACK: Force update to trigger re-render.
var _c = react.useState({}), forceUpdate = _c[1];
// Refs.
var storeRef = react.useRef(null);
var valuesRef = react.useRef({});
var isLoadingRef = react.useRef(true);
var isReadyRef = react.useRef(false);
var errorRef = react.useRef(null);
// useEffect.
react.useEffect(function () {
if (storeRef.current)
return;
storeRef.current = Store.getInstance(name, schema);
var loadData = function () { return __awaiter(void 0, void 0, void 0, function () {
var data, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
if (!storeRef.current)
return [2 /*return*/];
isLoadingRef.current = true;
errorRef.current = null;
return [4 /*yield*/, storeRef.current.getAllItems()];
case 1:
data = _a.sent();
valuesRef.current = data;
isLoadingRef.current = false;
return [3 /*break*/, 3];
case 2:
err_1 = _a.sent();
console.error("Error loading indexed DB data:", err_1);
errorRef.current = err_1 instanceof Error ? err_1 : new Error(String(err_1));
isLoadingRef.current = false;
return [3 /*break*/, 3];
case 3:
forceUpdate({});
return [2 /*return*/];
}
});
}); };
var handleError = function (err) {
errorRef.current = err instanceof Error ? err : new Error(String(err));
isLoadingRef.current = false;
forceUpdate({});
};
// Initial load.
loadData().then(function () {
isReadyRef.current = true;
forceUpdate({});
});
storeRef.current.on("change", loadData);
storeRef.current.on("error", handleError);
return function () {
// TODO: Unsubscribe from changes.
// storeRef.current?.off("change", loadData);
// storeRef.current?.off("error", handleError);
};
}, []);
// Handlers.
var mutations = {
getValue: function (id) { return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, safeCall(function () { return __awaiter(void 0, void 0, void 0, function () {
var storeValue;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, storeRef.current.getItem(id)];
case 1:
storeValue = _b.sent();
return [2 /*return*/, (_a = storeValue !== null && storeValue !== void 0 ? storeValue : valuesRef.current[id]) !== null && _a !== void 0 ? _a : null];
}
});
}); })];
});
}); },
addValue: function (id, value) {
return safeVoidCall(function () { return storeRef.current.addItem(id, value); });
},
deleteValue: function (id) {
return safeVoidCall(function () { return storeRef.current.deleteItem(id); });
},
updateValue: function (id, value) {
return safeVoidCall(function () { return storeRef.current.updateItem(id, value); });
},
addOrUpdateValue: function (id, value) {
return safeVoidCall(function () { return storeRef.current.addOrUpdateItem(id, value); });
},
};
return {
values: valuesRef.current,
mutations: mutations,
isLoading: isLoadingRef.current,
error: errorRef.current,
isReady: isReadyRef.current,
};
};
exports.DB = DB;
exports.Store = Store;
exports.idbRequestToPromise = idbRequestToPromise;
exports.safeCall = safeCall;
exports.safeVoidCall = safeVoidCall;
exports.useIndexedDbStore = useIndexedDbStore;
//# sourceMappingURL=index.cjs.js.map