Skip to content

Commit

Permalink
Fixes index signatures on state
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlplusb committed Jul 23, 2019
1 parent 55375dc commit 7ce3188
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 53 deletions.
60 changes: 33 additions & 27 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function thunkFailName(action: Thunk<any, any, any, any, any>): string;

// #region Actions

type ActionMapper<ActionsModel extends object, Depth extends number> = {
type ActionMapper<ActionsModel extends object, Depth extends string> = {
[P in keyof ActionsModel]: ActionsModel[P] extends Thunk<
any,
any,
Expand All @@ -77,23 +77,23 @@ type ActionMapper<ActionsModel extends object, Depth extends number> = {
: ActionsModel[P] extends object
? RecursiveActions<
ActionsModel[P],
Depth extends 1
? 2
: Depth extends 2
? 3
: Depth extends 3
? 4
: Depth extends 4
? 5
: 6
Depth extends '1'
? '2'
: Depth extends '2'
? '3'
: Depth extends '3'
? '4'
: Depth extends '4'
? '5'
: '6'
>
: unknown;
};

type RecursiveActions<
Model extends Object,
Depth extends number
> = Depth extends 6
Depth extends string
> = Depth extends '6'
? Model
: ActionMapper<
O.Filter<
Expand All @@ -114,13 +114,13 @@ type RecursiveActions<
*
* type OnlyActions = Actions<Model>;
*/
export type Actions<Model extends Object> = RecursiveActions<Model, 1>;
export type Actions<Model extends Object> = RecursiveActions<Model, '1'>;

// #endregion

// #region State

type StateMapper<StateModel extends object, Depth extends number> = {
type StateMapper<StateModel extends object, Depth extends string> = {
[P in keyof StateModel]: StateModel[P] extends Computed<any, any, any, any>
? StateModel[P]['result']
: StateModel[P] extends Reducer<any, any>
Expand All @@ -130,25 +130,31 @@ type StateMapper<StateModel extends object, Depth extends number> = {
? StateModel[P]
: RecursiveState<
StateModel[P],
Depth extends 1
? 2
: Depth extends 2
? 3
: Depth extends 3
? 4
: Depth extends 4
? 5
: 6
Depth extends '1'
? '2'
: Depth extends '2'
? '3'
: Depth extends '3'
? '4'
: Depth extends '4'
? '5'
: '6'
>
: StateModel[P];
};

type RecursiveState<
Model extends object,
Depth extends number
> = Depth extends 6
Depth extends string
> = Depth extends '6'
? Model
: StateMapper<O.Filter<Model, ActionTypes, 'default'>, Depth>;
: O.Merge<
StateMapper<
O.Omit<O.Filter<Model, ActionTypes>, IndexSignatureKeysOfType<Model>>,
Depth
>,
O.Pick<Model, IndexSignatureKeysOfType<Model>>
>;

/**
* Filters a model into a type that represents the state only (i.e. no actions)
Expand All @@ -157,7 +163,7 @@ type RecursiveState<
*
* type StateOnly = State<Model>;
*/
export type State<Model extends object> = RecursiveState<Model, 1>;
export type State<Model extends object> = RecursiveState<Model, '1'>;

// #endregion

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "easy-peasy",
"version": "2.6.6",
"version": "3.0.0-alpha.0",
"description": "Easy peasy state for React",
"license": "MIT",
"main": "dist/easy-peasy.cjs.js",
Expand Down
57 changes: 57 additions & 0 deletions src/__tests__/typescript/generic-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { O } from 'ts-toolbelt';

/**
* ARRANGE
*/

interface Person {
name: string;
}

type StateMapper<Model extends object> = {
[P in keyof Model]: Model[P];
};

type State<Model extends object> = StateMapper<O.Filter<Model, Action<any>>>;

type Action<Model extends object> = (state: State<Model>) => void;

/**
* WORKING CASE - "NORMAL" MODEL INTERFACE
*/

interface NormalModel {
data: Person[];
fetch: Action<NormalModel>;
}

const normalModel: NormalModel = {
data: [],
fetch: state => {
// 👍 works
state.data[0].name;
},
};

/**
* BROKEN CASE - "GENERIC" MODEL INTERFACE
*/

interface ObjectWithId {
id: string;
}

interface GenericModel<Item extends ObjectWithId> {
data: Item[];
fetch: Action<GenericModel<Item>>;
}

const createModel = <Item extends ObjectWithId>(): GenericModel<Item> => {
return {
data: [],
fetch: state => {
// 👇 broken
state.data;
},
};
};
44 changes: 19 additions & 25 deletions src/__tests__/typescript/issue224.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,76 +11,70 @@ import {
} from 'easy-peasy';

interface ObjectWithId {
id: number;
id: string;
}

interface Nested {
save: Thunk<Nested>;
save: Thunk<Nested, number>;
}

interface DataModel<DataItem extends ObjectWithId> {
data: Record<string, DataItem>;
sortBy: keyof DataItem | 'none';
data: { [key: number]: DataItem };
sortBy: 'none' | string;
name: string;
ids: Computed<DataModel<DataItem>, string[]>;
ids: Computed<DataModel<DataItem>, number[]>;
fetched: Action<DataModel<DataItem>, DataItem[]>;
fetch: Thunk<DataModel<DataItem>, string>;
getItemById: Computed<
DataModel<DataItem>,
(id: number) => DataItem | undefined
(id: string) => DataItem | undefined
>;
nested: Nested;
}

const dataModel = <Item extends ObjectWithId>(
name: string,
endpoint: () => Promise<Item[]>,
// @ts-ignore
): DataModel<Item> => {
/*
return {
const result: DataModel<Item> = {
data: {},
sortBy: 'none',
name,
ids: computed(state => Object.keys(state.data)),
ids: computed(state => Object.keys(state.data).map(id => parseInt(id))),
fetched: action((state, items) => {
state.name;
items.forEach((item, idx) => {
state.data[idx] = item;
});
84656;
}),
fetch: thunk(async (actions, payload) => {
const data = await endpoint();
actions.fetched(data);
// Nested actions do not work on generics :(
// typings:expect-error
actions.nested.save();
actions.nested.save(1);
}),
getItemById: computed(state => (id: number) =>
getItemById: computed(state => (id: string) =>
Object.values(state.data).find(item => item.id === id),
),
sortBy: 'id',
nested: {
save: thunk(() => {}),
save: thunk((actions, payload) => {
actions.save(payload + 1);
}),
},
};
*/
return result;
};

interface Person extends ObjectWithId {
id: number;
id: string;
name: string;
}

const personModel = dataModel<Person>('person', () =>
Promise.resolve([{ id: 1, name: 'bob' }]),
Promise.resolve([{ id: '1', name: 'bob' }]),
);

const store = createStore(personModel);

store.getState().sortBy;

store.getActions().fetched([]);
store.getActions().data;
// typings:expect-error
store.getActions().sortBy;
store.getActions().nested.save();
store.getActions().nested.save(1);

0 comments on commit 7ce3188

Please sign in to comment.