UNPKG

@foqum/redux-offline-crud-rest

Version:

Offline-first persistence for React Native web apps backed by CRUD-based operations over REST APIs

212 lines (177 loc) 5.66 kB
const uuid = require('react-native-uuid') const genActionTypes = require('./actionTypes') // Join params with slashses function composeUrl(...args) { return args.filter(item => item != null).join('/') } function filterMethods([name, func]) { return name !== 'dispatch' && func instanceof Function } function reduceActions(acum, [key, func]) { return { ...acum, [key]: (...args) => this(func(...args)) } } function reduceFilteredMethods(acum, [key, func]) { return { ...acum, [key]: func } } function reduceNamespaces(acum, name) { return { ...acum, [name]: actions(name, this) } } function reduceNamespacesObject(acum, [name, options]) { if (typeof options !== 'object') options = { options } return reduceNamespaces.call({ ...this, ...options }, acum, name) } const removeLastChar = string => string.slice(0, -1) function trim(string, char) { return string.replace(new RegExp( '^[' + char + ']+|[' + char + ']+$', 'g' // eslint-disable-line ), '') } function actions(basePath, options = {}) { if (Array.isArray(basePath)) { // basePath is an array, recursion return basePath.reduce(reduceNamespaces.bind(options), {}) } if (typeof basePath !== 'string') { // basePath is an dictionary like object, recursion return Object.entries(basePath).reduce(reduceNamespacesObject.bind(options), {}) } // basePath is a string if (basePath.endsWith('/')) basePath = removeLastChar(basePath) const { dispatch, headers } = options let baseUrl = options.baseUrl || '' if (baseUrl.endsWith('/')) baseUrl = removeLastChar(baseUrl) const actionTypes = genActionTypes(basePath) function reduceMethods(acum, [name, func]) { const actionType = `${basePath}#${name}` // eslint-disable-next-line return { ...acum, [name]: (id, body) => { const meta = { func, id } return { type: actionType, meta: { offline: { effect: { url: composeUrl(baseUrl, basePath, id, name), method: 'POST', body, headers, }, commit: { type: `${actionType}_commit`, meta }, rollback: { type: `${actionType}_rollback`, meta }, }, }, } } } } // create object with CREATE, GET, PUT, PATCH, DELETE let result = { create(body, prefix) { const id = `tmp_id:${uuid.v4()}` if (prefix) prefix = trim(prefix, '/') return { type: actionTypes.create, payload: body, meta: { id, offline: { effect: { url: composeUrl(baseUrl, prefix, basePath), method: 'POST', body, headers, }, commit: { type: actionTypes.create_commit, meta: { id } }, rollback: { type: actionTypes.create_rollback, meta: { id } }, }, }, } }, read(id, ownId = undefined, prefix) { if (prefix) prefix = trim(prefix, '/') return { type: actionTypes.read, meta: { id: ownId || id, offline: { effect: { url: composeUrl(baseUrl, prefix, basePath, id), method: 'GET', headers, }, commit: { type: actionTypes.read_commit, meta: { id: ownId || id } }, rollback: { type: actionTypes.read_rollback, meta: { id: ownId || id } }, }, }, } }, update(id, body, ownId = undefined, prefix) { if (prefix) prefix = trim(prefix, '/') return { type: actionTypes.update, payload: body, meta: { id: ownId || id, offline: { effect: { url: composeUrl(baseUrl, prefix, basePath, id), method: 'PUT', body, headers, }, commit: { type: actionTypes.update_commit, meta: { id: ownId || id } }, rollback: { type: actionTypes.update_rollback, meta: { id: ownId || id } }, }, }, } }, patch(id, body, prefix) { if (prefix) prefix = trim(prefix, '/') return { type: actionTypes.patch, payload: body, meta: { id, offline: { effect: { url: composeUrl(baseUrl, prefix, basePath, id), method: 'PATCH', body, headers: { ...headers, 'content-type': 'merge-patch+json' }, }, commit: { type: actionTypes.patch_commit, meta: { id } }, rollback: { type: actionTypes.patch_rollback, meta: { id } }, }, }, } }, delete(id, prefix) { if (prefix) prefix = trim(prefix, '/') return { type: actionTypes.delete, meta: { id, offline: { effect: { url: composeUrl(baseUrl, prefix, basePath, id), method: 'DELETE', headers, }, commit: { type: actionTypes.delete_commit, meta: { id } }, rollback: { type: actionTypes.delete_rollback, meta: { id } }, }, }, } }, } // custom methods let { resourceMethods } = options if (!resourceMethods) { resourceMethods = Object.entries(options).filter(filterMethods) .reduce(reduceFilteredMethods, {}) } // merge CRUD methods with custom ones result = Object.entries(resourceMethods).reduce(reduceMethods, result) if (!dispatch) return result return Object.entries(result).reduce(reduceActions.bind(dispatch), {}) } module.exports = actions