Merge branch 'subdux-initial' into typescript
This commit is contained in:
commit
93533a739f
31
src/Updux.ts
31
src/Updux.ts
@ -8,23 +8,16 @@ import {
|
||||
import {
|
||||
configureStore,
|
||||
Reducer,
|
||||
createAction,
|
||||
ActionCreator,
|
||||
PrepareAction,
|
||||
ActionCreatorWithPayload,
|
||||
ActionCreatorWithoutPayload,
|
||||
ActionCreatorWithPreparedPayload,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { withPayload } from './actions.js';
|
||||
import { AggregateActions, Dux, UnionToIntersection } from './types.js';
|
||||
import { AggregateActions, Dux } from './types.js';
|
||||
import { buildActions } from './buildActions.js';
|
||||
|
||||
type AggregateState<L> = L;
|
||||
import { buildInitial, AggregateState } from './initial.js';
|
||||
|
||||
type MyActionCreator = { type: string } & ((...args: any) => any);
|
||||
|
||||
type F = null extends any ? 'u' : 'n';
|
||||
|
||||
type ResolveAction<
|
||||
ActionType extends string,
|
||||
ActionArg extends any,
|
||||
@ -59,33 +52,37 @@ export default class Updux<
|
||||
T_LocalActions extends {
|
||||
[actionType: string]: any;
|
||||
} = {},
|
||||
SUBDUXES extends Record<string, Dux> = {},
|
||||
T_Subduxes extends Record<string, Dux> = {},
|
||||
> {
|
||||
#localInitial: T_LocalState;
|
||||
#localActions: T_LocalActions;
|
||||
#localMutations: Record<
|
||||
string,
|
||||
Mutation<ActionCreator<any>, AggregateState<T_LocalState>>
|
||||
Mutation<ActionCreator<any>, AggregateState<T_LocalState, T_Subduxes>>
|
||||
> = {};
|
||||
#subduxes: SUBDUXES;
|
||||
#subduxes: T_Subduxes;
|
||||
|
||||
#name: string;
|
||||
|
||||
#actions: AggregateActions<ResolveActions<T_LocalActions>, SUBDUXES>;
|
||||
#actions: AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>;
|
||||
|
||||
#initial: AggregateState<T_LocalState, T_Subduxes>;
|
||||
|
||||
constructor(
|
||||
config: Partial<{
|
||||
initial: T_LocalState;
|
||||
actions: T_LocalActions;
|
||||
subduxes: SUBDUXES;
|
||||
subduxes: T_Subduxes;
|
||||
}>,
|
||||
) {
|
||||
// TODO check that we can't alter the initial after the fact
|
||||
this.#localInitial = config.initial ?? ({} as T_LocalState);
|
||||
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.#initial = buildInitial(this.#localInitial, this.#subduxes);
|
||||
}
|
||||
|
||||
get actions() {
|
||||
@ -94,7 +91,7 @@ export default class Updux<
|
||||
|
||||
// TODO memoize?
|
||||
get initial() {
|
||||
return this.#localInitial;
|
||||
return this.#initial;
|
||||
}
|
||||
|
||||
createStore(
|
||||
@ -114,7 +111,7 @@ export default class Updux<
|
||||
// TODO force the actionCreator to be one of the actions?
|
||||
mutation<A extends ActionCreator<any>>(
|
||||
actionCreator: A,
|
||||
mutation: Mutation<A, AggregateState<T_LocalState>>,
|
||||
mutation: Mutation<A, AggregateState<T_LocalState, T_Subduxes>>,
|
||||
) {
|
||||
this.#localMutations[(actionCreator as any).type] = mutation;
|
||||
}
|
||||
|
21
src/buildInitial.test.ts
Normal file
21
src/buildInitial.test.ts
Normal 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();
|
||||
});
|
@ -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');
|
||||
});
|
@ -1,5 +1,15 @@
|
||||
import { expectType } from './tutorial.test.js';
|
||||
import Updux from './Updux.js';
|
||||
|
||||
const bar = new Updux({ initial: 123 });
|
||||
|
||||
const foo = new Updux({
|
||||
initial: { root: 'abc' },
|
||||
subduxes: {
|
||||
bar,
|
||||
},
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
const { initial } = new Updux({});
|
||||
|
||||
@ -29,3 +39,60 @@ test('initial to createStore', () => {
|
||||
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
23
src/initial.ts
Normal 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'], {})));
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import Updux, { createAction, withPayload } from './index.js';
|
||||
import u from '@yanick/updeep-remeda';
|
||||
|
||||
const expectType = <T>(value: T) => value;
|
||||
export const expectType = <T>(value: T) => value;
|
||||
|
||||
test('initial state', () => {
|
||||
const initial = {
|
||||
|
Loading…
Reference in New Issue
Block a user