Skip to content

Commit 3ce2b31

Browse files
committed
feat(machines)!: switch machines to signals
1 parent 63fcab7 commit 3ce2b31

14 files changed

Lines changed: 1083 additions & 330 deletions

File tree

packages/atoms/src/utils/general.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import type {
2424
* that they don't prevent node destruction.
2525
*
2626
* IMPORTANT: Keep these in-sync with the copies in the react package -
27-
* packages/react/src/utils.ts
27+
* packages/react/src/utils.ts - and the machines package =
28+
* packages/machines/src/utils.ts
2829
*/
2930
export const TopPrio = 0
3031
export const Eventless = 1

packages/machines/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# `@zedux/machines`
22

3+
> IMPORTANT: This readme has not yet been updated for the Zedux v2 signal-based implementation of the `@zedux/machines` package. That will happen after the v2 versions of all linked docs pages have been merged to the docs site.
4+
35
A simple, TypeScript-first state machine implementation for Zedux. This is an addon package, meaning it doesn't have any own dependencies or re-export any APIs from other packages. It uses peer dependencies instead, expecting you to download the needed packages yourself.
46

57
See [the documentation](https://omnistac.github.io/zedux/docs/packages/machines/overview) for this package.

packages/machines/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
"react-dom": "catalog:",
1818
"@types/react-dom": "catalog:",
1919
"@types/react": "catalog:",
20-
"@zedux/atoms": "2.0.0-rc.12",
21-
"@zedux/core": "2.0.0-rc.12"
20+
"@zedux/atoms": "2.0.0-rc.12"
2221
},
2322
"exports": {
2423
".": {
@@ -43,8 +42,7 @@
4342
],
4443
"license": "MIT",
4544
"peerDependencies": {
46-
"@zedux/atoms": "2.0.0-rc.12",
47-
"@zedux/core": "2.0.0-rc.12"
45+
"@zedux/atoms": "2.0.0-rc.12"
4846
},
4947
"repository": {
5048
"directory": "packages/machines",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {
2+
Ecosystem,
3+
RecursivePartial,
4+
Signal,
5+
Settable,
6+
} from '@zedux/atoms'
7+
import { MachineStateShape } from './types'
8+
9+
// eslint-disable-next-line @typescript-eslint/ban-types
10+
const signalSend = Signal.prototype.send as (...args: any[]) => void
11+
12+
/**
13+
* A low-level Signal subclass that represents a state machine. Don't create
14+
* this class yourself, use a helper such as @zedux/machines'
15+
* `injectMachineSignal()`
16+
*/
17+
export class MachineSignal<
18+
StateNames extends string = string,
19+
EventNames extends string = string,
20+
Context extends Record<string, any> | undefined = undefined
21+
> extends Signal<{
22+
Events: Record<EventNames, undefined>
23+
Params: undefined
24+
State: MachineStateShape<StateNames, Context>
25+
Template: undefined
26+
}> {
27+
#guard?: (currentState: any, nextValue: any) => boolean
28+
29+
#states: Record<
30+
string,
31+
Record<string, { name: string; guard?: (context: any) => boolean }>
32+
>
33+
34+
constructor(
35+
ecosystem: Ecosystem,
36+
id: string,
37+
initialState: StateNames,
38+
states: Record<
39+
StateNames,
40+
Record<
41+
EventNames,
42+
{ name: StateNames; guard?: (context: Context) => boolean }
43+
>
44+
>,
45+
initialContext?: Context,
46+
guard?: (
47+
currentState: MachineStateShape<StateNames, Context>,
48+
nextValue: StateNames
49+
) => boolean
50+
) {
51+
super(ecosystem, id, {
52+
context: initialContext as Context,
53+
value: initialState,
54+
})
55+
56+
this.#states = states
57+
this.#guard = guard
58+
59+
this.on(eventMap => {
60+
for (const key of Object.keys(eventMap)) {
61+
const currentState = this.v
62+
const transition = this.#states[currentState.value]?.[key]
63+
64+
if (
65+
!transition ||
66+
(transition.guard && !transition.guard(currentState.context)) ||
67+
(this.#guard && !this.#guard(currentState, transition.name))
68+
) {
69+
continue
70+
}
71+
72+
this.set({
73+
context: currentState.context,
74+
value: transition.name as StateNames,
75+
})
76+
}
77+
})
78+
}
79+
80+
public getContext = () => this.v.context
81+
82+
public getValue = () => this.v.value
83+
84+
public is = (stateName: StateNames) => this.v.value === stateName
85+
86+
public send = (
87+
eventNameOrEvents: EventNames | Partial<Record<EventNames, undefined>>
88+
) => {
89+
signalSend.call(this, eventNameOrEvents)
90+
}
91+
92+
public setContext = (context: Settable<Context>) =>
93+
this.set(state => ({
94+
context:
95+
typeof context === 'function'
96+
? (context as (prev: Context) => Context)(state.context)
97+
: context,
98+
value: state.value,
99+
}))
100+
101+
public mutateContext = (
102+
mutatable:
103+
| RecursivePartial<Context>
104+
| ((context: Context) => void | RecursivePartial<Context>)
105+
) => {
106+
if (typeof mutatable === 'function') {
107+
this.mutate(state => {
108+
const result = (
109+
mutatable as (ctx: Context) => void | RecursivePartial<Context>
110+
)(state.context)
111+
112+
if (result && typeof result === 'object') {
113+
return { context: result } as any
114+
}
115+
})
116+
} else {
117+
this.mutate({ context: mutatable } as any)
118+
}
119+
}
120+
}

packages/machines/src/MachineStore.ts

Lines changed: 0 additions & 79 deletions
This file was deleted.

packages/machines/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export * from './injectMachineStore'
2-
export * from './MachineStore'
1+
export * from './injectMachineSignal'
2+
export * from './MachineSignal'
33
export * from './types'

0 commit comments

Comments
 (0)