Merge branch 'validate-schema'

This commit is contained in:
Yanick Champoux 2023-09-07 09:57:37 -04:00
commit 8e16d9fef7
3 changed files with 39 additions and 1 deletions

View File

@ -2,6 +2,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@yanick/updeep-remeda": "^2.2.0", "@yanick/updeep-remeda": "^2.2.0",
"ajv": "^8.12.0",
"expect-type": "^0.16.0", "expect-type": "^0.16.0",
"immer": "^9.0.15", "immer": "^9.0.15",
"json-schema-shorthand": "^2.0.0", "json-schema-shorthand": "^2.0.0",

View File

@ -22,6 +22,7 @@ import {
EffectMiddleware, EffectMiddleware,
} from './effects.js'; } from './effects.js';
import buildSchema from './schema.js'; import buildSchema from './schema.js';
import Ajv from 'ajv';
export type Mutation<A = AnyAction, S = any> = ( export type Mutation<A = AnyAction, S = any> = (
payload: A extends { payload: A extends {
@ -32,6 +33,19 @@ export type Mutation<A = AnyAction, S = any> = (
action: A, action: A,
) => (state: S) => S | void; ) => (state: S) => S | void;
function buildValidateMiddleware(schema) {
// @ts-ignore
const ajv = new Ajv();
const validate = ajv.compile(schema);
return (api) => next => action => {
next(action);
const valid = validate(api.getState());
if (!valid) throw new Error("validation failed after action " + JSON.stringify(action) + "\n" + JSON.stringify(validate.errors));
}
}
export default class Updux<D extends DuxConfig> { export default class Updux<D extends DuxConfig> {
#mutations = []; #mutations = [];
@ -174,6 +188,7 @@ export default class Updux<D extends DuxConfig> {
createStore( createStore(
options: Partial<{ options: Partial<{
preloadedState: DuxState<D>; preloadedState: DuxState<D>;
validate: boolean;
}> = {}, }> = {},
): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D> { ): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D> {
const preloadedState = options.preloadedState; const preloadedState = options.preloadedState;
@ -184,10 +199,18 @@ export default class Updux<D extends DuxConfig> {
this.selectors, this.selectors,
); );
const middleware = [effects];
if (options.validate) {
middleware.unshift(
buildValidateMiddleware(this.schema)
)
}
const store = configureStore({ const store = configureStore({
reducer: this.reducer, reducer: this.reducer,
preloadedState, preloadedState,
middleware: [effects], middleware,
}); });
const dispatch: any = store.dispatch; const dispatch: any = store.dispatch;

View File

@ -29,3 +29,17 @@ test('schema default inherits from initial state', () => {
expect(dux.schema.default).toEqual(8); expect(dux.schema.default).toEqual(8);
}); });
test('validate', () => {
const dux = new Updux({
initialState: 12,
actions: {
doItWrong: null,
},
});
dux.addMutation('doItWrong', () => () => 'potato' as any);
const store = dux.createStore({ validate: true });
expect(() => store.dispatch.doItWrong()).toThrow();
});