Advertisement

#23 Using A Fluent Interface To Validate The Vuex Getters

In this article we will start by adding a couple of new functions that we can use to find individual items within our store by their id as well as all of the items in a particular state. These new functions will just make finding items within our vuex store just a little bit cleaner. We will then turn our attention to creating a new class, that implements a fluent interface, that we can use to validate the actions that we perform on our store. In particular we will see how to throw an error if we attempt to use a vuex getter to retrieve an item by id and the result of that retrieval is undefined.

Code Snippets

functions.ts (2:10)

interface IStateItem {
    id: number;
}

interface IState<T extends IStateItem> {
    index: number;
    items: T[];
}

export function findAll<T extends IStateItem>(state: IState<T>) {
    return state.items;
}

export function findById<T extends IStateItem>(state: IState<T>, id: number) {
    return state.items.find((x) => x.id === id);
}
src ⟩ store ⟩ functions.ts

store-constants.ts (4:14)

...
export const GETTER_SECURITY_MARKET      = "GETTER_SECURITY_MARKET";
export const GETTER_SECURITY_MARKETS     = "GETTER_SECURITY_MARKETS";
export const GETTER_SECURITY_SEGMENT     = "GETTER_SECURITY_SEGMENT";
export const GETTER_SECURITY_SEGMENTS    = "GETTER_SECURITY_SEGMENTS";
export const GETTER_SECURITY_TERRITORIES = "GETTER_SECURITY_TERRITORIES";
export const GETTER_SECURITY_TERRITORY   = "GETTER_SECURITY_TERRITORY";
export const GETTER_SECURITY_TYPE        = "GETTER_SECURITY_TYPE";
export const GETTER_SECURITY_TYPES       = "GETTER_SECURITY_TYPES";
...
src ⟩ store ⟩ store-constants.ts

security-types.ts (4:47)

import {
    ...
    GETTER_SECURITY_MARKET,
    GETTER_SECURITY_MARKETS,
    GETTER_SECURITY_SEGMENT,
    GETTER_SECURITY_SEGMENTS,
    GETTER_SECURITY_TERRITORIES,
    GETTER_SECURITY_TERRITORY,
    GETTER_SECURITY_TYPE,
    GETTER_SECURITY_TYPES,
    ...
} from "@/store/store-constants";
...
export type GetterCategory = (id: number) => SecurityCategoryModel;
export type GetterMarket = (id: number) => SecurityMarketModel;
export type GetterSecurity = (id: number) => SecurityModel;
export type GetterSegment = (id: number) => SecuritySegmentModel;
export type GetterTerritory = (id: number) => SecurityTerritoryModel;
export type GetterType = (id: number) => SecurityTypeModel;
...
export interface ISecurityGetters {
    [GETTER_SECURITIES]: SecurityModel[];
    [GETTER_SECURITY]: GetterSecurity;
    [GETTER_SECURITY_CATEGORIES]: SecurityCategoryModel[];
    [GETTER_SECURITY_CATEGORY]: GetterCategory;
    [GETTER_SECURITY_MARKET]: GetterMarket;
    [GETTER_SECURITY_MARKETS]: SecurityMarketModel[];
    [GETTER_SECURITY_SEGMENT]: GetterSegment;
    [GETTER_SECURITY_SEGMENTS]: SecuritySegmentModel[];
    [GETTER_SECURITY_TERRITORIES]: SecurityTerritoryModel[];
    [GETTER_SECURITY_TERRITORY]: GetterTerritory;
    [GETTER_SECURITY_TYPE]: GetterType;
    [GETTER_SECURITY_TYPES]: SecurityTypeModel[];
}
...
src ⟩ store ⟩ security-types.ts

security-module.ts (6:06)

import {
    ...
    GETTER_SECURITY_MARKET,
    GETTER_SECURITY_MARKETS,
    GETTER_SECURITY_SEGMENT,
    GETTER_SECURITY_SEGMENTS,
    GETTER_SECURITY_TERRITORIES,
    GETTER_SECURITY_TERRITORY,
    GETTER_SECURITY_TYPE,
    GETTER_SECURITY_TYPES,
    ...
} from "@/store/store-constants";

