Skip to content

Commit 7dcf140

Browse files
feat(overmind): strict mode using state machines
1 parent 909e5a4 commit 7dcf140

File tree

5 files changed

+99
-25
lines changed

5 files changed

+99
-25
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ProxyStateTree,
1212
TTree,
1313
VALUE,
14+
PROXY_TREE,
1415
} from 'proxy-state-tree'
1516

1617
import { Derived, IS_DERIVED, IS_DERIVED_CONSTRUCTOR } from './derived'
@@ -191,6 +192,7 @@ export class Overmind<ThisConfig extends IConfiguration>
191192
private mode: DefaultMode | TestMode | SSRMode
192193
private reydrateMutationsForHotReloading: IMutation[] = []
193194
private originalConfiguration
195+
private isStrict = false
194196
initialized: Promise<any>
195197
eventHub: EventEmitter<Events>
196198
devtools: Devtools
@@ -209,6 +211,7 @@ export class Overmind<ThisConfig extends IConfiguration>
209211
const devEnv = options.devEnv || 'development'
210212

211213
this.delimiter = options.delimiter || '.'
214+
this.isStrict = Boolean(options.strict)
212215

213216
if (
214217
(!process.env.NODE_ENV || process.env.NODE_ENV === devEnv) &&
@@ -238,7 +241,7 @@ export class Overmind<ThisConfig extends IConfiguration>
238241
const proxyStateTree = this.createProxyStateTree(
239242
configuration,
240243
eventHub,
241-
mode.mode === MODE_SSR ? false : process.env.NODE_ENV === devEnv
244+
mode.mode === MODE_TEST || process.env.NODE_ENV === devEnv
242245
)
243246
this.originalConfiguration = configuration
244247
this.state = proxyStateTree.state
@@ -574,8 +577,12 @@ export class Overmind<ThisConfig extends IConfiguration>
574577
)
575578
})
576579
} else {
580+
const mutationTree = execution.getMutationTree()
581+
if (this.isStrict) {
582+
mutationTree.blockMutations()
583+
}
577584
const returnValue = action(
578-
this.createContext(execution, execution.getMutationTree()),
585+
this.createContext(execution, mutationTree),
579586
value
580587
)
581588

@@ -596,7 +603,9 @@ export class Overmind<ThisConfig extends IConfiguration>
596603
this.eventHub.emit(EventType.OPERATOR_START, execution)
597604

598605
const mutationTree = execution.getMutationTree()
599-
606+
if (this.isStrict) {
607+
mutationTree.blockMutations()
608+
}
600609
mutationTree.onMutation((mutation) => {
601610
this.eventHub.emit(EventType.MUTATIONS, {
602611
...execution,

packages/node_modules/overmind/src/internalTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type Options = {
2424
devtools?: string | boolean
2525
logProxies?: boolean
2626
hotReloading?: boolean
27+
strict?: boolean
2728
}
2829

2930
export type DefaultMode = {

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

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ describe('Statemachine', () => {
205205
return overmind.actions.transition()
206206
})
207207

208-
test('should enable mutations after new async matching or transition', async () => {
208+
test('should only enable mutations with matches', async () => {
209209
expect.assertions(3)
210210

211211
type States = {
@@ -224,13 +224,13 @@ describe('Statemachine', () => {
224224
await state.transition('BAR', {}, async () => {
225225
await Promise.resolve()
226226
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(true)
227-
if (state.matches('BAR')) {
227+
state.matches('BAR', async () => {
228228
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(false)
229229
await Promise.resolve()
230230
state.transition('FOO', {}, () => {
231-
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(false)
231+
expect(state[PROXY_TREE].master.mutationTree.isBlocking).toBe(true)
232232
})
233-
}
233+
})
234234
})
235235
}
236236

@@ -390,4 +390,70 @@ describe('Statemachine', () => {
390390

391391
overmind.actions.transition()
392392
})
393+
test('should not allow mutations in strict mode', () => {
394+
type States = {
395+
current: 'FOO'
396+
} | {
397+
current: 'BAR'
398+
}
399+
400+
const state = statemachine<States>({
401+
FOO: ['BAR'],
402+
BAR: ['FOO']
403+
}, {
404+
current: 'FOO',
405+
})
406+
const transition: Action = ({ state }) => {
407+
state.current = 'BAR'
408+
}
409+
410+
const config = {
411+
state,
412+
actions: {
413+
transition
414+
}
415+
}
416+
417+
interface Action extends IAction<typeof config, void, void> {}
418+
419+
const overmind = createOvermindMock(config)
420+
// @ts-ignore
421+
overmind.isStrict = true
422+
423+
424+
expect(() => overmind.actions.transition()).toThrow()
425+
})
426+
test('should allow mutations using transition', () => {
427+
type States = {
428+
current: 'FOO'
429+
} | {
430+
current: 'BAR'
431+
}
432+
433+
const state = statemachine<States>({
434+
FOO: ['BAR'],
435+
BAR: ['FOO']
436+
}, {
437+
current: 'FOO',
438+
})
439+
const transition: Action = ({ state }) => {
440+
state.transition('BAR', {})
441+
}
442+
443+
const config = {
444+
state,
445+
actions: {
446+
transition
447+
}
448+
}
449+
450+
interface Action extends IAction<typeof config, void, void> {}
451+
452+
const overmind = createOvermindMock(config)
453+
// @ts-ignore
454+
overmind.isStrict = true
455+
456+
457+
expect(() => overmind.actions.transition()).not.toThrow()
458+
})
393459
})

packages/node_modules/overmind/src/statemachine.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export type StatemachineTransitions<States extends TStates> = {
3131
}
3232

3333
export interface MachineMethods<States extends TStates, Base extends IState = {}> {
34-
matches<T extends States["current"]>(...state: T[]): this is Statemachine<Base, States, States extends {
34+
matches<T extends States["current"], O = void>(state: T | T[], cb: (current: Statemachine<Base, States, States extends {
3535
current: T
36-
} ? States : never>;
36+
} ? States : never>) => O): O;
3737
transition<T extends States["current"], O = void>(
3838
state: T,
3939
newState: States extends {
@@ -70,30 +70,31 @@ export class StateMachine<Base extends IState, States extends TStates, State ext
7070
this[CURRENT_KEYS] = Object.keys(state)
7171
Object.assign(this, state, base)
7272
}
73-
matches(...states) {
73+
matches(states, cb) {
74+
states = Array.isArray(states) ? states : [states]
75+
7476
if (states.includes(this.current)) {
7577
const tree = (this[PROXY_TREE].master.mutationTree || this[PROXY_TREE])
7678

77-
if (tree.enableMutations) {
78-
tree.enableMutations()
79-
Promise.resolve().then(() => {
80-
tree.blockMutations()
81-
})
82-
79+
if (!tree.enableMutations) {
80+
throw new Error('Overmind - The "matches" API is only to be used for state changes in actions, point to "state.current" to check current state of machine')
8381
}
84-
return true
85-
}
8682

87-
return false
83+
tree.enableMutations()
84+
const result = cb(this)
85+
tree.blockMutations()
86+
return result
87+
}
8888
}
8989
transition(state, newState, callback) {
9090
const transitions = this[VALUE][TRANSITIONS]
91-
91+
9292
if (transitions[this.current].includes(state)) {
9393
const tree = (this[PROXY_TREE].master.mutationTree || this[PROXY_TREE])
9494
const baseKeys = Object.keys(this[VALUE][BASE])
9595

9696
tree.enableMutations()
97+
9798
this[VALUE][CURRENT_KEYS].forEach((key) => {
9899
if (!baseKeys.includes(key)) {
99100
delete this[key]
@@ -104,13 +105,13 @@ export class StateMachine<Base extends IState, States extends TStates, State ext
104105
this.current = state
105106
this[VALUE][CURRENT_KEYS] = Object.keys(newState)
106107

108+
tree.blockMutations()
109+
107110
let result
108111
if (callback) {
109112
result = callback(this)
110113
}
111114

112-
tree.blockMutations()
113-
114115
return result
115116
} else if (process.env.NODE_ENV === 'development' && state !== this.current) {
116117
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.current])}`)

packages/node_modules/proxy-state-tree/src/Proxyfier.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ export class Proxifier {
194194
)
195195

196196
const mutationTree = proxifier.getMutationTree()
197-
const existingValue = target[prop]
198197
const result = Reflect.set(target, prop, value)
199198

200199
mutationTree.addMutation({
@@ -333,8 +332,6 @@ export class Proxifier {
333332
}
334333

335334
const mutationTree = proxifier.getMutationTree()
336-
const existingValue = target[prop]
337-
338335

339336
delete target[prop]
340337

0 commit comments

Comments
 (0)