Skip to content

Commit

Permalink
Ensure mutators don't return a type through a build time error instea…
Browse files Browse the repository at this point in the history
…d of a run time error (#140)

* Ensure mutators don't return a type through a build time error instead of a run time error
  • Loading branch information
jarmit authored Mar 20, 2020
1 parent ed5a2f6 commit e964670
Show file tree
Hide file tree
Showing 11 changed files with 26 additions and 43 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "satcheljs",
"version": "4.2.0",
"version": "4.2.1",
"description": "Store implementation for functional reactive flux.",
"lint-staged": {
"*.{ts,tsx}": [
Expand All @@ -17,7 +17,7 @@
"test:unit": "jest",
"build": "run-s clean build:source",
"start": "run-s clean watch",
"test": "run-s lint test:unit",
"test": "run-s lint build test:unit",
"test:start": "jest --watch"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Subscriber from './interfaces/Subscriber';
import { getPrivateActionId } from './actionCreator';
import { getGlobalContext } from './globalContext';

export function subscribe(actionId: string, callback: Subscriber<any>) {
export function subscribe(actionId: string, callback: Subscriber<any, any>) {
let subscriptions = getGlobalContext().subscriptions;
if (!subscriptions[actionId]) {
subscriptions[actionId] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/globalContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface GlobalContext {
schemaVersion: number;
rootStore: ObservableMap<any>;
nextActionId: number;
subscriptions: { [key: string]: Subscriber<ActionMessage>[] };
subscriptions: { [key: string]: Subscriber<ActionMessage, void>[] };
dispatchWithMiddleware: DispatchFunction;
currentMutator: string | null;

Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/MutatorFunction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import ActionMessage from './ActionMessage';

type MutatorFunction<T extends ActionMessage> = (actionMessage: T) => void;
// if the return type is void, then it can be a function. Or else it should never happen.
// this is how we ensure that all functions passed in have a return type of void
type MutatorFunction<TAction extends ActionMessage, TReturn> = void extends TReturn
? (actionMessage: TAction) => TReturn
: never;
export default MutatorFunction;
5 changes: 1 addition & 4 deletions src/interfaces/SimpleAction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
interface SimpleAction {
(...args: any[]): void;
}

type SimpleAction<TReturn> = void extends TReturn ? (...args: any[]) => TReturn : never;
export default SimpleAction;
4 changes: 3 additions & 1 deletion src/interfaces/Subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ import ActionMessage from './ActionMessage';
import MutatorFunction from './MutatorFunction';
import OrchestratorFunction from './OrchestratorFunction';

type Subscriber<T extends ActionMessage> = MutatorFunction<T> | OrchestratorFunction<T>;
type Subscriber<TAction extends ActionMessage, TReturn> =
| MutatorFunction<TAction, TReturn>
| OrchestratorFunction<TAction>;
export default Subscriber;
14 changes: 6 additions & 8 deletions src/mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ import { getPrivateActionType, getPrivateActionId } from './actionCreator';
import { subscribe } from './dispatcher';
import { getGlobalContext } from './globalContext';

export default function mutator<T extends ActionMessage>(
actionCreator: ActionCreator<T>,
target: MutatorFunction<T>
): MutatorFunction<T> {
export default function mutator<TAction extends ActionMessage, TReturn>(
actionCreator: ActionCreator<TAction>,
target: MutatorFunction<TAction, TReturn>
): MutatorFunction<TAction, TReturn> {
let actionId = getPrivateActionId(actionCreator);
if (!actionId) {
throw new Error('Mutators can only subscribe to action creators.');
}

// Wrap the callback in a MobX action so it can modify the store
let wrappedTarget = action(getPrivateActionType(actionCreator), (actionMessage: T) => {
let wrappedTarget = action(getPrivateActionType(actionCreator), (actionMessage: TAction) => {
try {
getGlobalContext().currentMutator = actionCreator.name;
if (target(actionMessage) as any) {
throw new Error('Mutators cannot return a value and cannot be async.');
}
target(actionMessage);
} finally {
getGlobalContext().currentMutator = null;
}
Expand Down
7 changes: 5 additions & 2 deletions src/simpleSubscribers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { action } from './actionCreator';
import mutator from './mutator';

export function createSimpleSubscriber(decorator: Function) {
return function simpleSubscriber<T extends SimpleAction>(actionType: string, target: T): T {
return function simpleSubscriber<TReturn>(
actionType: string,
target: SimpleAction<TReturn>
): SimpleAction<TReturn> {
// Create the action creator
let simpleActionCreator = action(actionType, function simpleActionCreator() {
return {
Expand All @@ -17,7 +20,7 @@ export function createSimpleSubscriber(decorator: Function) {
});

// Return a function that dispatches that action
return (simpleActionCreator as any) as T;
return (simpleActionCreator as any) as SimpleAction<TReturn>;
};
}

Expand Down
4 changes: 2 additions & 2 deletions test/endToEndTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('satcheljs', () => {
});

// Create a mutator that subscribes to it
mutator(testAction, function(actionMessage) {
mutator<any, void>(testAction, function(actionMessage: any) {
actualValue = actionMessage.value;
});

Expand All @@ -43,7 +43,7 @@ describe('satcheljs', () => {
let arg1Value;
let arg2Value;

let testMutatorAction = mutatorAction('testMutatorAction', function testMutatorAction(
let testMutatorAction = mutatorAction<void>('testMutatorAction', function testMutatorAction(
arg1: string,
arg2: number
) {
Expand Down
12 changes: 0 additions & 12 deletions test/mutatorTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,6 @@ describe('mutator', () => {
expect(returnValue).toBe(callback);
});

it('throws if the target function is async', () => {
// Arrange
let actionCreator: any = { __SATCHELJS_ACTION_ID: 'testAction' };
let callback = async () => {};

mutator(actionCreator, callback);
let subscribedCallback = (dispatcher.subscribe as jasmine.Spy).calls.argsFor(0)[1];

// Act / Assert
expect(subscribedCallback).toThrow();
});

it('sets the currentMutator to actionMessage type for the duration of the mutator callback', () => {
// Arrange
let actionCreator: any = { __SATCHELJS_ACTION_ID: 'testAction', name: 'testName' };
Expand Down
9 changes: 0 additions & 9 deletions test/simpleSubscribersTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,4 @@ describe('simpleSubscribers', () => {
// Assert
expect(callback).toHaveBeenCalledWith(1, 2, 3);
});

it('throws if the target function is async', () => {
// Arrange
let callback = async () => {};
let actionCreator = mutatorAction('testMutator', callback);

// Act / Assert
expect(actionCreator).toThrow();
});
});

0 comments on commit e964670

Please sign in to comment.