mutation generic matcher
This commit is contained in:
parent
4c28a9ad05
commit
2e03a51e15
55
src/Updux.ts
55
src/Updux.ts
@ -15,6 +15,7 @@ import {
|
||||
import { AggregateActions, Dux } from './types.js';
|
||||
import { buildActions } from './buildActions.js';
|
||||
import { buildInitial, AggregateState } from './initial.js';
|
||||
import { buildReducer } from './reducer.js';
|
||||
|
||||
type MyActionCreator = { type: string } & ((...args: any) => any);
|
||||
|
||||
@ -41,12 +42,13 @@ type ResolveActions<
|
||||
: never;
|
||||
};
|
||||
|
||||
export type Mutation<
|
||||
A extends ActionCreator<any> = ActionCreator<any>,
|
||||
S = any,
|
||||
> = (
|
||||
payload: ReturnType<A>['payload'],
|
||||
action: ReturnType<A>,
|
||||
export type Mutation<A extends Action<any> = Action<any>, S = any> = (
|
||||
payload: A extends {
|
||||
payload: infer P;
|
||||
}
|
||||
? P
|
||||
: undefined,
|
||||
action: A,
|
||||
) => (state: S) => S | void;
|
||||
|
||||
export default class Updux<
|
||||
@ -58,10 +60,7 @@ export default class Updux<
|
||||
> {
|
||||
#localInitial: T_LocalState;
|
||||
#localActions: T_LocalActions;
|
||||
#localMutations: Record<
|
||||
string,
|
||||
Mutation<ActionCreator<any>, AggregateState<T_LocalState, T_Subduxes>>
|
||||
> = {};
|
||||
#localMutations: MutationCase[] = [];
|
||||
#subduxes: T_Subduxes;
|
||||
|
||||
#name: string;
|
||||
@ -110,11 +109,37 @@ export default class Updux<
|
||||
return store;
|
||||
}
|
||||
|
||||
// TODO force the actionCreator to be one of the actions?
|
||||
mutation<A extends ActionCreator<any>>(
|
||||
actionCreator: A,
|
||||
// TODO memoize this sucker
|
||||
get reducer() {
|
||||
return buildReducer(this.initial, this.#localMutations) as any as (
|
||||
state: undefined | typeof this.initial,
|
||||
action: Action,
|
||||
) => typeof this.initial;
|
||||
}
|
||||
|
||||
addMutation<A extends Action<any>>(
|
||||
matcher: (action: A) => boolean,
|
||||
mutation: Mutation<A, AggregateState<T_LocalState, T_Subduxes>>,
|
||||
) {
|
||||
this.#localMutations[(actionCreator as any).type] = mutation;
|
||||
terminal?: boolean,
|
||||
);
|
||||
addMutation<A extends ActionCreator<any>>(
|
||||
actionCreator: A,
|
||||
mutation: Mutation<
|
||||
ReturnType<A>,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>,
|
||||
terminal?: boolean,
|
||||
);
|
||||
addMutation(matcher, mutation, terminal = false) {
|
||||
if (typeof matcher === 'function' && matcher.match) {
|
||||
// matcher, matcher man...
|
||||
matcher = matcher.match;
|
||||
}
|
||||
|
||||
this.#localMutations.push({
|
||||
terminal,
|
||||
matcher,
|
||||
mutation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import schema from 'json-schema-shorthand';
|
||||
import u from 'updeep';
|
||||
|
||||
@ -6,31 +5,6 @@ import { action } from './actions.js';
|
||||
|
||||
import { Updux, dux } from './Updux.js';
|
||||
|
||||
test('set a mutation', () => {
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
x: 'potato',
|
||||
},
|
||||
actions: {
|
||||
foo: action('foo', (x) => ({ x })),
|
||||
bar: action('bar'),
|
||||
},
|
||||
});
|
||||
|
||||
dux.setMutation(dux.actions.foo, (payload, action) => {
|
||||
expect(payload).toEqual({ x: 'hello ' });
|
||||
expect(action).toEqual(dux.actions.foo('hello '));
|
||||
return u({
|
||||
x: payload.x + action.type,
|
||||
});
|
||||
});
|
||||
|
||||
const result = dux.reducer(undefined, dux.actions.foo('hello '));
|
||||
expect(result).toEqual({
|
||||
x: 'hello foo',
|
||||
});
|
||||
});
|
||||
|
||||
test('mutation of a subdux', async () => {
|
||||
const bar = dux({
|
||||
actions: {
|
||||
@ -72,19 +46,3 @@ test('strings and generators', async () => {
|
||||
expect(foo.actions.d).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
test('splat mutation', () => {
|
||||
const myDux = new Updux({
|
||||
initial: [],
|
||||
actions: { one: null, two: null },
|
||||
mutations: {
|
||||
'*': (payload) => (state) => payload ? [...state, payload] : state,
|
||||
},
|
||||
});
|
||||
const store = myDux.createStore();
|
||||
expect(store.getState()).toEqual([]);
|
||||
|
||||
store.dispatch.one(11);
|
||||
store.dispatch.two(22);
|
||||
|
||||
expect(store.getState()).toEqual([11, 22]);
|
||||
});
|
||||
|
39
src/mutations.test.ts
Normal file
39
src/mutations.test.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { test, expect } from 'vitest';
|
||||
|
||||
import Updux from './index.js';
|
||||
|
||||
test('set a mutation', () => {
|
||||
const dux = new Updux({
|
||||
initial: 'potato',
|
||||
actions: {
|
||||
foo: (x) => ({ x }),
|
||||
bar: 0,
|
||||
},
|
||||
});
|
||||
|
||||
let didIt = false;
|
||||
|
||||
dux.addMutation(dux.actions.foo, (payload, action) => () => {
|
||||
didIt = true;
|
||||
expect(payload).toEqual({ x: 'hello ' });
|
||||
expect(action).toEqual(dux.actions.foo('hello '));
|
||||
return payload.x + action.type;
|
||||
});
|
||||
|
||||
const result = dux.reducer(undefined, dux.actions.foo('hello '));
|
||||
expect(didIt).toBeTruthy();
|
||||
expect(result).toEqual('hello foo');
|
||||
});
|
||||
|
||||
test('catch-all mutation', () => {
|
||||
const dux = new Updux({
|
||||
initial: '',
|
||||
});
|
||||
|
||||
dux.addMutation(
|
||||
() => true,
|
||||
(payload, action) => () => 'got it',
|
||||
);
|
||||
|
||||
expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it');
|
||||
});
|
@ -4,7 +4,7 @@ import * as R from 'remeda';
|
||||
import { Dux } from './types.js';
|
||||
import { Mutation } from './Updux.js';
|
||||
|
||||
type MutationCase = {
|
||||
export type MutationCase = {
|
||||
matcher: (action: Action) => boolean;
|
||||
mutation: Mutation;
|
||||
terminal: boolean;
|
||||
@ -20,7 +20,7 @@ export function buildReducer(
|
||||
|
||||
// TODO matcherMutation
|
||||
// TODO defaultMutation
|
||||
|
||||
//
|
||||
const reducer = (state = initialState, action: Action) => {
|
||||
if (!action?.type)
|
||||
throw new Error('upreducer called with a bad action');
|
||||
@ -28,20 +28,14 @@ export function buildReducer(
|
||||
let terminal = false;
|
||||
let didSomething = false;
|
||||
|
||||
const foo = createAction('foo');
|
||||
|
||||
const localMutation = mutations.find(({ matcher }) => matcher(action));
|
||||
|
||||
if (localMutation) {
|
||||
didSomething = true;
|
||||
if (localMutation.terminal) terminal = true;
|
||||
|
||||
mutations
|
||||
.filter(({ matcher }) => matcher(action))
|
||||
.forEach(({ mutation, terminal: t }) => {
|
||||
if (t) terminal = true;
|
||||
//
|
||||
// TODO wrap mutations in immer
|
||||
state = localMutation.mutation(
|
||||
(action as any).payload,
|
||||
action,
|
||||
)(state);
|
||||
}
|
||||
state = mutation((action as any).payload, action)(state);
|
||||
});
|
||||
|
||||
// TODO defaultMutation
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Action, ActionCreator } from 'redux';
|
||||
import { Action, ActionCreator, Reducer } from 'redux';
|
||||
|
||||
export type Dux<
|
||||
STATE = any,
|
||||
@ -6,6 +6,10 @@ export type Dux<
|
||||
> = Partial<{
|
||||
initial: STATE;
|
||||
actions: ACTIONS;
|
||||
reducer: (
|
||||
state: STATE,
|
||||
action: ReturnType<ACTIONS[keyof ACTIONS]>,
|
||||
) => STATE;
|
||||
}>;
|
||||
|
||||
type ActionsOf<DUX> = DUX extends { actions: infer A } ? A : {};
|
||||
|
Loading…
Reference in New Issue
Block a user