Skip to content

Commit

Permalink
Stronger scoped transform type enforcement
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmackrodt committed Jan 8, 2019
1 parent 9fb2b4f commit dce4010
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 54 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('utils', () => {
},
};

const result = setScope(state, { name: 'Another' }, 'level1');
const result = setScope(state, { name: 'Another' }, ['level1']);
expect(result).toEqual({
level1: {
name: 'Another',
Expand All @@ -27,7 +27,7 @@ describe('utils', () => {
},
};

const result = setScope(state, { name: 'Another' }, 'level1.level2');
const result = setScope(state, { name: 'Another' }, ['level1', 'level2']);
expect(result).toEqual({
level1: {
level2: {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import staat from './staat';
import { isPromise, getScope } from './utils';
export {
Transformer,
Transformers,
Subscription,
Staat,
IType,
StateContainerType,
} from './types';
export * from './scoped-transformer';
Expand Down
35 changes: 15 additions & 20 deletions packages/core/src/scoped-transformer.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { isPromise, getScope, setScope } from './utils';
import { getScope, isPromise, setScope } from './utils';
import { IScopedTransformer } from './types';

export function scopedTransformer<
TState extends Record<keyof TState, unknown>,
TScope
>(scope: string) {
return function<TArgs extends any[]>(
definition: (
currentScope: TScope,
...args: TArgs
) => TScope | Promise<TScope>,
) {
return function(currentState: TState, ...args: TArgs) {
const s = getScope<TState, TScope>(currentState, scope);
const result = definition(s, ...args);
if (isPromise(result)) {
return result.then(promiseResult =>
setScope({ ...currentState }, promiseResult, scope),
);
}
return setScope({ ...currentState }, result, scope);
export function scopedTransformer<TState>(): IScopedTransformer<TState> {
return function scopedFn(scope: string[]) {
return function(definition: (currentScope: any, ...args: any) => unknown) {
return function(currentState: any, ...args: any) {
const s = getScope<TState, any>(currentState, scope);
const result = definition(s, ...args);
if (isPromise(result)) {
return result.then(promiseResult =>
setScope({ ...currentState }, promiseResult, scope),
);
}
return setScope({ ...currentState }, result, scope);
};
};
};
}
76 changes: 61 additions & 15 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export type IType<T> = new (...args: any[]) => T;
export type Transformer<TState, TArgs extends any[]> = (
currentState: TState,
...args: TArgs
) => TState | Promise<TState>;

export type Transformers<TTransformers extends {}> = {
[TKey in keyof TTransformers]: TTransformers[TKey] extends (
currentState: infer TState,
...args: infer TArgs
) => unknown
? (...args: TArgs) => Promise<TState>
? Transformer<TState, TArgs>
: TTransformers[TKey]
};

Expand All @@ -14,20 +17,12 @@ export type TransformersTree<TTransformers extends {}> = {
currentState: infer TState,
...args: infer TArgs
) => unknown
? (...args: TArgs) => Promise<TState>
? Transformer<TState, TArgs>
: TTransformers[TKey] extends {}
? TransformersTree<TTransformers[TKey]>
: TTransformers[TKey]
};

export type TimeTravelTransformers<
TState,
TTransformers extends {}
> = Transformers<TTransformers> & {
undo(): Promise<TState>;
redo(): Promise<TState>;
};

export type StateContainerType<TState> = {
currentState: TState;
subscribe(fn: Subscription): void;
Expand All @@ -39,11 +34,62 @@ export type Staat<TState, TTransformers> = StateContainerType<TState> &

export type Subscription = () => Promise<void>;

export type TransformerSignature<TState> = (
currentState: TState,
...args: any[]
) => TState;
export type TransformerSignature<TState> = Transformer<TState, any[]>;

export type TransformerOrObject<TState> =
| TransformerSignature<TState>
| Record<string, TransformerSignature<TState>>;

/*tslint:disable callable-types*/
export interface IScopedTransformerFactory<TState, TScope> {
<TArgs extends any[]>(
definition: (
currentScope: TScope,
...args: TArgs
) => TScope | Promise<TScope>,
): (currentState: TState, ...args: TArgs) => TState | Promise<TState>;
}

export interface IScopedTransformer<TState> {
<
K1 extends keyof TState,
K2 extends keyof TState[K1],
K3 extends keyof TState[K1][K2],
K4 extends keyof TState[K1][K2][K3],
K5 extends keyof TState[K1][K2][K3][K4]
>(
prop1: K1,
prop2: K2,
prop3: K3,
prop4: K4,
prop5: K5,
): IScopedTransformerFactory<TState, TState[K1][K2][K3][K4][K5]>;
<
K1 extends keyof TState,
K2 extends keyof TState[K1],
K3 extends keyof TState[K1][K2],
K4 extends keyof TState[K1][K2][K3]
>(
prop1: K1,
prop2: K2,
prop3: K3,
prop4: K4,
): IScopedTransformerFactory<TState, TState[K1][K2][K3][K4]>;
<
K1 extends keyof TState,
K2 extends keyof TState[K1],
K3 extends keyof TState[K1][K2]
>(
prop1: K1,
prop2: K2,
prop3: K3,
): IScopedTransformerFactory<TState, TState[K1][K2][K3]>;
<K1 extends keyof TState, K2 extends keyof TState[K1]>(
prop1: K1,
prop2: K2,
): IScopedTransformerFactory<TState, TState[K1][K2]>;
<K1 extends keyof TState>(prop1: K1): IScopedTransformerFactory<
TState,
TState[K1]
>;
}
10 changes: 4 additions & 6 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ export function setProperty<TScope, TState extends Record<string, any>>(
export function setScope<TScope, TState extends Record<string, any>>(
state: TState,
scope: TScope,
path: string,
path: string[],
): TState {
const parts = path.split('.');
return setProperty(state, parts, scope);
return setProperty(state, path, scope);
}

export function getProperty<TScope, TState extends Record<string, any>>(
Expand All @@ -61,8 +60,7 @@ export function getProperty<TScope, TState extends Record<string, any>>(

export function getScope<TState extends Record<string, any>, TScope>(
state: TState,
path: string,
path: string[],
): TScope {
const parts = path.split('.');
return getProperty(state, parts);
return getProperty(state, path);
}
21 changes: 11 additions & 10 deletions packages/time-travel/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ function udpateHistory<TState>(
currentState: TState,
newState: TState,
container: TimeTravelContainer<TState>,
scope?: string,
scope?: keyof TState,
): TState {
if (scope) {
const newScope = internals.getScope<TState, any>(newState, scope);
const currentScope = internals.getScope<TState, TState>(
currentState,
scope,
);
const newScope = internals.getScope<TState, any>(newState, [
scope as string,
]);
const currentScope = internals.getScope<TState, TState>(currentState, [
scope as string,
]);
container.setPresent(currentScope, newScope);
} else {
container.setPresent(currentState, newState);
Expand All @@ -28,7 +29,7 @@ function udpateHistory<TState>(
function createTimeTravelTransformer<TState, TArgs extends any[]>(
transformer: Transformer<TState, TArgs>,
container: TimeTravelContainer<TState>,
scope?: string,
scope?: keyof TState,
) {
return (currentState: TState, ...args: TArgs) => {
const result = transformer(currentState, ...args);
Expand Down Expand Up @@ -59,7 +60,7 @@ export function timeTravelTransformers<
>(
transformers: TTransformers,
container: TimeTravelContainer<TState>,
scope?: string,
scope?: keyof TState,
): TimeTravelTransformers<TState, TTransformers> {
const newTransformers: TimeTravelTransformers<
TState,
Expand All @@ -78,7 +79,7 @@ export function timeTravelTransformers<
{} as Record<keyof TTransformers, Transformer<TState, any[]>>,
) as TimeTravelTransformers<TState, TTransformers>;

const scoped = scope ? scopedTransformer<TState, TState>(scope) : undefined;
const scoped = scope ? scopedTransformer<TState>()(scope) : undefined;

newTransformers.undo = scoped
? scoped(createUndo(container))
Expand All @@ -95,7 +96,7 @@ export function timeTravel<
TTransformers extends Record<keyof TTransformers, Transformer<any, any[]>>
>(
transformers: TTransformers,
scope?: string,
scope?: keyof TState,
): TimeTravelTransformers<TState, TTransformers> {
const container = new TimeTravelContainer<TState>();
return timeTravelTransformers(transformers, container, scope);
Expand Down

0 comments on commit dce4010

Please sign in to comment.