ra-data-local-forage
Version:
LocalForage data provider for react-admin
224 lines • 8.72 kB
JavaScript
;
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