diff --git a/src/Updux.ts b/src/Updux.ts index d275700..abb2155 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -41,11 +41,13 @@ type ResolveActions< : never; }; -type Mutation = ActionCreator, S = any> = ( - state: S, +export type Mutation< + A extends ActionCreator = ActionCreator, + S = any, +> = ( payload: ReturnType['payload'], action: ReturnType, -) => S | void; +) => (state: S) => S | void; export default class Updux< T_LocalState = Record, diff --git a/src/reducer.test.todo b/src/reducer.test.todo deleted file mode 100644 index 2b5ff18..0000000 --- a/src/reducer.test.todo +++ /dev/null @@ -1,19 +0,0 @@ -import { test, expect } from 'vitest'; - -import { Updux } from './Updux.js'; - -test('basic reducer', () => { - const dux = new Updux({ initial: { a: 3 } }); - - expect(dux.reducer).toBeTypeOf('function'); - - expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop -}); - -test('basic upreducer', () => { - const dux = new Updux({ initial: { a: 3 } }); - - expect(dux.upreducer).toBeTypeOf('function'); - - expect(dux.upreducer({ type: 'foo' })({ a: 1 })).toMatchObject({ a: 1 }); // noop -}); diff --git a/src/reducer.test.ts b/src/reducer.test.ts new file mode 100644 index 0000000..9ce0ba8 --- /dev/null +++ b/src/reducer.test.ts @@ -0,0 +1,31 @@ +import { test, expect } from 'vitest'; + +import { buildReducer } from './reducer.js'; +import Updux from './Updux.js'; + +test('buildReducer, initial state', () => { + const reducer = buildReducer({ a: 1 }); + + expect(reducer(undefined, { type: 'foo' })).toEqual({ a: 1 }); +}); + +test('buildReducer, mutation', () => { + const reducer = buildReducer(1, [ + { + matcher: ({ type }) => type === 'inc', + mutation: () => (state) => state + 1, + terminal: false, + }, + ]); + + expect(reducer(undefined, { type: 'foo' })).toEqual(1); + expect(reducer(undefined, { type: 'inc' })).toEqual(2); +}); + +test.todo('basic reducer', () => { + const dux = new Updux({ initial: { a: 3 } }); + + expect(dux.reducer).toBeTypeOf('function'); + + expect(dux.reducer({ a: 1 }, { type: 'foo' })).toMatchObject({ a: 1 }); // noop +}); diff --git a/src/reducer.ts b/src/reducer.ts new file mode 100644 index 0000000..0d87941 --- /dev/null +++ b/src/reducer.ts @@ -0,0 +1,79 @@ +import { Action, ActionCreator, createAction } from '@reduxjs/toolkit'; +import { BaseActionCreator } from '@reduxjs/toolkit/dist/createAction.js'; +import * as R from 'remeda'; +import { Dux } from './types.js'; +import { Mutation } from './Updux.js'; + +type MutationCase = { + matcher: (action: Action) => boolean; + mutation: Mutation; + terminal: boolean; +}; + +export function buildReducer( + initialState: any, + mutations: MutationCase[] = [], + subduxes: Record = {}, +) { + // const subReducers = + // ? R.mapValues(subduxes, R.prop('reducer')); + + // TODO matcherMutation + // TODO defaultMutation + + const reducer = (state = initialState, action: Action) => { + if (!action?.type) + throw new Error('upreducer called with a bad action'); + + 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; + + // TODO wrap mutations in immer + state = localMutation.mutation( + (action as any).payload, + action, + )(state); + } + + // TODO defaultMutation + + return state; + }; + + return reducer; + + /* + if (subReducers) { + if (subduxes['*']) { + newState = u.updateIn( + '*', + subduxes['*'].upreducer(action), + newState, + ); + } else { + const update = mapValues(subReducers, (upReducer) => + upReducer(action), + ); + + newState = u(update, newState); + } + } + + const a = mutations[action.type] || mutations['+']; + + if (!a) return newState; + + return a(action.payload, action)(newState); + }; + + return wrapper ? wrapper(upreducer) : upreducer; + */ +}