Skip to content

Commit 7a0ae97

Browse files
Merge branch 'next' of https://github.com/cerebral/overmind into next
2 parents 8d0a3d0 + d3989a8 commit 7a0ae97

22 files changed

+328
-151
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ export class Overmind<ThisConfig extends IConfiguration>
401401
getMutationTree: () => {
402402
return this.proxyStateTree.getMutationTree()
403403
},
404+
getTrackStateTree: () => {
405+
return this.proxyStateTree.getTrackStateTree()
406+
},
404407
emit: this.eventHub.emit.bind(this.eventHub),
405408
} as any) as Execution
406409
}
@@ -439,9 +442,14 @@ export class Overmind<ThisConfig extends IConfiguration>
439442
this.addExecutionMutation(mutation)
440443
})
441444
}
442-
443-
return mutationTree
444-
},
445+
return mutationTree
446+
},
447+
getTrackStateTree: () => {
448+
return this.proxyStateTree.getTrackStateTree()
449+
},
450+
onFlush: (cb) => {
451+
return this.proxyStateTree.onFlush(cb)
452+
},
445453
scopeValue: (value, tree) => {
446454
return this.scopeValue(value, tree)
447455
},
@@ -1446,3 +1454,29 @@ export function debounce<Input, ThisConfig extends IConfiguration = Config>(
14461454
}
14471455
)
14481456
}
1457+
1458+
export function throttle<Input, ThisConfig extends IConfiguration = Config>(
1459+
ms: number
1460+
): IOperator<ThisConfig, Input, Input> {
1461+
let timeout
1462+
let previousFinal
1463+
1464+
return createOperator(
1465+
'throttle',
1466+
String(ms),
1467+
(err, context, value, next, final) => {
1468+
if (err) next(err, value)
1469+
else {
1470+
if (timeout) {
1471+
previousFinal(null, value)
1472+
} else {
1473+
timeout = setTimeout(() => {
1474+
timeout = null
1475+
next(null, value)
1476+
}, ms)
1477+
}
1478+
previousFinal = final
1479+
}
1480+
}
1481+
)
1482+
}

packages/node_modules/overmind/src/internalTypes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { IMutation, IMutationTree } from 'proxy-state-tree'
1+
import {
2+
IMutation,
3+
IMutationTree,
4+
ITrackStateTree,
5+
IFlushCallback,
6+
} from 'proxy-state-tree'
27
import { IAction, IOperator, IState } from './types'
38

49
export type SubType<Base, Condition> = Pick<
@@ -71,6 +76,8 @@ export type Execution = {
7176
flushId: number
7277
}
7378
getMutationTree(): IMutationTree<any>
79+
getTrackStateTree(): ITrackStateTree<any>
80+
onFlush(cb: IFlushCallback): () => IFlushCallback[]
7481
value?: any
7582
error?: string
7683
}

packages/node_modules/overmind/src/operator.test.ts

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createOperator, createMutationOperator } from './operator'
2-
import { Overmind, IConfig, IOperator, pipe, map, mutate } from './'
2+
import { Overmind, IConfig, IOperator, pipe, map, mutate, IAction } from './'
3+
import { IS_PROXY, PATH } from 'proxy-state-tree'
34