import {
    findAll,
    findById,
} from "@/store/functions";
...
export const securitiesGetters: StoreGetterTree = {
    ...
    [GETTER_SECURITY]: (state: IStoreState, getters: ISecurityGetters) => {
        return (id: number) => {
            const security = findById(state[STATE_SECURITIES], id);

            const category = getters[GETTER_SECURITY_CATEGORY](security!.categoryId);
            const market = getters[GETTER_SECURITY_MARKET](security!.marketId);

            return security!
                .setCategory(category)
                .setMarket(market);
        };
    },
    ...
    [GETTER_SECURITY_CATEGORY]: (state: IStoreState, getters: ISecurityGetters) => {
        return (id: number) => {
            const category = findById(state[STATE_SECURITY_CATEGORIES], id);

            const segment = getters[GETTER_SECURITY_SEGMENT](category!.segmentId);
            const territory = getters[GETTER_SECURITY_TERRITORY](category!.territoryId);
            const type = getters[GETTER_SECURITY_TYPE](category!.typeId);

            return category!
                .setSegment(segment!)
                .setTerritory(territory!)
                .setType(type!);
        };
    },
    [GETTER_SECURITY_MARKET]: (state: IStoreState) => {
        return (id: number) => {
            const market = findById(state[STATE_SECURITY_MARKETS], id);

            return market;
        };
    },
    [GETTER_SECURITY_MARKETS]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_MARKETS]);
    },
    [GETTER_SECURITY_SEGMENT]: (state: IStoreState) => {
        return (id: number) => {
            const segment = findById(state[STATE_SECURITY_SEGMENTS], id);

            return segment;
        };
    },
    [GETTER_SECURITY_SEGMENTS]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_SEGMENTS]);
    },
    [GETTER_SECURITY_TERRITORIES]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_TERRITORIES]);
    },
    [GETTER_SECURITY_TERRITORY]: (state: IStoreState) => {
        return (id: number) => {
            const territory = findById(state[STATE_SECURITY_TERRITORIES], id);

            return territory;
        };
    },
    [GETTER_SECURITY_TYPE]: (state: IStoreState) => {
        return (id: number) => {
            const type = findById(state[STATE_SECURITY_TYPES], id);

            return type;
        };
    },
    [GETTER_SECURITY_TYPES]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_TYPES]);
    },
};
...
src ⟩ store ⟩ security-module.ts

Advertisement

Securities.vue (10:36)

<script lang="tsx">
...
import {
    ...
    GETTER_SECURITY_MARKETS,
    GETTER_SECURITY_SEGMENTS,
    GETTER_SECURITY_TERRITORIES,
    GETTER_SECURITY_TYPES,
    ...
    SecurityMarketModel,
    ...
    SecuritySegmentModel,
    SecurityTerritoryModel,
    SecurityTypeModel,
    ...
} from "@/store";
...
export default class Securities extends Vue {
    @Getter(GETTER_SECURITY_CATEGORIES) private readonly categories!: SecurityCategoryModel[];
    @Getter(GETTER_SECURITY_MARKETS) private readonly markets!: SecurityMarketModel[];
    @Getter(GETTER_SECURITIES) private readonly securities!: SecurityModel[];
    @Getter(GETTER_SECURITY_SEGMENTS) private readonly segments!: SecuritySegmentModel[];
    @Getter(GETTER_SECURITY_TERRITORIES) private readonly territories!: SecurityTerritoryModel[];
    @Getter(GETTER_SECURITY_TYPES) private readonly types!: SecurityTypeModel[];
    ...
}
</script>
src ⟩ views ⟩ Securities.vue

store-action-validator.ts (12:19)

export enum StoreActions {
    None,
    Creating,
    Getting,
    Reading,
    Updating,
    Deleting,
}

type ItemType = object | undefined;

interface IItem {
    error: string;
    message: string;
}

interface IStoreActionValidatorA {
    begin: () => IStoreActionValidatorB;
}

interface IStoreActionValidatorB {
    while: (action: StoreActions) => IStoreActionValidatorC;
}

interface IStoreActionValidatorC {
    throwIf: (item: ItemType) => IStoreActionValidatorD;
}

interface IStoreActionValidatorD {
    isUndefined: (message: string) => IStoreActionValidatorB;
}

