Skip to content

Commit

Permalink
feat: Add "isOptimist" option for .update and .delete methods. Will
Browse files Browse the repository at this point in the history
commit changes immediately to state and revert in case of error
  • Loading branch information
andreidmt committed Sep 11, 2020
1 parent 16a8a8d commit c6d1a96
Show file tree
Hide file tree
Showing 26 changed files with 542 additions and 461 deletions.
2 changes: 1 addition & 1 deletion src/create/create.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const debug = require("debug")("ReduxList:CreateAction")
const debug = require("debug")("JustAList:CreateAction")

/**
* Call list.create method to add result to slice.items
Expand Down
6 changes: 3 additions & 3 deletions src/create/create.reducers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const debug = require("debug")("ReduxList:CreateReducers")
const debug = require("debug")("JustAList:CreateReducers")

import { hasWith, intersect, not, is, i } from "@mutant-ws/m"
import { hasWith, intersect, not, is, i } from "m.xyz"

export const startReducer = (state, { items }) => ({
...state,
Expand All @@ -14,7 +14,7 @@ export const endReducer = (state, { listName, items = [], onChange = i }) => {

if (itemWithoutId) {
throw new TypeError(
`ReduxList: "${listName}" Trying to create item without id property`
`JustAList: "${listName}" Trying to create item without id property`
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/create/create.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import test from "tape"
import { createStore, combineReducers } from "redux"
import { map } from "@mutant-ws/m"
import { map } from "m.xyz"

import { buildList } from ".."

Expand Down
2 changes: 1 addition & 1 deletion src/create/create__error.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ test("Create - error", async t => {

t.equals(
error.message,
`ReduxList: "CREATE-ERROR_TODOS" Trying to create item without id property`,
`JustAList: "CREATE-ERROR_TODOS" Trying to create item without id property`,
"Creating item without id field should throw"
)
}
Expand Down
158 changes: 64 additions & 94 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const debug = require("debug")("ReduxList:Main")
const debug = require("debug")("JustAList:Main")

import { get, pipe, findWith, hasWith, is, isEmpty, hasKey } from "@mutant-ws/m"
import { get, pipe, findWith, hasWith, is, isEmpty, hasKey } from "m.xyz"

import { createAction } from "./create/create"
import {
Expand Down Expand Up @@ -37,17 +37,12 @@ import {
errorReducer as removeErrorReducer,
} from "./remove/remove.reducers"

import { buildQueue } from "./lib/queue"

const collections = Object.create(null)

/**
* Construct a set of actions and reducers to manage a state slice as an array
*
* @param {string} name Unique name so actions dont overlap
* @param {Object} methods Object with CRUD method
* @param {Object} hooks Transformer functions called when specific actions
* occur
* @param {string} name Unique list name
* @param {Function} onChange Function triggered on every list change
*
* @return {Object}
Expand All @@ -67,14 +62,14 @@ const buildList = ({
} = {}) => {
if (isEmpty(name)) {
throw new Error(
`ReduxList: "name" property is required, received "${JSON.stringify(
`JustAList: "name" property is required, received "${JSON.stringify(
name
)}"`
)
}

if (hasKey(name)(collections)) {
throw new Error(`ReduxList: List with name "${name}" already exists`)
throw new Error(`JustAList: List with name "${name}" already exists`)
}

collections[name] = true
Expand All @@ -93,7 +88,6 @@ const buildList = ({
removeHasDispatchEnd: true,
}

const queue = buildQueue()
const createStart = `${name}_CREATE_START`
const createEnd = `${name}_CREATE_END`
const createError = `${name}_CREATE_ERROR`
Expand Down Expand Up @@ -123,7 +117,7 @@ const buildList = ({
create: (data, { isLocal = false, ...options } = {}) => {
if (isLocal === false && typeof create !== "function") {
throw new TypeError(
`ReduxList: "${name}"."create" must be a function, got "${typeof create}"`
`JustAList: "${name}"."create" must be a function, got "${typeof create}"`
)
}

Expand All @@ -140,72 +134,54 @@ const buildList = ({
return Promise.resolve({ result: data })
}

return queue.enqueue({
id: `${name}__create`,
fn: createAction({
listName: name,
dispatch: props.dispatch,
api: create,
hasDispatchStart: props.createHasDispatchStart,
hasDispatchEnd: props.createHasDispatchEnd,
onChange,
}),

// queue calls fn(...args)
args: [data, { isLocal, ...options }],
})
return createAction({
listName: name,
dispatch: props.dispatch,
api: create,
hasDispatchStart: props.createHasDispatchStart,
hasDispatchEnd: props.createHasDispatchEnd,
onChange,
})(data, { isLocal, ...options })
},

read: (query, options) => {
if (typeof read !== "function") {
throw new TypeError(
`ReduxList: "${name}"."read" must be a function, got "${typeof read}"`
`JustAList: "${name}"."read" must be a function, got "${typeof read}"`
)
}

return queue.enqueue({
id: `${name}__read`,
fn: readAction({
listName: name,
dispatch: props.dispatch,
api: read,
hasDispatchStart: props.readHasDispatchStart,
hasDispatchEnd: props.readHasDispatchEnd,
onChange,
}),

// queue calls fn(...args)
args: [query, options],
})
return readAction({
listName: name,
dispatch: props.dispatch,
api: read,
hasDispatchStart: props.readHasDispatchStart,
hasDispatchEnd: props.readHasDispatchEnd,
onChange,
})(query, options)
},

readOne: (query, options) => {
if (typeof readOne !== "function") {
throw new TypeError(
`ReduxList: "${name}"."readOne" must be a function, got "${typeof readOne}"`
`JustAList: "${name}"."readOne" must be a function, got "${typeof readOne}"`
)
}

return queue.enqueue({
id: `${name}__readOne`,
fn: readOneAction({
listName: name,
dispatch: props.dispatch,
api: readOne,
hasDispatchStart: props.readOneHasDispatchStart,
hasDispatchEnd: props.readOneHasDispatchEnd,
onChange,
}),

// queue calls fn(...args)
args: [query, options],
})
return readOneAction({
listName: name,
dispatch: props.dispatch,
api: readOne,
hasDispatchStart: props.readOneHasDispatchStart,
hasDispatchEnd: props.readOneHasDispatchEnd,
onChange,
})(query, options)
},

update: (id, data, { isLocal = false, onMerge, ...options } = {}) => {
if (isLocal === false && typeof update !== "function") {
throw new TypeError(
`ReduxList: "${name}"."update" must be a function, got "${typeof update}"`
`JustAList: "${name}"."update" must be a function, got "${typeof update}"`
)
}

Expand All @@ -223,57 +199,44 @@ const buildList = ({
return Promise.resolve({ result: { id, ...data } })
}

return queue.enqueue({
id: `${name}__update`,
fn: updateAction({
listName: name,
dispatch: props.dispatch,
api: update,
hasDispatchStart: props.updateHasDispatchStart,
hasDispatchEnd: props.updateHasDispatchEnd,
onMerge,
onChange,
}),

// queue calls fn(...args)
args: [id, data, options],
})
return updateAction({
listName: name,
dispatch: props.dispatch,
api: update,
hasDispatchStart: props.updateHasDispatchStart,
hasDispatchEnd: props.updateHasDispatchEnd,
onMerge,
onChange,
})(id, data, options)
},

remove: (id, { isLocal = false, ...options } = {}) => {
remove: (id, { isLocal = false, ...restOptions } = {}) => {
if (isLocal === false && typeof remove !== "function") {
throw new TypeError(
`ReduxList: "${name}"."remove" must be a function, got "${typeof remove}"`
`JustAList: "${name}"."remove" must be a function, got "${typeof remove}"`
)
}

if (isLocal) {
props.dispatch({
type: removeEnd,
payload: {
listName: name,
item: { id },
id,
onChange,
},
})

return Promise.resolve({ result: { id } })
}

return queue.enqueue({
id: `${name}__remove`,
fn: removeAction({
listName: name,
dispatch: props.dispatch,
api: remove,
hasDispatchStart: props.removeHasDispatchStart,
hasDispatchEnd: props.removeHasDispatchEnd,
onChange,
}),

// queue calls fn(...args)
args: [id, { isLocal, ...options }],
})
return removeAction({
listName: name,
dispatch: props.dispatch,
api: remove,
hasDispatchStart: props.removeHasDispatchStart,
hasDispatchEnd: props.removeHasDispatchEnd,
onChange,
})(id, { isLocal, ...restOptions })
},

clear: () => {
Expand All @@ -290,22 +253,28 @@ const buildList = ({

reducer: (
state = {
listName: name,
items: [],
reading: null,
optimistItems: [],
creating: [],
reading: null,
updating: [],
removing: [],

errors: {
read: null,
readOne: null,
create: null,
remove: null,
update: null,
remove: null,
},

loadDate: null,
isCreating: false,
isLoading: false,
isLoadingOne: false,
isUpdating: false,
isRemoving: false,
},
{ type, payload }
) => {
Expand Down Expand Up @@ -372,14 +341,15 @@ const buildList = ({
isRemoving: id => {
const removing = get([name, "removing"])(state)

return is(id) ? hasWith({ id })(removing) : !isEmpty(removing)
return is(id) ? hasWith({ id }, removing) : !isEmpty(removing)
},
isUpdating: id => {
const updating = get([name, "updating"])(state)

return is(id) ? hasWith({ id })(updating) : !isEmpty(updating)
return is(id) ? hasWith({ id }, updating) : !isEmpty(updating)
},
isLoading: () => get([name, "isLoading"])(state),
isLoadingOne: () => get([name, "isLoadingOne"])(state),
isLoaded: () => pipe(get([name, "loadDate"]), is)(state),
}),
}
Expand Down
14 changes: 7 additions & 7 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ test("List without API methods", async t => {
() => {
buildList({ name: "TODOS" })
},
/ReduxList: List with name "TODOS" already exists/,
/JustAList: List with name "TODOS" already exists/,
"Throw exception when creating a list with a duplicate name"
)

t.throws(
() => {
buildList()
},
/ReduxList: "name" property is required, received "undefined"/,
/JustAList: "name" property is required, received "undefined"/,
"Throw exception when creating a list without any params"
)

Expand Down Expand Up @@ -81,39 +81,39 @@ test("List without API methods", async t => {
() => {
todos.create({ id: 2 })
},
/ReduxList: "TODOS"."create" must be a function, got "undefined"/,
/JustAList: "TODOS"."create" must be a function, got "undefined"/,
'Throw exception when calling "create" on list without methods'
)

t.throws(
() => {
todos.read()
},
/ReduxList: "TODOS"."read" must be a function, got "undefined"/,
/JustAList: "TODOS"."read" must be a function, got "undefined"/,
'Throw exception when calling "read" on list without methods'
)

t.throws(
() => {
todos.readOne()
},
/ReduxList: "TODOS"."readOne" must be a function, got "undefined"/,
/JustAList: "TODOS"."readOne" must be a function, got "undefined"/,
'Throw exception when calling "readOne" on list without methods'
)

t.throws(
() => {
todos.update(1, { test: 2 })
},
/ReduxList: "TODOS"."update" must be a function, got "undefined"/,
/JustAList: "TODOS"."update" must be a function, got "undefined"/,
'Throw exception when calling "update" on list without methods'
)

t.throws(
() => {
todos.remove(1, { test: 2 })
},
/ReduxList: "TODOS"."remove" must be a function, got "undefined"/,
/JustAList: "TODOS"."remove" must be a function, got "undefined"/,
'Throw exception when calling "remove" on list without methods'
)

Expand Down
Loading

0 comments on commit c6d1a96

Please sign in to comment.