From 0370e1293a0d460e2ce4493570de37bee1267c87 Mon Sep 17 00:00:00 2001 From: christianalfoni Date: Mon, 28 Jan 2019 20:56:55 +0100 Subject: [PATCH] feat(overmind): remove fromOperator as it causes typing issues BREAKING CHANGE: fromOperator is removed, use plain operators --- .../overmind-devtools/src/overmind/actions.ts | 14 ++--- packages/node_modules/overmind/src/index.ts | 48 +++++++--------- .../overmind/src/internalTypes.ts | 8 +++ .../overmind/src/operators.test.ts | 34 ++++------- .../examples/api/operators.ts | 8 +-- .../examples/api/operators_pipe.ts | 28 ++++------ .../guide/goingfunctional/actionoperator.ts | 16 ++---- .../goingfunctional/callactionoperator.ts | 22 +++----- .../examples/guide/goingfunctional/clean.ts | 44 +++++++-------- .../guides/intermediate/04_goingfunctional.md | 8 ++- .../overmind-website/src/overmind/actions.ts | 56 ++++++++++--------- .../src/overmind/operators.ts | 20 ------- .../overmind-website/src/overmind/types.ts | 2 +- 13 files changed, 133 insertions(+), 175 deletions(-) delete mode 100644 packages/overmind-website/src/overmind/operators.ts diff --git a/packages/node_modules/overmind-devtools/src/overmind/actions.ts b/packages/node_modules/overmind-devtools/src/overmind/actions.ts index 2f0d7118..da68c2c1 100644 --- a/packages/node_modules/overmind-devtools/src/overmind/actions.ts +++ b/packages/node_modules/overmind-devtools/src/overmind/actions.ts @@ -1,4 +1,4 @@ -import { Action, pipe, OnInitialize, Operator, fromOperator } from 'overmind' +import { Action, pipe, OnInitialize, Operator } from 'overmind' import { Message, Tab, @@ -69,13 +69,11 @@ const handleClientMessage: Operator = pipe( }) ) -export const onMessage: Action = fromOperator( - pipe( - isPortExistsMessage({ - true: setPortExists, - false: handleClientMessage, - }) - ) +export const onMessage: Operator = pipe( + isPortExistsMessage({ + true: setPortExists, + false: handleClientMessage, + }) ) export const setError: Action = ({ value: error, state }) => diff --git a/packages/node_modules/overmind/src/index.ts b/packages/node_modules/overmind/src/index.ts index 1f3e4c58..26f208b7 100644 --- a/packages/node_modules/overmind/src/index.ts +++ b/packages/node_modules/overmind/src/index.ts @@ -340,10 +340,24 @@ export class Overmind implements Configuration { this.eventHub.emit(EventType.ACTION_START, execution) action[IS_OPERATOR] - ? resolve( - action( - this.createContext(value, execution, this.proxyStateTree) - ) + ? action( + null, + { + value, + state: this.proxyStateTree.state, + actions: this.actions, + execution, + effects: this.trackEffects(this.effects, execution), + }, + (err, finalContext) => { + finalContext && + this.eventHub.emit(EventType.ACTION_END, { + ...finalContext.execution, + operatorId: finalContext.execution.operatorId - 1, + }) + if (err) reject(err) + else resolve(this.options.testMode && finalContext.execution) + } ) : resolve( action( @@ -613,28 +627,6 @@ export type Operator = IOperator< Output > -export function fromOperator( - operator: IOperator -): IAction { - const func = (context) => { - return new Promise((resolve, reject) => { - operator(null, context, (err, finalContext) => { - finalContext && - context.execution.emit(EventType.ACTION_END, { - ...finalContext.execution, - operatorId: finalContext.execution.operatorId - 1, - }) - if (err) reject(err) - else resolve() - }) - }) - } - - func[IS_OPERATOR] = true - - return func -} - export function pipe( aOperator: IOperator ): IOperator @@ -758,14 +750,14 @@ function stopDebugOperator(context, value) { value.then((promiseValue) => { context.execution.emit(EventType.OPERATOR_END, { ...context.execution, - result: promiseValue, + result: safeValue(promiseValue), isAsync: true, }) }) } else { context.execution.emit(EventType.OPERATOR_END, { ...context.execution, - result: value, + result: safeValue(value), isAsync: false, }) } diff --git a/packages/node_modules/overmind/src/internalTypes.ts b/packages/node_modules/overmind/src/internalTypes.ts index 1337b459..e6f7974d 100644 --- a/packages/node_modules/overmind/src/internalTypes.ts +++ b/packages/node_modules/overmind/src/internalTypes.ts @@ -174,6 +174,10 @@ export type ResolveActions< ? [TActionValue] extends [void] ? () => Promise : (value: TActionValue) => Promise + : Actions[T] extends IOperator + ? [TOperationValue] extends [void] + ? () => Promise + : (value: TOperationValue) => Promise : Actions[T] extends NestedActions ? ResolveActions : never @@ -199,6 +203,10 @@ export type ResolveMockActions< ? [TActionValue] extends [void] ? () => Promise : (value: TActionValue) => Promise + : Actions[T] extends IOperator + ? [TOperationValue] extends [void] + ? () => Promise + : (value: TOperationValue) => Promise : Actions[T] extends NestedMockActions ? ResolveMockActions : never diff --git a/packages/node_modules/overmind/src/operators.test.ts b/packages/node_modules/overmind/src/operators.test.ts index 2034c504..e5cef9d8 100644 --- a/packages/node_modules/overmind/src/operators.test.ts +++ b/packages/node_modules/overmind/src/operators.test.ts @@ -12,18 +12,16 @@ import { action, parallel, IConfig, - fromOperator, IAction, } from './' -describe.only('OPERATORS', () => { +describe('OPERATORS', () => { test('map', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( map(({ value }) => value.toUpperCase()), action(({ value, state }) => (state.foo = value)) ) - const test: Action = fromOperator(operator) const state = { foo: 'bar', @@ -51,13 +49,11 @@ describe.only('OPERATORS', () => { test('map (async)', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( map(({ value }) => Promise.resolve(value.toUpperCase())), action(({ value, state }) => (state.foo = value)) ) - const test: Action = fromOperator(operator) - const state = { foo: 'bar', } @@ -85,13 +81,12 @@ describe.only('OPERATORS', () => { test('forEach', () => { expect.assertions(1) let runCount = 0 - const operator: Operator = pipe( + const test: Operator = pipe( forEach((_, val, next) => { runCount++ next(null, val) }) ) - const test: Action = fromOperator(operator) const config = { actions: { @@ -115,7 +110,7 @@ describe.only('OPERATORS', () => { test('parallel', () => { expect.assertions(1) let runCount = 0 - const operator: Operator = pipe( + const test: Operator = pipe( parallel( (_, value, next) => { runCount++ @@ -127,7 +122,6 @@ describe.only('OPERATORS', () => { } ) ) - const test: Action = fromOperator(operator) const config = { actions: { @@ -150,12 +144,11 @@ describe.only('OPERATORS', () => { test('filter - truthy', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( filter(({ value }) => value === 'foo'), map(({ value }) => value.toUpperCase()), action(({ value, state }) => (state.foo = value)) ) - const test: Action = fromOperator(operator) const state = { foo: 'bar', @@ -181,12 +174,11 @@ describe.only('OPERATORS', () => { }) test('filter - falsy', () => { - const operator: Operator = pipe( + const test: Operator = pipe( filter(({ value }) => value === 'bar'), map(({ value }) => value.toUpperCase()), action(({ value, state }) => (state.foo = value)) ) - const test: Action = fromOperator(operator) const state = { foo: 'bar', @@ -213,7 +205,7 @@ describe.only('OPERATORS', () => { test('fork', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( fork(() => 'foo', { foo: pipe( map(({ value }) => value.toUpperCase()), @@ -221,7 +213,6 @@ describe.only('OPERATORS', () => { ), }) ) - const test: Action = fromOperator(operator) const state = { foo: 'bar', @@ -248,7 +239,7 @@ describe.only('OPERATORS', () => { test('when', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( when(() => true, { true: pipe( map(({ value }) => value.toUpperCase()), @@ -260,7 +251,6 @@ describe.only('OPERATORS', () => { ), }) ) - const test: Action = fromOperator(operator) const state = { foo: 'bar', @@ -289,8 +279,7 @@ describe.only('OPERATORS', () => { test('wait', () => { expect.assertions(1) const runTime = Date.now() - const operator: Operator = wait(500) - const test: Action = fromOperator(operator) + const test: Operator = wait(500) const config = { actions: { @@ -313,11 +302,10 @@ describe.only('OPERATORS', () => { test('debounce', () => { expect.assertions(1) - const operator: Operator = pipe( + const test: Operator = pipe( debounce(100), action(({ state }) => state.runCount++) ) - const test: Action = fromOperator(operator) const state = { runCount: 0, } diff --git a/packages/overmind-website/examples/api/operators.ts b/packages/overmind-website/examples/api/operators.ts index b93dd0c7..76f743a9 100644 --- a/packages/overmind-website/examples/api/operators.ts +++ b/packages/overmind-website/examples/api/operators.ts @@ -5,11 +5,9 @@ export default (ts) => code: ` import { Operator, action } from 'overmind' -export const changeFoo: Action = fromOperator( - action(({ state }) => { - state.foo = 'bar' - }) -) +export const changeFoo: Operator = action(({ state }) => { + state.foo = 'bar' +}) `, }, ] diff --git a/packages/overmind-website/examples/api/operators_pipe.ts b/packages/overmind-website/examples/api/operators_pipe.ts index 6c5508c3..7df3c093 100644 --- a/packages/overmind-website/examples/api/operators_pipe.ts +++ b/packages/overmind-website/examples/api/operators_pipe.ts @@ -3,7 +3,7 @@ export default (ts) => ? [ { code: ` -import { Action, Operator, fromOperator, pipe, debounce } from 'overmind' +import { Action, Operator, pipe, debounce } from 'overmind' import { QueryResult } from './state' import { setQuery, @@ -11,13 +11,11 @@ import { queryResult } from './operators' -export const search: Action = fromOperator( - pipe( - setQuery, - filterValidQuery, - debounce(200), - queryResult - ) +export const search: Action = pipe( + setQuery, + filterValidQuery, + debounce(200), + queryResult ) `, }, @@ -25,20 +23,18 @@ export const search: Action = fromOperator( : [ { code: ` -import { fromOperator, pipe, debounce } from 'overmind' +import { pipe, debounce } from 'overmind' import { setQuery, filterValidQuery, queryResult } from './operators' -export const search = fromOperator( - pipe( - setQuery, - filterValidQuery, - debounce(200), - queryResult - ) +export const search = pipe( + setQuery, + filterValidQuery, + debounce(200), + queryResult ) `, }, diff --git a/packages/overmind-website/examples/guide/goingfunctional/actionoperator.ts b/packages/overmind-website/examples/guide/goingfunctional/actionoperator.ts index 291de507..f42ec581 100644 --- a/packages/overmind-website/examples/guide/goingfunctional/actionoperator.ts +++ b/packages/overmind-website/examples/guide/goingfunctional/actionoperator.ts @@ -4,17 +4,15 @@ export default (ts) => { fileName: 'overmind/actions.ts', code: ` -import { Action, fromOperator, action } from 'overmind' +import { Action, Operator, action } from 'overmind' export const plainAction: Action = ({ value, state }) => { } -export const functionlAction: Action = fromOperator( - action(({ value, state }) => { +export const functionlAction: Operator = action(({ value, state }) => { - }) -) +}) `, }, ] @@ -22,17 +20,15 @@ export const functionlAction: Action = fromOperator( { fileName: 'overmind/actions.js', code: ` -import { fromOperator, action } from 'overmind' +import { action } from 'overmind' export const plainAction = ({ value, state }) => { } -export const functionlAction = fromOperator( - action(({ value, state }) => { +export const functionlAction = action(({ value, state }) => { - }) -) +}) `, }, ] diff --git a/packages/overmind-website/examples/guide/goingfunctional/callactionoperator.ts b/packages/overmind-website/examples/guide/goingfunctional/callactionoperator.ts index 335f9ccd..235ea91e 100644 --- a/packages/overmind-website/examples/guide/goingfunctional/callactionoperator.ts +++ b/packages/overmind-website/examples/guide/goingfunctional/callactionoperator.ts @@ -18,15 +18,13 @@ export const getUsers: Operator = action(async ({ state, effects }) => { { fileName: 'overmind/actions.ts', code: ` -import { Action, fromOperator, parallel } from 'overmind' +import { Operator, parallel } from 'overmind' import { getPosts, getUsers } from './operators' -export const grabData: Action = fromOperator( - parallel( - getPosts, - getUsers - ) -) +export const grabData: Operator = parallel( + getPosts, + getUsers +) `, }, ] @@ -48,14 +46,12 @@ export const getUsers = action(async ({ state, effects }) => { { fileName: 'overmind/actions.js', code: ` -import { fromOperator, parallel } from 'overmind' +import { parallel } from 'overmind' import { getPosts, getUsers } from './operators' -export const grabData = fromOperator( - parallel( - getPosts, - getUsers - ) +export const grabData = parallel( + getPosts, + getUsers ) `, }, diff --git a/packages/overmind-website/examples/guide/goingfunctional/clean.ts b/packages/overmind-website/examples/guide/goingfunctional/clean.ts index 1ca47d23..e40a4309 100644 --- a/packages/overmind-website/examples/guide/goingfunctional/clean.ts +++ b/packages/overmind-website/examples/guide/goingfunctional/clean.ts @@ -4,20 +4,18 @@ export default (ts) => { fileName: 'overmind/actions.ts', code: ` -import { Action, fromOperator, pipe, debounce, action } from 'overmind' +import { Operator, pipe, debounce, action } from 'overmind' import { getEventTargetValue, lengthGreaterThan } from './operators' -export const search: Action = fromOperator( - pipe( - getEventTargetValue, - lengthGreaterThan(2), - debounce(200), - action(async ({ value: query, state, effects }) => { - state.isSearching = true - state.searchResult = await effects.api.search(query) - state.isSearching = false - }) - ) +export const search: Operator = pipe( + getEventTargetValue, + lengthGreaterThan(2), + debounce(200), + action(async ({ value: query, state, effects }) => { + state.isSearching = true + state.searchResult = await effects.api.search(query) + state.isSearching = false + }) ) `, }, @@ -39,20 +37,18 @@ const lengthGreaterThan: (length: number) => Operator = { fileName: 'overmind/actions.js', code: ` -import { fromOperator, pipe, debounce, action } from 'overmind' +import { pipe, debounce, action } from 'overmind' import { getEventTargetValue, lengthGreaterThan } from './operators' -export const search = fromOperator( - pipe( - getEventTargetValue, - lengthGreaterThan(2), - debounce(200), - action(async ({ value: query, state, effects }) => { - state.isSearching = true - state.searchResult = await effects.api.search(query) - state.isSearching = false - }) - ) +export const search = pipe( + getEventTargetValue, + lengthGreaterThan(2), + debounce(200), + action(async ({ value: query, state, effects }) => { + state.isSearching = true + state.searchResult = await effects.api.search(query) + state.isSearching = false + }) ) `, }, diff --git a/packages/overmind-website/guides/intermediate/04_goingfunctional.md b/packages/overmind-website/guides/intermediate/04_goingfunctional.md index db83f45b..7bf63e41 100644 --- a/packages/overmind-website/guides/intermediate/04_goingfunctional.md +++ b/packages/overmind-website/guides/intermediate/04_goingfunctional.md @@ -16,7 +16,11 @@ If we were to do this in a functional style it would look more like this: h(Example, { name: "guide/goingfunctional/clean" }) ``` -Now we have created a couple of custom operators that we can reuse in other compositions. In addition we have made our code declarative. Instead of showing implementation details we rather "tell the story of the code". +Now we have created a couple of custom operators that we can reuse in other compositions, **getEventTargetValue** and **lengthGreaterThan**. In addition we have made our code declarative. Instead of showing implementation details we rather "tell the story of the code". + +```marksy +h(Notice, null, "Notice that instead of exporting **search** as a function, we export it as a pipe operator. That is perfectly okay. Any of the operators can act as a callable action on the Overmind instance.") +``` ## Structuring and calling operators @@ -28,7 +32,7 @@ To get going with functional code you can simply convert any existing action by h(Example, { name: "guide/goingfunctional/actionoperator" }) ``` -Your first impression is probably "more syntax". But we are doing something important here. We have created a composable piece of logic. The **action** operator is one of many operators. The **action** operator just allows us to express logic as a traditional action, though other operators has other behaviours. The **fromOperator** function converts any operator into a callable action. +Your first impression is probably "more syntax". But we are doing something important here. We have created a composable piece of logic. The **action** operator is one of many operators. The **action** operator just allows us to express logic as a traditional action, though other operators has other behaviours. Let us look at an example where we actually create two composable pieces and use them: diff --git a/packages/overmind-website/src/overmind/actions.ts b/packages/overmind-website/src/overmind/actions.ts index 08fdb85a..061f2311 100644 --- a/packages/overmind-website/src/overmind/actions.ts +++ b/packages/overmind-website/src/overmind/actions.ts @@ -1,6 +1,5 @@ -import { Action, fromOperator, pipe, action, filter, debounce } from 'overmind' +import { Action, pipe, action, filter, debounce, Operator } from 'overmind' import { Page, RouteContext, GuideParams, VideoParams } from './types' -import * as operators from './operators' export const openHome: Action = async ({ state, effects }) => { state.page = Page.HOME @@ -18,12 +17,23 @@ export const openGuides: Action = async ({ state, effects }) => { } } -export const openVideos: Action = fromOperator( - operators.openVideos() +export const openVideos: Operator> = action( + async ({ state, effects }) => { + state.page = Page.VIDEOS + state.currentVideo = null + if (!state.videos.length) { + state.isLoadingVideos = true + state.videos = await effects.request('/backend/videos') + state.isLoadingVideos = false + } + } ) -export const openVideo: Action> = fromOperator( - operators.openVideo +export const openVideo: Operator> = pipe( + openVideos, + action(({ value: routeContext, state }) => { + state.currentVideo = routeContext.params.title + }) ) export const openGuide: Action> = async ({ @@ -75,26 +85,22 @@ export const closeSearch: Action = ({ state }) => { state.query = '' } -export const changeQuery: Action< - React.ChangeEvent -> = fromOperator( - pipe( - action(({ value: event, state }) => { - const query = event.currentTarget.value +export const changeQuery: Operator> = pipe( + action(({ value: event, state }) => { + const query = event.currentTarget.value - state.query = query - state.showSearchResult = query.length > 2 - state.isLoadingSearchResult = query.length > 2 - }), - filter(({ state }) => state.query.length >= 3), - debounce(200), - action(async ({ state, effects }) => { - state.searchResult = await effects.request( - '/backend/search?query=' + state.query - ) - state.isLoadingSearchResult = false - }) - ) + state.query = query + state.showSearchResult = query.length > 2 + state.isLoadingSearchResult = query.length > 2 + }), + filter(({ state }) => state.query.length >= 3), + debounce(200), + action(async ({ state, effects }) => { + state.searchResult = await effects.request( + '/backend/search?query=' + state.query + ) + state.isLoadingSearchResult = false + }) ) export const viewHelpGotIt: Action = ({ state, effects }) => { diff --git a/packages/overmind-website/src/overmind/operators.ts b/packages/overmind-website/src/overmind/operators.ts deleted file mode 100644 index 025c6e59..00000000 --- a/packages/overmind-website/src/overmind/operators.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { action, Operator, pipe } from 'overmind' -import { Page, RouteContext, VideoParams } from './types' - -export const openVideos: () => Operator = () => - action(async ({ state, effects }) => { - state.page = Page.VIDEOS - state.currentVideo = null - if (!state.videos.length) { - state.isLoadingVideos = true - state.videos = await effects.request('/backend/videos') - state.isLoadingVideos = false - } - }) - -export const openVideo: Operator> = pipe( - openVideos>(), - action(({ value: routeContext, state }) => { - state.currentVideo = routeContext.params.title - }) -) diff --git a/packages/overmind-website/src/overmind/types.ts b/packages/overmind-website/src/overmind/types.ts index 1c12ddc9..5f67bf83 100644 --- a/packages/overmind-website/src/overmind/types.ts +++ b/packages/overmind-website/src/overmind/types.ts @@ -15,7 +15,7 @@ export type GuideParams = { } export type VideoParams = { - title: string + title?: string } export type ApiParams = {