export class StoreActionValidator implements IStoreActionValidatorA, IStoreActionValidatorB,
    IStoreActionValidatorC, IStoreActionValidatorD {
    private _action = StoreActions.None;
    private _item: ItemType | null = null;

    public begin = (): IStoreActionValidatorB => {
        return this;
    }

    public isUndefined = (message: string): IStoreActionValidatorB => {
        if (this._item === null) {
            throw new Error("You must specify the item.");
        }

        const item: IItem = {
            error: "undefined",
            message,
        };

        if (typeof(this._item) === "undefined") {
            this.failureResponse(item);
        }

        return this;
    }

    public throwIf = (item: ItemType): IStoreActionValidatorD => {
        this._item = item;
        this.failureResponse = this.throw;
        return this;
    }

    public while = (action: StoreActions): IStoreActionValidatorC => {
        this._action = action;
        return this;
    }

    private failureResponse: (item: IItem) => void = () => { return; };
    private failureMessage = (item: IItem) => {
        const action = StoreActions[this._action];
        return `Failed [${action}] ${item.message}. The result of the operation was <${item.error}>.`;
    }
    private throw = (item: IItem) => {
        throw new Error(this.failureMessage(item));
    }
}

// const storeActionValidator = new StoreActionValidator();
// storeActionValidator
//     .begin()
//     .while(StoreActions.Getting)
//     .throwIf(security)
//     .isUndefined(undefinedMessage("security", id, state[STATE_SECURITIES].index));
src ⟩ store ⟩ store-action-validator.ts

functions.ts (23:33)

...
export function undefinedMessage(entityName: string, id: number, stateIndex: number) {
    return id < stateIndex
        ? `${entityName} with id ${id}`
        : `${entityName} with id ${id}. The id is greater than or equal to the state index of ${stateIndex}`;
}
src ⟩ store ⟩ functions.ts

security-module.ts (24:16)

import {
    StoreActions,
    StoreActionValidator,
} from "@/store/store-action-validator";

import {
    ...
    undefinedMessage,
} from "@/store/functions";
...
const storeActionValidator = new StoreActionValidator();

export const securitiesGetters: StoreGetterTree = {
    [GETTER_SECURITIES]: (state: IStoreState, getters: ISecurityGetters) => {
        return state[STATE_SECURITIES].items.map((x) => getters[GETTER_SECURITY](x.id));
    },
    [GETTER_SECURITY]: (state: IStoreState, getters: ISecurityGetters) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(security)
                .isUndefined(undefinedMessage("security", id, state[STATE_SECURITIES].index));
            ...
        };
    },
    [GETTER_SECURITY_CATEGORIES]: (state: IStoreState, getters: ISecurityGetters) => {
        return state[STATE_SECURITY_CATEGORIES].items.map((x) => getters[GETTER_SECURITY_CATEGORY](x.id));
    },
    [GETTER_SECURITY_CATEGORY]: (state: IStoreState, getters: ISecurityGetters) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(category)
                .isUndefined(undefinedMessage("category", id, state[STATE_SECURITY_CATEGORIES].index));
            ...
        };
    },
    [GETTER_SECURITY_MARKET]: (state: IStoreState) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(market)
                .isUndefined(undefinedMessage("market", id, state[STATE_SECURITY_MARKETS].index));
            ...
        };
    },
    [GETTER_SECURITY_MARKETS]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_MARKETS]);
    },
    [GETTER_SECURITY_SEGMENT]: (state: IStoreState) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(segment)
                .isUndefined(undefinedMessage("segment", id, state[STATE_SECURITY_SEGMENTS].index));
            ...
        };
    },
    [GETTER_SECURITY_SEGMENTS]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_SEGMENTS]);
    },
    [GETTER_SECURITY_TERRITORIES]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_TERRITORIES]);
    },
    [GETTER_SECURITY_TERRITORY]: (state: IStoreState) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(territory)
                .isUndefined(undefinedMessage("territory", id, state[STATE_SECURITY_TERRITORIES].index));
            ...
        };
    },
    [GETTER_SECURITY_TYPE]: (state: IStoreState) => {
        return (id: number) => {
            ...
            storeActionValidator
                .begin()
                .while(StoreActions.Getting)
                .throwIf(type)
                .isUndefined(undefinedMessage("type", id, state[STATE_SECURITY_TYPES].index));
            ...
        };
    },
    [GETTER_SECURITY_TYPES]: (state: IStoreState) => {
        return findAll(state[STATE_SECURITY_TYPES]);
    },
};
src ⟩ store ⟩ security-module.ts

Exciton Interactive LLC
Advertisement