Skip to content

Commit

Permalink
first version
Browse files Browse the repository at this point in the history
  • Loading branch information
albancreton committed Sep 20, 2018
1 parent c381856 commit 04b6a2a
Show file tree
Hide file tree
Showing 11 changed files with 633 additions and 3 deletions.
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "redeuce",
"version": "0.1",
"version": "1.0.0-alpha.1",
"engines": {
"node": "8.9.3",
"npm": "6.0.1"
"node": "8.9",
"npm": "6"
},
"devDependencies": {
"@babel/cli": "^7.1.0",
Expand All @@ -18,5 +18,8 @@
"eslint-plugin-import": "^2.14.0",
"jest": "^23.6.0",
"regenerator-runtime": "^0.12.1"
},
"scripts": {
"test": "NODE_ENV=development jest --config jest.conf.json --colors --notify"
}
}
14 changes: 14 additions & 0 deletions src/collectionStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { memoize, makeCallable } from './tools';

import { buildReducer, buildCollectionReducers } from './generators/reducers';
import { COLLECTION, generateActionTypes, generateActionCreators } from './generators/actions';

const generateCollectionStore = (entityName, { idKey = 'id', defaultValue = [] } = {}) => {
const actionTypes = generateActionTypes(COLLECTION, entityName);
const actionCreators = generateActionCreators(COLLECTION, entityName);

const reducer = buildReducer(buildCollectionReducers(actionTypes, idKey), defaultValue);

return makeCallable(reducer, actionCreators);
};
export default memoize(generateCollectionStore);
39 changes: 39 additions & 0 deletions src/generators/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export const SIMPLE = 'SIMPLE';
export const COLLECTION = 'COLLECTION';
export const VERBS = {
[SIMPLE]: {
SET: 'set'
},
[COLLECTION]: {
SET: 'set',
UPDATE: 'update',
DELETE: 'delete',
MERGE: 'merge',
MERGEDEEP: 'mergeDeep',
DELETEALL: 'deleteAll',
CLEAR: 'clear'
}
};

const generateActionType = (storeType, entityName, verb) =>
`REDEUCE:${storeType}@@${entityName}@@${verb}`;
export const generateActionTypes = (storeType, entityId) =>
Object.keys(VERBS[storeType]).reduce(
(actionTypes, verb) => ({
...actionTypes,
[verb]: generateActionType(storeType, entityId, verb)
}),
{}
);

const generateActionCreator = type => payload => ({ type, payload });
export const generateActionCreators = (storeType, entityId) => {
const actionTypes = generateActionTypes(storeType, entityId);
return Object.keys(VERBS[storeType]).reduce(
(actionCreators, verb) => ({
...actionCreators,
[VERBS[storeType][verb]]: generateActionCreator(actionTypes[verb])
}),
{}
);
};
101 changes: 101 additions & 0 deletions src/generators/reducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const sortByKeyName = keyName => (a, b) => a[keyName] > b[keyName];
const getKeyNameValues = (entities, keyName) =>
entities.map(({ [keyName]: id }) => id);

const filterByKeyname = (entities, keyName) =>
entities.filter(({ [keyName]: id }) => id !== undefined);

const filterWithoutIndexes = (entities, indexes, keyName) =>
entities.filter(({ [keyName]: id }) => indexes.indexOf(id) === -1);

const filterByIndexes = (entities, indexes, keyName) =>
entities.filter(({ [keyName]: id }) => indexes.indexOf(id) > -1);

const findOneByIndex = (entities, keyName, uniqueId) =>
entities.find(({ [keyName]: id }) => id === uniqueId);

export const buildReducer = (reducers, defaultValue) => (
state = defaultValue,
{ type, payload }
) => (reducers[type] ? reducers[type](state, payload) : state);

const asArray = o => [].concat(o);

