Skip to content

Commit c7bf63d

Browse files
feat(overmind): simplify statemachines
BREAKING CHANGE: no more exit and reset, also transition API has changed
1 parent 9627262 commit c7bf63d

File tree

2 files changed

+36
-163
lines changed

2 files changed

+36
-163
lines changed

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

Lines changed: 23 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,11 @@ describe('Statemachine', () => {
104104
})
105105

106106
const transition: Action = ({ state }) => {
107-
return state.transition('BAR', () => {
107+
if (state.transition('BAR')) {
108108
expect(state.state).toBe('BAR')
109109
state.transition('FOO')
110-
}, () => {
111-
expect(state.state).toBe('BAR')
112-
})
110+
expect(state.state).toBe('FOO')
111+
}
113112
}
114113

115114
const config = {
@@ -176,11 +175,11 @@ describe('Statemachine', () => {
176175
}, {
177176
state: 'FOO'
178177
})
179-
const transition: Action = ({ state }) => {
180-
return state.transition('BAR', async () => {
178+
const transition: Action = async ({ state }) => {
179+
if (state.transition('BAR')) {
181180
await Promise.resolve()
182181
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(true)
183-
})
182+
}
184183
}
185184

186185
const config = {
@@ -196,9 +195,8 @@ describe('Statemachine', () => {
196195

197196
return overmind.actions.transition()
198197
})
199-
200-
test('should reset a statemachine', () => {
201-
expect.assertions(1)
198+
test('should enable mutations after new async matching or transition', async () => {
199+
expect.assertions(3)
202200

203201
type States = {
204202
state: 'FOO'
@@ -207,58 +205,23 @@ describe('Statemachine', () => {
207205
}
208206

209207
const state = statemachine<States>({
210-
FOO: ['BAR'],
211-
BAR: ['FOO']
208+
FOO: ['BAR'],
209+
BAR: ['FOO']
212210
}, {
213211
state: 'FOO'
214212
})
215-
216-
const transition: Action = ({ state }) => {
217-
state.transition('BAR')
218-
}
219-
220-
const config = {
221-
state,
222-
actions: {
223-
transition
213+
const transition: Action = async ({ state }) => {
214+
if (state.transition('BAR')) {
215+
await Promise.resolve()
216+
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(true)
217+
if (state.matches('BAR')) {
218+
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(false)
219+
await Promise.resolve()
220+
if (state.transition('FOO')) {
221+
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(false)
222+
}
223+
}
224224
}
225-
}
226-
227-
interface Action extends IAction<typeof config, void, void> {}
228-
229-
const overmind = createOvermind(config)
230-
overmind.actions.transition()
231-
overmind.state.reset()
232-
expect(overmind.state.state).toBe('FOO')
233-
})
234-
235-
test('should pass statemachine to transition callback, correctly typed', () => {
236-
expect.assertions(3)
237-
238-
type States = {
239-
state: 'FOO'
240-
foo: string
241-
} | {
242-
state: 'BAR'
243-
bar: string
244-
}
245-
246-
const state = statemachine<States>({
247-
FOO: ['BAR'],
248-
BAR: ['FOO']
249-
}, {
250-
state: 'FOO',
251-
foo: 'bar'
252-
})
253-
254-
const transition: Action = ({ state }) => {
255-
return state.transition('BAR', () => {
256-
state.transition('FOO', (current) => {
257-
current.foo = 'bar2'
258-
})
259-
}, (current) => {
260-
current.bar = 'baz2'
261-
})
262225
}
263226

264227
const config = {
@@ -268,62 +231,10 @@ describe('Statemachine', () => {
268231
}
269232
}
270233

271-
interface Action extends IAction<typeof config, void, void | Promise<void>> {}
234+
interface Action extends IAction<typeof config, void, Promise<void>> {}
272235

273236
const overmind = createOvermind(config)
274-
overmind.actions.transition()
275-
expect(overmind.state.state).toBe('FOO')
276-
// @ts-ignore
277-
expect(overmind.state.foo).toBe('bar2')
278-
// @ts-ignore
279-
expect(overmind.state.bar).toBe('baz2')
280-
})
281237

282-
test('should allow async exit transition', async () => {
283-
expect.assertions(3)
284-
285-
type States = {
286-
state: 'FOO'
287-
foo: string
288-
} | {
289-
state: 'BAR'
290-
bar: string
291-
}
292-
293-
const state = statemachine<States>({
294-
FOO: ['BAR'],
295-
BAR: ['FOO']
296-
}, {
297-
state: 'FOO',
298-
foo: 'bar'
299-
})
300-
301-
const transition: Action = ({ state }) => {
302-
return state.transition('BAR', async () => {
303-
await Promise.resolve()
304-
state.transition('FOO', (current) => {
305-
current.foo = 'bar2'
306-
})
307-
}, (current) => {
308-
current.bar = 'baz2'
309-
})
310-
}
311-
312-
const config = {
313-
state,
314-
actions: {
315-
transition
316-
}
317-
}
318-
319-
interface Action extends IAction<typeof config, void, void | Promise<void>> {}
320-
321-
const overmind = createOvermind(config)
322-
await overmind.actions.transition()
323-
expect(overmind.state.state).toBe('FOO')
324-
// @ts-ignore
325-
expect(overmind.state.foo).toBe('bar2')
326-
// @ts-ignore
327-
expect(overmind.state.bar).toBe('baz2')
238+
return overmind.actions.transition()
328239
})
329240
})

packages/node_modules/overmind/src/statemachine.ts

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,89 +11,51 @@ export type StatemachineTransitions<States extends TStates> = {
1111
}
1212

1313
export interface MachineMethods<States extends TStates> {
14-
reset: () => void
15-
matches<T extends States["state"][]>(...states: T): this is Statemachine<States, States extends {
16-
state: T extends Array<infer S> ? S : never;
14+
matches<T extends States["state"]>(state: T): this is Statemachine<States, States extends {
15+
state: T;
1716
} ? States : never>;
1817
transition<State extends States["state"]>(
1918
state: State,
20-
entry?: (current: Statemachine<States, States extends { state: State } ? States : never>) => void,
21-
exit?: (current: Statemachine<States, States extends { state: State } ? States : never>) => void
22-
): Promise<void>
23-
whenTransitioned: (state: States["state"]) => Promise<void>
19+
): this is Statemachine<States, States extends {
20+
state: State
21+
} ? States : never>
2422
}
2523

2624
export type Statemachine<States extends TStates, State extends TStates = States> = State & MachineMethods<States>
2725

28-
const CURRENT_EXIT = Symbol('CURRENT_EXIT')
2926
const INITIAL_STATE = Symbol('INITIAL_STATE')
3027
const TRANSITIONS = Symbol('TRANSITIONS')
31-
const PENDING_TRANSITIONS = Symbol('PENDING_TRANSITIONS')
3228

3329
class StateMachine<States extends TStates, State extends TStates = States> {
3430
state: State["state"]
35-
private [CURRENT_EXIT]: (() => void) | undefined
3631
private [INITIAL_STATE]: State["state"]
37-
private [PENDING_TRANSITIONS]: { [key: string]: Function[] } = {}
3832
constructor(transitions: StatemachineTransitions<States>, definition: States) {
3933
this[INITIAL_STATE] = definition.state
4034
this[TRANSITIONS] = transitions
4135
Object.assign(this, definition)
4236
}
43-
transition(state, entry, exit) {
37+
transition(state) {
4438
const transitions = this[VALUE][TRANSITIONS]
4539
if (transitions[this.state].includes(state)) {
4640
const tree = (this[PROXY_TREE].master.mutationTree || this[PROXY_TREE])
47-
let exitResult
4841
tree.enableMutations()
49-
50-
if (this[CURRENT_EXIT]) this[CURRENT_EXIT]!()
51-
if (exit) {
52-
exitResult = new Promise((resolve) => {
53-
this[VALUE][CURRENT_EXIT] = () => resolve(exit(this))
54-
})
55-
} else {
56-
this[VALUE][CURRENT_EXIT] = undefined
57-
}
5842
this.state = state
59-
const entryResult = entry && entry(this)
60-
61-
tree.blockMutations()
62-
63-
if (this[VALUE][PENDING_TRANSITIONS][this.state]) {
64-
this[VALUE][PENDING_TRANSITIONS][this.state].forEach((resolve) => resolve())
65-
this[VALUE][PENDING_TRANSITIONS][this.state] = []
66-
}
67-
68-
return exitResult || entryResult
43+
Promise.resolve().then(() => tree.blockMutations())
44+
return true
6945
} else if (process.env.NODE_ENV === 'development') {
7046
console.warn(`Overmind Statemachine - You tried to transition into "${state}", but it is not a valid transition. The valid transitions are ${JSON.stringify(transitions[this.state])}`)
7147
}
48+
return false
7249
}
73-
matches(...states) {
74-
if (states.includes(this.state)) {
50+
matches(state) {
51+
if (state === this.state) {
52+
const tree = (this[PROXY_TREE].master.mutationTree || this[PROXY_TREE])
53+
tree.enableMutations()
7554
return true
7655
}
7756

7857
return false
7958
}
80-
reset() {
81-
const exit = this[VALUE][CURRENT_EXIT]
82-
if (typeof exit === 'function') {
83-
exit()
84-
this[VALUE][CURRENT_EXIT] = undefined
85-
}
86-
this.state = this[INITIAL_STATE]
87-
}
88-
whenTransitioned(state: States["state"]) {
89-
if (!this[VALUE][PENDING_TRANSITIONS][state]) {
90-
this[VALUE][PENDING_TRANSITIONS][state] = []
91-
}
92-
93-
return new Promise((resolve) => {
94-
this[VALUE][PENDING_TRANSITIONS][state].push(resolve)
95-
})
96-
}
9759
}
9860

9961
export function statemachine<States extends TStates>(definition: StatemachineTransitions<States>, state: States): Statemachine<States> {

0 commit comments

Comments
 (0)