redux-code
Version:
Redux helpers for actions and reducers
179 lines (178 loc) • 7.13 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectEntitiesTotal = exports.selectEntityById = exports.selectEntities = exports.entitiesReducer = exports.entitiesActions = void 0;
exports.entitiesActions = {
/**
* Creates an action to add a new entity to the store.
* @param {entity} entity The entity to add.
*/
addOne: (entity) => entity,
/**
* Creates an action to add multiple entities to the store.
* @param {entities} entities The entities to add.
*/
addMany: (entities) => entities,
/**
* Creates an action to update an entity in the store.
* @param {entity} entity The entity to update.
*/
updateOne: (entity) => entity,
/**
* Creates an action to update multiple entities in the store.
* @param {entities} entities The entities to update.
*/
updateMany: (entities) => entities,
/**
* Creates an action to insert or update an entity in the store.
* @param {entity} entity The entity to insert or update.
*/
upsertOne: (entity) => entity,
/**
* Create an action to insert or update multiple entities in the store.
* @param {entities} entities The entities to insert or update.
*/
upsertMany: (entities) => entities,
/**
* Creates an action to replace an entity in the store.
* @param {entity} entity The entity to replace.
*/
setOne: (entity) => entity,
/**
* Creates an action to replace multiple entities in the store.
* @param {entities} entities The entities to replace.
*/
setMany: (entities) => entities,
/**
* Creates an action to replace all entities in the store.
* @param {entities} entities The entities to replace.
*/
setAll: (entities) => entities,
/**
* Creates an action to remove an entity from the store.
* @param {entity} entity The entity to remove.
*/
removeOne: (entity) => entity,
/**
* Creates an action to remove multiple entities from the store.
* @param {entities} entities The entities to remove.
*/
removeMany: (entities) => entities,
/**
* Creates an action to remove all entities from the store.
*/
removeAll: () => undefined,
};
/**
* Create a mixin for a reducer that adds the ability to handle entities.
* @param {actions} actions The actions to handle.
* @param {config} config The configuration.
*/
const entitiesReducer = (actions, { processEntity = (entity) => entity, selectId = (entity) => entity.id, sortComparer, updateComparer = (a, b) => a === b, } = {}) => {
function merge(id, entity, entities) {
const source = entities[id];
if (updateComparer(source, entity))
return entities;
return { ...entities, [id]: { ...source, ...entity } };
}
function sorter(ids, entities) {
if (!sortComparer)
return ids;
return Object.values(entities).sort(sortComparer).map(selectId);
}
function addMany(items, state) {
const entities = Object.fromEntries([
...Object.entries(state.entities),
...items.map((entity) => [selectId(entity), processEntity(entity)]),
]);
return {
...state,
entities,
ids: sorter([...state.ids, ...items.map(selectId)], entities),
};
}
function updateMany(updates, state) {
let entities = state.entities;
for (let entity of updates) {
const id = selectId(entity);
if (!(id in entities))
continue;
const source = entities[id];
entity = processEntity(entity);
if (!updateComparer(entity, source))
entities = { ...entities, [id]: { ...source, ...entity } };
}
if (state.entities === entities)
return state;
return {
...state,
entities,
ids: sorter(state.ids, entities),
};
}
function setMany(items, state, strategy = replace) {
let ids = state.ids;
let entities = state.entities;
for (const entity of items) {
const id = selectId(entity);
if (id in entities)
entities = strategy(id, processEntity(entity), entities);
else {
ids = [...ids, id];
entities = { ...entities, [id]: entity };
}
}
return { ...state, ids: sorter(ids, entities), entities };
}
function removeMany(items, state) {
const ids = items.map(selectId);
return {
...state,
ids: state.ids.filter((id) => !ids.includes(id)),
entities: Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(state.entities).filter(([id, _]) => !ids.includes(id))),
};
}
return {
[actions.addOne.type]: (state, { payload }) => addMany([payload], state),
[actions.addMany.type]: (state, { payload }) => addMany(payload, state),
[actions.removeOne.type]: (state, { payload }) => removeMany([payload], state),
[actions.removeMany.type]: (state, { payload }) => removeMany(payload, state),
[actions.removeAll.type]: (state) => ({ ...state, ids: [], entities: {} }),
[actions.updateOne.type]: (state, { payload }) => updateMany([payload], state),
[actions.updateMany.type]: (state, { payload }) => updateMany(payload, state),
[actions.upsertOne.type]: (state, { payload }) => setMany([payload], state, merge),
[actions.upsertMany.type]: (state, { payload }) => setMany(payload, state, merge),
[actions.setOne.type]: (state, { payload }) => setMany([payload], state),
[actions.setMany.type]: (state, { payload }) => setMany(payload, state),
[actions.setAll.type]: (state, { payload }) => {
const items = payload.map((item) => {
const id = selectId(item);
return updateComparer(item, state.entities[id]) ? state.entities[id] : item;
});
return addMany(items, { ...state, ids: [], entities: {} });
},
};
};
exports.entitiesReducer = entitiesReducer;
/**
* Select the entities from the state.
* @param {state} state The state.
*/
const selectEntities = (state) => state.ids.map((id) => state.entities[id]);
exports.selectEntities = selectEntities;
/**
* Select the entity with the given id from the state.
* @param {state} state The state.
*/
const selectEntityById = (state, id) => state.entities[id];
exports.selectEntityById = selectEntityById;
/**
* Select the total number of entities in the state.
* @param {state} state The state.
*/
const selectEntitiesTotal = (state) => state.ids.length;
exports.selectEntitiesTotal = selectEntitiesTotal;
// const merge = (id, entity, entities) => ({ ...entities, [id]: { ...entities[id], ...entity } })
const replace = (id, entity, entities) => ({ ...entities, [id]: entity });