export const buildCollectionReducers = (actionTypes, keyName) => {
const {
SET,
UPDATE,
DELETE,
MERGE,
MERGEDEEP,
DELETEALL,
CLEAR
} = actionTypes;

// Reducer to SET a list of objects in the store
const setReducer = (state, payload) => {
const filteredPayload = filterByKeyname(asArray(payload), keyName);
const setIndexes = getKeyNameValues(filteredPayload, keyName);

return [
// the list of objects from the previous state that are not to be replaced
...filterWithoutIndexes(state, setIndexes, keyName),
// the list of new objects
...filteredPayload
].sort(sortByKeyName(keyName));
};

// Reducer to UPDATE a list of objects in the store
const updateReducer = (state, payload) => {
const filteredPayload = filterByKeyname(asArray(payload), keyName);
const updateIndexes = getKeyNameValues(filteredPayload, keyName);
const existingIndexes = getKeyNameValues(state, keyName);

return [
// the list of objects from the previous state that are not to be updated
...filterWithoutIndexes(state, updateIndexes, keyName),
// the list of objects from the previous state that have to be updated
// mapped to be merged with the new version
...filterByIndexes(state, updateIndexes, keyName).map(obj => ({
...obj,
...findOneByIndex(filteredPayload, keyName, obj[keyName])
})),
// the list new objects that are not existing in the previous state
...filterWithoutIndexes(filteredPayload, existingIndexes, keyName)
].sort(sortByKeyName(keyName));
};

// Reducer to DELETE a list of objects in the store
const deleteReducer = (state, payload) => {
const filteredPayload = filterByKeyname(asArray(payload), keyName);
const setIndexes = getKeyNameValues(filteredPayload, keyName);

return [
// the list of objects from the previous state that are not to be deleted
...filterWithoutIndexes(state, setIndexes, keyName)
].sort(sortByKeyName(keyName));
};

const clearReducer = () => [];

return {
[SET]: setReducer,
[UPDATE]: updateReducer,
[DELETE]: deleteReducer,
[MERGE]: setReducer,
[MERGEDEEP]: updateReducer,
[DELETEALL]: deleteReducer,
[CLEAR]: clearReducer
};
};

export const buildSimpleReducer = actionTypes => {
const { SET } = actionTypes;

// Reducer to SET a list of objects in the store
const setReducer = (_, payload) => payload;

return {
[SET]: setReducer
};
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as generateCollectionStore } from './collectionStore';
export { default as generateSimpleStore } from './simpleStore';
14 changes: 14 additions & 0 deletions src/simpleStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { memoize, makeCallable } from './tools';

import { buildReducer, buildSimpleReducer } from './generators/reducers';
import { SIMPLE, generateActionTypes, generateActionCreators } from './generators/actions';

const generateSimpleStore = (entityName, { defaultValue = undefined } = {}) => {
const actionTypes = generateActionTypes(SIMPLE, entityName);
const actionCreators = generateActionCreators(SIMPLE, entityName);

const reducer = buildReducer(buildSimpleReducer(actionTypes), defaultValue);

return makeCallable(reducer, actionCreators);
};
export default memoize(generateSimpleStore);
2 changes: 2 additions & 0 deletions src/tools/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as makeCallable } from './makeCallable';
export { default as memoize } from './memoize';
7 changes: 7 additions & 0 deletions src/tools/makeCallable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default (reducer, actionCreators) => {
const fn = () => ({ reducer, ...actionCreators });
fn.getReducer = () => reducer;
fn.getActionCreators = () => actionCreators;

return fn;
};
16 changes: 16 additions & 0 deletions src/tools/memoize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default function memoize(fn) {
memoize.cache = {};
return (...args) => {
const key = JSON.stringify(args[0]);
if (memoize.cache[key]) {
if (JSON.stringify(args) !== JSON.stringify(memoize.cache[key].args)) {
throw new Error('boo');
}
return memoize.cache[key].val;
} else {
const val = fn(...args);
memoize.cache[key] = { val, args };
return val;
}
};
}
Loading

0 comments on commit 04b6a2a

Please sign in to comment.