Source: Vrac/index.js

/* global VraOptions */

import {
    cacheMultiple as cacheMultipleDefault,
    cacheSingle as cacheSingleDefault,
    cacheDestroy as cacheDestroyDefault,
    cacheBinary as cacheBinaryDefault,
} from './cachers/index';

import Vra from '../Vra/index';

/**
 * Vrac only class initilisation options.
 *
 * @typedef {object} VracOnlyOptions
 * @property {Function} cacheSingle - Function to cache a single item returned from the API.
 * @property {Function} cacheMultiple - Function to cache multiple items returned from the API.
 * @property {Function} cacheDestroy - Function to remove an item from the cache after a `destroy` call.
 * @property {Function} cacheBinary - Function to cache cache a binary item returned from the API.
 * @property {boolean} singleton - Whether this is a singleton endpoint or not.
 */

/**
 * Vrac and Vra combined class initilisation options.
 *
 * @typedef {VracOnlyOptions | VraOptions} VracOptions
 */

/**
 * Vuex Rest API Cacher class.
 *
 * Generates Vuex stores based on the supplied config. The stores have actions
 * for fetching API data, mutators for caching them and getters for accessing
 * the cache.
 */
class Vrac extends Vra {
    /**
     * Instantiate the class.
     *
     * @param {VracOptions} options
     */
    constructor(options = {}) {
        super(options);

        const {
            cacheMultiple = cacheMultipleDefault,
            cacheSingle = cacheSingleDefault,
            cacheDestroy = cacheDestroyDefault,
            cacheBinary = cacheBinaryDefault,
            singleton = false,
        } = options;

        this.cacheMultiple = cacheMultiple;
        this.cacheSingle = cacheSingle;
        this.cacheDestroy = cacheDestroy;
        this.cacheBinary = cacheBinary;

        this.modifyCall('create', {
            cacher: cacheSingle,
        });

        this.modifyCall('read', {
            readCache: !singleton,
            cacher: cacheSingle,
        });

        this.modifyCall('update', {
            cacher: cacheSingle,
        });

        this.modifyCall('destroy', {
            cacher: cacheDestroy,
        });
    }

    /**
     * Get the default cacher.
     *
     * @param {object} options
     * @param {boolean} options.identified - Whether this is a cacher for an identified API call or not.
     * @param {boolean} options.binary - Whether this is a cacher for a binary API call or not.
     *
     * @returns {Function}
     */
    getCacher({ identified, binary }) {
        if (binary) {
            return this.cacheBinary;
        }

        return identified ? this.cacheSingle : this.cacheMultiple;
    }

    /**
     * Get the default state for this module.
     *
     * @returns {object} State.
     */
    get state() {
        return () => ({
            index: [],
            actionsLoading: {},
        });
    }

    /**
     * Get the mutators for this module.
     *
     * @returns {object} Mutators.
     */
    get mutations() {
        return {
            createOrUpdate: (state, model) => {
                state.index = [
                    ...state.index.filter(m => m[this.identifier] !== model[this.identifier]),
                    model,
                ];
            },

            destroy: (state, model) => {
                state.index = state.index.filter(
                    m => m[this.identifier] !== model[this.identifier],
                );
            },

            loading: (state, action) => {
                state.actionsLoading = Object.assign(
                    {},
                    state.actionsLoading,
                    { [action]: (state.actionsLoading[action] || 0) + 1 },
                );
            },

            loaded: (state, action) => {
                state.actionsLoading = Object.assign(
                    {},
                    state.actionsLoading,
                    { [action]: state.actionsLoading[action] - 1 },
                );
            },
        };
    }

    /**
     * Get the getters for this module.
     *
     * @returns {object} Getters.
     */
    get getters() {
        return {
            index: ({ index }) => index,

            read: (state, getters) => identifier => getters.index.find(
                m => m[this.identifier] === identifier,
            ),

            loading: ({ actionsLoading }) => {
                return Object.keys(actionsLoading).some(k => actionsLoading[k]);
            },
        };
    }

    /**
     * Get the actions for this module.
     *
     * @returns {object} Actions.
     */
    get actions() {
        const actions = super.actions;
        const self = this;

        Object.keys(actions).forEach(name => {
            const call = this.getCall(name);
            const cacher = call.cacher || this.getCacher(call);
            const action = actions[name];

            actions[name] = async function(context, options = {}) {
                const {
                    readCache = call.readCache,
                    fields = {},
                } = options;

                if (call.identified) {
                    if (readCache) {
                        const model = context.getters.read(fields[self.identifier]);

                        if (model) {
                            return self.createModel(model, call);
                        }
                    }
                } else if (readCache) {
                    const cachedModels = context.getters.index;

                    if (cachedModels.length) {
                        return cachedModels.map(m => self.createModel(m, call));
                    }
                }

                try {
                    context.commit('loading', name);

                    const model = await action.call(this, context, options);

                    cacher(context, model);

                    return model;
                } finally {
                    context.commit('loaded', name);
                }
            };
        });

        return actions;
    }

    /**
     * Get the entire store for this module, for Vuex.
     *
     * @returns {object} Store.
     */
    get store() {
        const { getters, mutations, state } = this;

        return {
            ...super.store,
            getters,
            mutations,
            state,
        };
    }
}

export default Vrac;