Skip to content

Commit 12f3995

Browse files
feat(overmind): root statecharts
1 parent b88e5b4 commit 12f3995

File tree

8 files changed

+56
-34
lines changed

8 files changed

+56
-34
lines changed

packages/node_modules/overmind/src/config/statecharts.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ describe('Statecharts', () => {
2626
})).toEqual(true)
2727
})
2828

29+
test.only('should allow root chart', () => {
30+
const config = {}
31+
32+
const chart: Statechart<typeof config, {
33+
foo: void
34+
}> = {
35+
initial: 'foo',
36+
states: {
37+
foo: {},
38+
},
39+
}
40+
const instance = createOvermind(statecharts(config, chart))
41+
42+
expect(instance.state.states).toEqual([['ROOT_CHART', 'foo']])
43+
expect(instance.state.actions).toEqual({})
44+
expect(instance.state.matches({
45+
foo: true
46+
})).toEqual(true)
47+
})
48+
2949
test('should filter actions', () => {
3050
const increaseCount: Action = ({ state }) => {
3151
state.count++

packages/node_modules/overmind/src/config/statecharts.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from '../'
1010

1111
const ACTIONS = 'ACTIONS'
12+
const ROOT_CHART = 'ROOT_CHART'
1213

1314
export interface Statechart<C extends IConfiguration, S extends {
1415
[state: string]: Statecharts | void
@@ -182,15 +183,18 @@ function getStateTarget(charts, path) {
182183
)
183184
}
184185

185-
type Match<T extends Statecharts> = {
186+
type Match<T extends Statecharts | Statechart<any, any>> = T extends Statecharts ? {
186187
[I in keyof T]?: {
187188
[S in keyof T[I]["states"]]?: T[I]["states"][S]["charts"] extends void ? boolean : boolean | Match<T[I]["states"][S]["charts"]>
188189
}
189-
}
190+
} : T extends Statechart<any, any> ? {
191+
[S in keyof T["states"]]?: T["states"][S]["charts"] extends void ? boolean : boolean | Match<T["states"][S]["charts"]>
192+
} : never
193+
190194

191-
export function statecharts<C extends IConfiguration, Charts extends Statecharts>(
195+
export function statecharts<C extends IConfiguration, Charts extends Statecharts | Statechart<any, any>>(
192196
config: C,
193-
charts: Charts
197+
chartsDefinition: Charts
194198
): IConfig<{
195199
onInitialize: C['onInitialize']
196200
state: C['state'] & {
@@ -204,6 +208,9 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
204208
onInitialize: C['onInitialize']
205209
} {
206210
let currentInstance
211+
212+
const IS_ROOT_CHART = ('initial' in chartsDefinition && 'states' in chartsDefinition)
213+
const charts = (IS_ROOT_CHART ? { [ROOT_CHART]: chartsDefinition} : chartsDefinition) as Statecharts
207214
const actions = config.actions || {}
208215
const state = config.state || {}
209216

@@ -232,6 +239,10 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
232239
context.state,
233240
context.execution.namespacePath
234241
)
242+
const actionsTarget = getTarget(
243+
context.actions,
244+
context.execution.namespacePath
245+
)
235246

236247
const statePaths = stateTarget.states.slice()
237248

@@ -242,7 +253,7 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
242253
const target = getStateTarget(charts, state)
243254

244255
if (config.actions && config.actions[target.entry]) {
245-
config.actions[target.entry](context)
256+
actionsTarget[ACTIONS][target.entry](context)
246257
}
247258

248259
state.pop()
@@ -265,6 +276,11 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
265276
states: getInitialStates(charts),
266277
actions: ((state) => getCanTransitionActions(actions, charts, state)) as any,
267278
matches: (state) => (match) => {
279+
if (IS_ROOT_CHART) {
280+
match = {
281+
[ROOT_CHART]: match
282+
}
283+
}
268284
const matchPaths = getMatchPaths(match)
269285

270286
for (let x = 0; x < matchPaths.length; x++) {
@@ -299,6 +315,8 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
299315
const canTransition = stateTarget.actions[key]
300316
if (currentTransitionAction && !canTransition) {
301317
console.warn(`Overmind Statecharts: Transition action "${currentTransitionAction}" is calling transition action "${key}" synchronously. The previous transition is not done yet and "${key}" will be ignored. Consider calling it asynchronously `)
318+
} else if (!canTransition && process.env.NODE_ENV === 'development') {
319+
console.warn(`You tried to call action "${key}", but it was blocked by the statechart. You are not supposed to call this action in the current state of the chart. This warning only appear during development`)
302320
}
303321

304322
return {

packages/overmind-website/examples/guide/statecharts/condition.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ const loginChart: Statechart<typeof config, {
3535
}
3636
}
3737
38-
export default statecharts(config, {
39-
login: loginChart
40-
})
38+
export default statecharts(config, loginChart)
4139
`,
4240
},
4341
]

packages/overmind-website/examples/guide/statecharts/define.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ const loginChart: Statechart<typeof config, {
4747
}
4848
}
4949
50-
export default statecharts(config, {
51-
login: loginChart
52-
})
50+
export default statecharts(config, loginChart)
5351
`,
5452
},
5553
]
@@ -95,9 +93,7 @@ const loginChart = {
9593
}
9694
}
9795
98-
export default statecharts(config, {
99-
login: loginChart
100-
})
96+
export default statecharts(config, loginChart)
10197
`,
10298
},
10399
]

packages/overmind-website/examples/guide/statecharts/matches.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ export default (ts, view) =>
44
{
55
code: `
66
state.login.matches({
7-
login: {
8-
LOGIN: true
9-
}
7+
LOGIN: true
108
})
119
`,
1210
},
@@ -15,9 +13,7 @@ state.login.matches({
1513
{
1614
code: `
1715
state.login.matches({
18-
login: {
19-
LOGIN: true
20-
}
16+
LOGIN: true
2117
})
2218
`,
2319
},

packages/overmind-website/examples/guide/statecharts/matches_multiple.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ export default (ts, view) =>
55
code: `
66
// Nested
77
const isSearching = state.dashboard.matches({
8-
issues: {
9-
LIST: {
10-
search: {
11-
SEARCHING: true
12-
}
8+
LIST: {
9+
search: {
10+
SEARCHING: true
1311
}
1412
}
1513
})
@@ -41,11 +39,9 @@ const isOnlyDownloading = state.files.matches({
4139
code: `
4240
// Nested
4341
const isSearching = state.dashboard.matches({
44-
issues: {
45-
LIST: {
46-
search: {
47-
SEARCHING: true
48-
}
42+
LIST: {
43+
search: {
44+
SEARCHING: true
4945
}
5046
}
5147
})

packages/overmind-website/examples/guide/statecharts/nested.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ const dashboardChart: Statechart<typeof config, {
9494
}
9595
}
9696
97-
export default statecharts(config, {
98-
dashboard: dashboardChart
99-
})
97+
export default statecharts(config, dashboardChart)
10098
`,
10199
},
102100
]

packages/overmind-website/guides/intermediate/06_statecharts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ As you can see we have no state indicating that we have received an error, like
8686
password: '',
8787
user: null,
8888
authenticationError: null,
89-
states: [['login', 'LOGIN']],
89+
states: [['ROOT_CHART', 'LOGIN']],
9090
actions: {
9191
changeUsername: true,
9292
changePassword: true,

0 commit comments

Comments
 (0)