45
describe('OPERATOR', () => {
56
test('should be able to create an operator', () => {
@@ -181,4 +182,125 @@ describe('OPERATOR', () => {
181182
expect(overmind.state.foo).toBe('hihihi')
182183
})
183184
})
185+
test('should be able to create an operator that can track mutations', (done) => {
186+
function waitForMutation(
187+
operation: (state: Config['state']) => void
188+
): Operator {
189+
return createOperator<Config>(
190+
'waitForMutation',
191+
operation.name,
192+
(err, context, value, next) => {
193+
if (err) next(err, value)
194+
else {
195+
const tree = context.execution.getTrackStateTree()
196+
tree.trackScope(
197+
() => {
198+
operation(tree.state)
199+
},
200+
() => {
201+
tree.dispose()
202+
next(null, value)
203+
}
204+
)
205+
}
206+
}
207+
)
208+
}
209+
210+
const waitForFoo: Operator = waitForMutation((state) => {
211+
state.foo
212+
})
213+
214+
const test: Operator = pipe(waitForFoo)
215+
216+
const mutateFoo: Action = ({ state }) => {
217+
state.foo = 'hihihi'
218+
}
219+
220+
const state = {
221+
foo: 'bar',
222+
}
223+
224+
const config = {
225+
state,
226+
actions: {
227+
test,
228+
mutateFoo,
229+
},
230+
}
231+
const overmind = new Overmind(config)
232+
233+
type Config = IConfig<typeof config>
234+
235+
interface Operator<Input = void, Output = Input>
236+
extends IOperator<Config, Input, Output> {}
237+
238+
interface Action<Input = void, Output = void | Promise<void>>
239+
extends IAction<Config, Input, Output> {}
240+
241+
overmind.actions.test().then(done)
242+
243+
// Trigger mutation with an action.
244+
overmind.actions.mutateFoo()
245+
})
246+
test('should be able to create an operator that evaluates mutations', (done) => {
247+
function waitUntilTrue(
248+
operation: (state: Config['state']) => boolean
249+
): Operator {
250+
return createOperator<Config>(
251+
'waitUntilTrue',
252+
operation.name,
253+
(err, context, value, next) => {
254+
if (err) next(err, value)
255+
else {
256+
const tree = context.execution.getTrackStateTree()
257+
const test = () => {
258+
if (operation(tree.state)) {
259+
tree.dispose()
260+
next(null, value)
261+
}
262+
}
263+
tree.trackScope(test, test)
264+
}
265+
}
266+
)
267+
}
268+
269+
const waitForFoo: Operator = waitUntilTrue(
270+
(state) => state.foo.bar === 'hihihi'
271+
)
272+
273+
const test: Operator = pipe(waitForFoo)
274+
275+
const mutateBar: Action = ({ state }) => {
276+
state.foo.bar = 'hihihi'
277+
}
278+
279+
const state = {
280+
foo: {
281+
bar: 'baz',
282+
},
283+
}
284+
285+
const config = {
286+
state,
287+
actions: {
288+
test,
289+
mutateBar,
290+
},
291+
}
292+
const overmind = new Overmind(config)
293+
294+
type Config = IConfig<typeof config>
295+
296+
interface Operator<Input = void, Output = Input>
297+
extends IOperator<Config, Input, Output> {}
298+
299+
interface Action<Input = void> extends IAction<Config, Input> {}
300+
301+
overmind.actions.test().then(done)
302+
303+
// Trigger mutation with an action.
304+
overmind.actions.mutateBar()
305+
})
184306
})

packages/node_modules/overmind/src/operator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
createActionsProxy,
55
ORIGINAL_ACTIONS,
66
} from './utils'
7-
import { EventType } from './internalTypes'
7+
import { EventType, Execution } from './internalTypes'
88
import { safeValue } from './Devtools'
99
import { IContext, IConfiguration, IOperator } from './types'
1010

