UNPKG

zustand-storage

Version:

A universal solution combining @aivron/sync-storage and zust-api for React (web & desktop). It merges local persistence with a Zustand-inspired API to provide core storage operations, bulk actions, JSON support, TTL, and integrated React hooks.

276 lines (275 loc) 13.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(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); }; 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()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "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 }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createSyncedStore = void 0; var zust_api_1 = require("zust-api"); var storageEngine_1 = __importDefault(require("./storageEngine")); var utils_1 = require("./utils"); /** * Creates a synced store that combines zust‑api state management with persistence via @aivron/sync-storage, * and optional database integration. * * @template State - The type of the state. Must extend object. * @template Actions - The type of the actions. * @param config - The configuration object for the synced store. * @returns A synced store containing the React hook and utility methods. */ function createSyncedStore(config) { var _this = this; var key = config.key, _a = config.storageEngine, storageEngine = _a === void 0 ? storageEngine_1.default : _a, initialState = config.initialState, actionsCreator = config.actions, _b = config.syncOptions, syncOptions = _b === void 0 ? {} : _b, _c = config.mergeStrategy, mergeStrategy = _c === void 0 ? "replace" : _c, migrate = config.migrate, database = config.database; var _d = syncOptions.debounce, debounceDelay = _d === void 0 ? 0 : _d, _e = syncOptions.serialize, serialize = _e === void 0 ? JSON.stringify : _e, _f = syncOptions.deserialize, deserialize = _f === void 0 ? JSON.parse : _f, _g = syncOptions.persistOn, persistOn = _g === void 0 ? "change" : _g, _h = syncOptions.interval, interval = _h === void 0 ? 0 : _h, onError = syncOptions.onError; // Create the zust‑api store using the initial state. var store = (0, zust_api_1.createStore)(initialState); // Build the actions using zust‑api's setDeep and getState. var actions = actionsCreator(store.setDeep, store.getState); /** * Custom setter supporting functional updates. */ var customSetState = function (partial, replace) { if (replace === void 0) { replace = false; } var current = store.getState(); var update = typeof partial === "function" ? partial(current) : partial; store.setDeep("", update); }; /** * Persists the current state to storage. */ var triggerPersist = function () { try { var currentState = store.getState(); var data = serialize(currentState); storageEngine.setItem(key, data); } catch (error) { if (onError) onError(error); else console.error("Persist error:", error); } }; if (debounceDelay > 0) { triggerPersist = (0, utils_1.debounce)(triggerPersist, debounceDelay); } // Persist state on change if configured. if (persistOn === "change") { store.subscribe(function (state) { triggerPersist(); }); } // Periodic persistence using interval mode. if (persistOn === "interval" && interval > 0) { setInterval(function () { try { var currentState = store.getState(); var data = serialize(currentState); storageEngine.setItem(key, data); } catch (error) { if (onError) onError(error); else console.error("Persist error:", error); } }, interval); } /** * Rehydrates the state from persisted storage. */ var rehydrate = function () { try { var data = storageEngine.getItem(key); if (data) { var persistedState = deserialize(data); if (migrate) { persistedState = migrate(persistedState); } var newState_1; switch (mergeStrategy) { case "deep": newState_1 = (0, utils_1.deepMerge)(__assign({}, initialState), persistedState); break; case "shallow": newState_1 = (0, utils_1.shallowMerge)(initialState, persistedState); break; case "replace": default: newState_1 = persistedState; break; } customSetState(function () { return newState_1; }, true); } } catch (error) { if (onError) onError(error); else console.error("Rehydrate error:", error); } }; // Immediately rehydrate on store creation. rehydrate(); // Middleware support. var middlewares = []; var addMiddleware = function (mw) { middlewares.push(mw); var currentState = store.getState(); var newState = mw(currentState); customSetState(function () { return newState; }, true); }; /** * Resets the store to its initial state and clears persisted storage. */ var resetStore = function () { customSetState(function () { return initialState; }, true); storageEngine.removeItem(key); }; // Database integration methods (if configured) var fetchFromDB; var saveToDB; if (database) { /** * Fetches data from the database and merges it into the store state. * @param query - The query object used to fetch data. */ fetchFromDB = function (query) { return __awaiter(_this, void 0, void 0, function () { var client, collectionOrTable, fromRecord, records, record, newStatePartial, mergedState_1, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); client = database.client, collectionOrTable = database.collectionOrTable, fromRecord = database.fromRecord; return [4 /*yield*/, client.find(collectionOrTable, query)]; case 1: records = _a.sent(); if (records.length > 0) { record = records[0]; newStatePartial = fromRecord ? fromRecord(record) : record; switch (mergeStrategy) { case "deep": mergedState_1 = (0, utils_1.deepMerge)(store.getState(), newStatePartial); break; case "shallow": mergedState_1 = (0, utils_1.shallowMerge)(store.getState(), newStatePartial); break; case "replace": default: mergedState_1 = newStatePartial; break; } customSetState(function () { return mergedState_1; }, true); } return [3 /*break*/, 3]; case 2: error_1 = _a.sent(); if (database.onError) database.onError(error_1); else console.error("Error fetching from DB:", error_1); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; /** * Saves the current store state to the database. * @param query - The query object to locate an existing record. Updates if found, inserts otherwise. */ saveToDB = function (query) { return __awaiter(_this, void 0, void 0, function () { var client, collectionOrTable, toRecord, state, record, existingRecords, error_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 6, , 7]); client = database.client, collectionOrTable = database.collectionOrTable, toRecord = database.toRecord; state = store.getState(); record = toRecord ? toRecord(state) : state; return [4 /*yield*/, client.find(collectionOrTable, query)]; case 1: existingRecords = _a.sent(); if (!(existingRecords.length > 0)) return [3 /*break*/, 3]; return [4 /*yield*/, client.update(collectionOrTable, query, record)]; case 2: _a.sent(); return [3 /*break*/, 5]; case 3: return [4 /*yield*/, client.insert(collectionOrTable, record)]; case 4: _a.sent(); _a.label = 5; case 5: return [3 /*break*/, 7]; case 6: error_2 = _a.sent(); if (database.onError) database.onError(error_2); else console.error("Error saving to DB:", error_2); return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); }; } return { useStore: store.useStore, getState: store.getState, setState: customSetState, subscribe: store.subscribe, persist: triggerPersist, rehydrate: rehydrate, addMiddleware: addMiddleware, resetStore: resetStore, actions: actions, // Expose DB integration methods when configured fetchFromDB: fetchFromDB, saveToDB: saveToDB, }; } exports.createSyncedStore = createSyncedStore;