UNPKG

ra-data-local-forage

Version:

LocalForage data provider for react-admin

224 lines 8.72 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ra_data_fakerest_1 = __importDefault(require("ra-data-fakerest")); const pullAt_js_1 = __importDefault(require("lodash/pullAt.js")); const localforage_1 = __importDefault(require("localforage")); /** * Respond to react-admin data queries using a localForage for storage. * * Useful for local-first web apps. * * @example // initialize with no data * * import localForageDataProvider from 'ra-data-local-forage'; * const dataProvider = localForageDataProvider(); * * @example // initialize with default data (will be ignored if data has been modified by user) * * import localForageDataProvider from 'ra-data-local-forage'; * const dataProvider = localForageDataProvider({ * defaultData: { * posts: [ * { id: 0, title: 'Hello, world!' }, * { id: 1, title: 'FooBar' }, * ], * comments: [ * { id: 0, post_id: 0, author: 'John Doe', body: 'Sensational!' }, * { id: 1, post_id: 0, author: 'Jane Doe', body: 'I agree' }, * ], * } * }); */ exports.default = (params) => { const { defaultData = {}, prefixLocalForageKey = 'ra-data-local-forage-', loggingEnabled = false, } = params || {}; let data; let baseDataProvider; let initializePromise; const getLocalForageData = async () => { const keys = await localforage_1.default.keys(); const keyFiltered = keys.filter(key => { return key.includes(prefixLocalForageKey); }); if (keyFiltered.length === 0) { return undefined; } const localForageData = {}; for (const key of keyFiltered) { const keyWithoutPrefix = key.replace(prefixLocalForageKey, ''); const res = await localforage_1.default.getItem(key); localForageData[keyWithoutPrefix] = res || []; } return localForageData; }; const initialize = async () => { if (!initializePromise) { initializePromise = initializeProvider(); } return initializePromise; }; const initializeProvider = async () => { const localForageData = await getLocalForageData(); data = localForageData ?? defaultData; baseDataProvider = (0, ra_data_fakerest_1.default)(data, loggingEnabled); }; // Persist in localForage const updateLocalForage = (resource) => { if (!data) { throw new Error('The dataProvider is not initialized.'); } localforage_1.default.setItem(`${prefixLocalForageKey}${resource}`, data[resource]); }; return { // read methods are just proxies to FakeRest getList: async (resource, params) => { await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } return baseDataProvider .getList(resource, params) .catch(error => { if (error.code === 1) { // undefined collection error: hide the error and return an empty list instead return { data: [], total: 0 }; } else { throw error; } }); }, getOne: async (resource, params) => { await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } return baseDataProvider.getOne(resource, params); }, getMany: async (resource, params) => { await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } return baseDataProvider.getMany(resource, params); }, getManyReference: async (resource, params) => { await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } return baseDataProvider .getManyReference(resource, params) .catch(error => { if (error.code === 1) { // undefined collection error: hide the error and return an empty list instead return { data: [], total: 0 }; } else { throw error; } }); }, // update methods need to persist changes in localForage update: async (resource, params) => { checkResource(resource); await initialize(); if (!data) { throw new Error('The dataProvider is not initialized.'); } if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } const index = data[resource].findIndex((record) => record.id === params.id); data[resource][index] = { ...data[resource][index], ...params.data, }; updateLocalForage(resource); return baseDataProvider.update(resource, params); }, updateMany: async (resource, params) => { checkResource(resource); await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } params.ids.forEach((id) => { if (!data) { throw new Error('The dataProvider is not initialized.'); } const index = data[resource].findIndex((record) => record.id === id); data[resource][index] = { ...data[resource][index], ...params.data, }; }); updateLocalForage(resource); return baseDataProvider.updateMany(resource, params); }, create: async (resource, params) => { checkResource(resource); await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } // we need to call the fakerest provider first to get the generated id return baseDataProvider .create(resource, params) .then(response => { if (!data) { throw new Error('The dataProvider is not initialized.'); } if (!data.hasOwnProperty(resource)) { data[resource] = []; } data[resource].push(response.data); updateLocalForage(resource); return response; }); }, delete: async (resource, params) => { checkResource(resource); await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } if (!data) { throw new Error('The dataProvider is not initialized.'); } const index = data[resource].findIndex((record) => record.id === params.id); (0, pullAt_js_1.default)(data[resource], [index]); updateLocalForage(resource); return baseDataProvider.delete(resource, params); }, deleteMany: async (resource, params) => { checkResource(resource); await initialize(); if (!baseDataProvider) { throw new Error('The dataProvider is not initialized.'); } if (!data) { throw new Error('The dataProvider is not initialized.'); } const indexes = params.ids.map((id) => { if (!data) { throw new Error('The dataProvider is not initialized.'); } return data[resource].findIndex((record) => record.id === id); }); (0, pullAt_js_1.default)(data[resource], indexes); updateLocalForage(resource); return baseDataProvider.deleteMany(resource, params); }, }; }; const checkResource = resource => { if (['__proto__', 'constructor', 'prototype'].includes(resource)) { // protection against prototype pollution throw new Error(`Invalid resource key: ${resource}`); } }; module.exports = exports.default; //# sourceMappingURL=index.js.map