@@ -127,7 +127,7 @@ export function createOperator<ThisConfig extends IConfiguration>(
127127
name: string,
128128
cb: (
129129
err: Error | null,
130-
context: IContext<ThisConfig>,
130+
context: IContext<ThisConfig> & { execution: Execution },
131131
value: any,
132132
next: (
133133
err: Error | null,
@@ -150,6 +150,7 @@ export function createOperator<ThisConfig extends IConfiguration>(
150150
state: context.state,
151151
effects: context.effects,
152152
actions: context.actions,
153+
execution: context.execution,
153154
},
154155
context.value,
155156
(err, value, options = {}) => {

packages/node_modules/overmind/src/operators.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
IAction,
1616
catchError,
1717
tryCatch,
18+
throttle,
1819
} from './'
1920

2021
describe('OPERATORS', () => {
@@ -316,6 +317,35 @@ describe('OPERATORS', () => {
316317
}
317318
)
318319
})
320+
test('throttle', () => {
321+
expect.assertions(1)
322+
const test: Operator = pipe(
323+
throttle(100),
324+
mutate(({ state }) => state.runCount++)
325+
)
326+
const state = {
327+
runCount: 0,
328+
}
329+
const config = {
330+
state,
331+
actions: {
332+
test,
333+
},
334+
}
335+
const overmind = new Overmind(config)
336+
337+
type Config = IConfig<typeof config>
338+
339+
interface Operator<Input = void, Output = Input>
340+
extends IOperator<Config, Input, Output> {}
341+
342+
return Promise.all([overmind.actions.test(), overmind.actions.test()]).then(
343+
() => {
344+
expect(overmind.state.runCount).toBe(1)
345+
}
346+
)
347+
})
348+
319349
test('catchError', () => {
320350
expect.assertions(3)
321351
const test: Operator<string> = pipe(

packages/overmind-website/guides/beginner/01_quickstart.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Now set up a simple application like this:
1414
h(Example, { name: "guide/quickstart/simple_app" })
1515
```
1616

17-
And fire up your application in the browser or whatever environment your user interface is to be consumed by the users.
17+
And fire up your application in the browser or whatever environment your user interface is to be consumed in by the users.
1818

1919

2020
## VS Code
@@ -38,4 +38,4 @@ First... try to refresh your app to reconnect. If this does not work make sure t
3838

3939
**The devtool does not open in VS Code?**
4040

41-
Restart VS Code.
41+
Restart VS Code.

packages/overmind-website/guides/beginner/02_getstarted.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ If you are moving from an existing state management solution, please read the re
55
To get started with Overmind you have to set up a project. You can do this with whatever tool your framework of choice provides or you can use [webpack](https://webpack.js.org/) or [parceljs](https://parceljs.org/). You can also use [codesandbox.io](https://codesandbox.io/) to play around with Overmind directly in the browser.
66

77
```marksy
8-
h(Notice, null, "Due to using the Proxy feature of JavaScript, Overmind does not support **Internet Explorer 11**. Though did you know IE 11 mode is coming to [Microsofts next browser](https://www.pcworld.com/article/3393198/microsoft-edge-ie-mode.html)?")
8+
h(Notice, null, "Due to using the Proxy feature of JavaScript, Overmind does not support **Internet Explorer 11**. Though did you know IE 11 mode is coming to [Microsoft's next browser](https://www.pcworld.com/article/3393198/microsoft-edge-ie-mode.html)?")
99
```
1010

1111

12-
When you have your project up and running install the Overmind dependency by using [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/en/):
12+
When you have your project up and running, install the Overmind dependency by using [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/en/):
1313

1414
```marksy
1515
h(Example, { name: "guide/getstarted/install" })
@@ -41,7 +41,7 @@ The devtools should respond when you open up your application:
4141

4242
![open_devtool](/images/devtool_state.png)
4343

44-
Now that we have our state in place, lets change it.
44+
Now that we have our state in place, let's change it.
4545

4646
## Changing state
4747

@@ -58,13 +58,13 @@ To trigger this action, go to the **actions** tab in the development tool and ru
5858

5959
## Creating effects
6060

61-
State management tools has a tendency to end their introduction here, but Overmind helps you manage one more important ingredient, **effects**. An effect, or side effect, is everything from doing http requests to storing data in local storage. What Overmind helps you with is separating the generic low level APIs of using effects from your actual application logic inside actions. Let us look at an example. We are simply going to grab a random number from "somewhere out there":
61+
State management tools have a tendency to end their introduction here, but Overmind helps you manage one more important ingredient, **effects**. An effect, or side effect, is everything from doing HTTP requests to storing data in local storage. What Overmind helps you with is separating the generic low level APIs of using effects from your actual application logic inside actions. Let us look at an example. We are simply going to grab a random number from "somewhere out there":
6262

6363
```marksy
6464
h(Example, { name: "guide/getstarted/effect" })
6565
```
6666

67-
As you can see we separated the low level generic code of creating a random number from our actual application logic in the action. Think of effects as the API you custom tailor to your application. There are several benefits to effects, which you can read about later, but the really important thing is that you separate the tools you are using from your actual application. That means you can at any time replace this custom random number generator with some existing tool or maybe you will grab it from the server? This is also true for everything else. If your application needs posts you will create a **getPosts** effect. It does not matter to the application if this comes from a restful API, graphql or whatever other source. It is an implementation detail.
67+
As you can see we separated the low level generic code of creating a random number from our actual application logic in the action. Think of effects as the API you custom tailor to your application. There are several benefits to effects, which you can read about later, but the really important thing is that you separate the tools you are using from your actual application. That means you can at any time replace this custom random number generator with some existing tool or maybe you will grab it from the server? This is also true for everything else. If your application needs posts you will create a **getPosts** effect. It does not matter to the application if this comes from a restful API, GraphQL or whatever other source. It is an implementation detail.
6868

6969
With our effect in place, let us run the actions again:
7070

@@ -78,12 +78,12 @@ Now that we know our application works as expected we can actually produce the U
7878
h(Example, { name: "guide/getstarted/connectapp" })
7979
```
8080

81-
Now you can run the actions by clicking the buttons in the UI and the devtools continues to track their execution giving you valuable insight into what happens inside your app.
81+
Now you can run the actions by clicking the buttons in the UI and the devtool continues to track their execution giving you valuable insight into what happens inside your app.
8282

8383
## Hot Module Replacement
8484

8585
A popular concept introduced by Webpack is [HMR](https://webpack.js.org/concepts/hot-module-replacement/). It allows you to make changes to your code without having to refresh. Overmind automatically supports HMR. That means when **HMR** is activated Overmind will make sure it updates and manages its state, actions and effects. Even the devtools will be updated as you make changes.
8686

8787
## Summary
8888

89-
You have now stepped your toes into Overmind. We introduced Overmind with the concept of thinking the UI as an implementation detail, but it is totally up to you have you want to separate the responsibilities of states and logic in your application. Please continue reading guides to learn more about how Overmind scales.
89+
You have now stepped your toes into Overmind. We introduced Overmind with the concept of thinking of the UI as an implementation detail, but it is totally up to you how you want to separate the responsibilities of states and logic in your application. Please continue reading guides to learn more about how Overmind scales.

0 commit comments

Comments
 (0)