Merge branch 'subdux-initial' into typescript

This commit is contained in:
Yanick Champoux 2023-03-07 19:07:21 -05:00
commit 93533a739f
6 changed files with 126 additions and 64 deletions

View File

@ -8,23 +8,16 @@ import {
import { import {
configureStore, configureStore,
Reducer, Reducer,
createAction,
ActionCreator, ActionCreator,
PrepareAction,
ActionCreatorWithPayload,
ActionCreatorWithoutPayload, ActionCreatorWithoutPayload,
ActionCreatorWithPreparedPayload, ActionCreatorWithPreparedPayload,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { withPayload } from './actions.js'; import { AggregateActions, Dux } from './types.js';
import { AggregateActions, Dux, UnionToIntersection } from './types.js';
import { buildActions } from './buildActions.js'; import { buildActions } from './buildActions.js';
import { buildInitial, AggregateState } from './initial.js';
type AggregateState<L> = L;
type MyActionCreator = { type: string } & ((...args: any) => any); type MyActionCreator = { type: string } & ((...args: any) => any);
type F = null extends any ? 'u' : 'n';
type ResolveAction< type ResolveAction<
ActionType extends string, ActionType extends string,
ActionArg extends any, ActionArg extends any,
@ -59,33 +52,37 @@ export default class Updux<
T_LocalActions extends { T_LocalActions extends {
[actionType: string]: any; [actionType: string]: any;
} = {}, } = {},
SUBDUXES extends Record<string, Dux> = {}, T_Subduxes extends Record<string, Dux> = {},
> { > {
#localInitial: T_LocalState; #localInitial: T_LocalState;
#localActions: T_LocalActions; #localActions: T_LocalActions;
#localMutations: Record< #localMutations: Record<
string, string,
Mutation<ActionCreator<any>, AggregateState<T_LocalState>> Mutation<ActionCreator<any>, AggregateState<T_LocalState, T_Subduxes>>
> = {}; > = {};
#subduxes: SUBDUXES; #subduxes: T_Subduxes;
#name: string; #name: string;
#actions: AggregateActions<ResolveActions<T_LocalActions>, SUBDUXES>; #actions: AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>;
#initial: AggregateState<T_LocalState, T_Subduxes>;
constructor( constructor(
config: Partial<{ config: Partial<{
initial: T_LocalState; initial: T_LocalState;
actions: T_LocalActions; actions: T_LocalActions;
subduxes: SUBDUXES; subduxes: T_Subduxes;
}>, }>,
) { ) {
// TODO check that we can't alter the initial after the fact // TODO check that we can't alter the initial after the fact
this.#localInitial = config.initial ?? ({} as T_LocalState); this.#localInitial = config.initial ?? ({} as T_LocalState);
this.#localActions = config.actions ?? ({} as T_LocalActions); this.#localActions = config.actions ?? ({} as T_LocalActions);
this.#subduxes = config.subduxes ?? ({} as SUBDUXES); this.#subduxes = config.subduxes ?? ({} as T_Subduxes);
this.#actions = buildActions(this.#localActions, this.#subduxes); this.#actions = buildActions(this.#localActions, this.#subduxes);
this.#initial = buildInitial(this.#localInitial, this.#subduxes);
} }
get actions() { get actions() {
@ -94,7 +91,7 @@ export default class Updux<
// TODO memoize? // TODO memoize?
get initial() { get initial() {
return this.#localInitial; return this.#initial;
} }
createStore( createStore(
@ -114,7 +111,7 @@ export default class Updux<
// TODO force the actionCreator to be one of the actions? // TODO force the actionCreator to be one of the actions?
mutation<A extends ActionCreator<any>>( mutation<A extends ActionCreator<any>>(
actionCreator: A, actionCreator: A,
mutation: Mutation<A, AggregateState<T_LocalState>>, mutation: Mutation<A, AggregateState<T_LocalState, T_Subduxes>>,
) { ) {
this.#localMutations[(actionCreator as any).type] = mutation; this.#localMutations[(actionCreator as any).type] = mutation;
} }

21
src/buildInitial.test.ts Normal file
View File

@ -0,0 +1,21 @@
import { test, expect } from 'vitest';
import { buildInitial } from './initial.js';
test('basic', () => {
expect(
buildInitial(
{ a: 1 },
{ b: { initial: { c: 2 } }, d: { initial: 'e' } },
),
).toEqual({
a: 1,
b: { c: 2 },
d: 'e',
});
});
test('throw if subduxes and initial is not an object', () => {
expect(() => {
buildInitial(3, { bar: 'foo' });
}).toThrow();
});

View File

@ -1,46 +0,0 @@
import { test, expect } from 'vitest';
import { Updux } from './Updux.js';
const bar = new Updux({ initial: 123 });
const foo = new Updux({
initial: { root: 'abc' },
subduxes: {
bar,
},
});
test('single dux', () => {
const foo = new Updux({
initial: { a: 1 },
});
expect(foo.initial).toEqual({ a: 1 });
});
test('initial value', () => {
expect(foo.initial).toEqual({
root: 'abc',
bar: 123,
});
});
test('splat initial', async () => {
const bar = new Updux({
initial: { id: 0 },
});
const foo = new Updux({
subduxes: { '*': bar },
});
expect(foo.initial).toEqual([]);
expect(
new Updux({
initial: 'overriden',
subduxes: { '*': bar },
}).initial,
).toEqual('overriden');
});

View File

@ -1,5 +1,15 @@
import { expectType } from './tutorial.test.js';
import Updux from './Updux.js'; import Updux from './Updux.js';
const bar = new Updux({ initial: 123 });
const foo = new Updux({
initial: { root: 'abc' },
subduxes: {
bar,
},
});
test('default', () => { test('default', () => {
const { initial } = new Updux({}); const { initial } = new Updux({});
@ -29,3 +39,60 @@ test('initial to createStore', () => {
b: 4, b: 4,
}); });
}); });
test('single dux', () => {
const foo = new Updux({
initial: { a: 1 },
});
expect(foo.initial).toEqual({ a: 1 });
});
// TODO add 'check for no todo eslint rule'
test('initial value', () => {
expect(foo.initial).toEqual({
root: 'abc',
bar: 123,
});
expectType<{
root: string;
bar: number;
}>(foo.initial);
});
test('no initial', () => {
const dux = new Updux({});
expectType<{}>(dux.initial);
expect(dux.initial).toEqual({});
});
test('no initial for subdux', () => {
const dux = new Updux({
subduxes: {
bar: new Updux({}),
baz: new Updux({ initial: 'potato' }),
},
});
expectType<{ bar: {}; baz: string }>(dux.initial);
expect(dux.initial).toEqual({ bar: {}, baz: 'potato' });
});
test.todo('splat initial', async () => {
const bar = new Updux({
initial: { id: 0 },
});
const foo = new Updux({
subduxes: { '*': bar },
});
expect(foo.initial).toEqual([]);
expect(
new Updux({
initial: 'overriden',
subduxes: { '*': bar },
}).initial,
).toEqual('overriden');
});

23
src/initial.ts Normal file
View File

@ -0,0 +1,23 @@
import u from '@yanick/updeep-remeda';
import * as R from 'remeda';
type SubduxState<S> = 'initial' extends keyof S ? S['initial'] : {};
export type AggregateState<LOCAL, SUBDUXES extends Record<any, any>> = LOCAL &
(keyof SUBDUXES extends never
? {}
: {
[Slice in keyof SUBDUXES]: Slice extends string
? SubduxState<SUBDUXES[Slice]>
: never;
});
export function buildInitial(localInitial, subduxes) {
if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') {
throw new Error(
"can't have subduxes when the initial value is not an object",
);
}
return u(localInitial, R.mapValues(subduxes, R.pathOr(['initial'], {})));
}

View File

@ -1,7 +1,7 @@
import Updux, { createAction, withPayload } from './index.js'; import Updux, { createAction, withPayload } from './index.js';
import u from '@yanick/updeep-remeda'; import u from '@yanick/updeep-remeda';
const expectType = <T>(value: T) => value; export const expectType = <T>(value: T) => value;
test('initial state', () => { test('initial state', () => {
const initial = { const initial = {