Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/atoms/src/utils/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import type {
* that they don't prevent node destruction.
*
* IMPORTANT: Keep these in-sync with the copies in the react package -
* packages/react/src/utils.ts
* packages/react/src/utils.ts - and the machines package =
* packages/machines/src/utils.ts
*/
export const TopPrio = 0
export const Eventless = 1
Expand Down
2 changes: 2 additions & 0 deletions packages/machines/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `@zedux/machines`

> 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.

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.

See [the documentation](https://omnistac.github.io/zedux/docs/packages/machines/overview) for this package.
Expand Down
6 changes: 2 additions & 4 deletions packages/machines/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
"react-dom": "catalog:",
"@types/react-dom": "catalog:",
"@types/react": "catalog:",
"@zedux/atoms": "2.0.0-rc.12",
"@zedux/core": "2.0.0-rc.12"
"@zedux/atoms": "2.0.0-rc.12"
},
"exports": {
".": {
Expand All @@ -43,8 +42,7 @@
],
"license": "MIT",
"peerDependencies": {
"@zedux/atoms": "2.0.0-rc.12",
"@zedux/core": "2.0.0-rc.12"
"@zedux/atoms": "2.0.0-rc.12"
},
"repository": {
"directory": "packages/machines",
Expand Down
120 changes: 120 additions & 0 deletions packages/machines/src/MachineSignal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {
Ecosystem,
RecursivePartial,
Signal,
Settable,
} from '@zedux/atoms'
import { MachineStateShape } from './types'

// eslint-disable-next-line @typescript-eslint/ban-types
const signalSend = Signal.prototype.send as (...args: any[]) => void

/**
* A low-level Signal subclass that represents a state machine. Don't create
* this class yourself, use a helper such as @zedux/machines'
* `injectMachineSignal()`
*/
export class MachineSignal<
StateNames extends string = string,
EventNames extends string = string,
Context extends Record<string, any> | undefined = undefined
> extends Signal<{
Events: Record<EventNames, undefined>
Params: undefined
State: MachineStateShape<StateNames, Context>
Template: undefined
}> {
#guard?: (currentState: any, nextValue: any) => boolean

#states: Record<
string,
Record<string, { name: string; guard?: (context: any) => boolean }>
>

constructor(
ecosystem: Ecosystem,
id: string,
initialState: StateNames,
states: Record<
StateNames,
Record<
EventNames,
{ name: StateNames; guard?: (context: Context) => boolean }
>
>,
initialContext?: Context,
guard?: (
currentState: MachineStateShape<StateNames, Context>,
nextValue: StateNames
) => boolean
) {
super(ecosystem, id, {
context: initialContext as Context,
value: initialState,
})

this.#states = states
this.#guard = guard

this.on(eventMap => {
for (const key of Object.keys(eventMap)) {
const currentState = this.v
const transition = this.#states[currentState.value]?.[key]

if (
!transition ||
(transition.guard && !transition.guard(currentState.context)) ||
(this.#guard && !this.#guard(currentState, transition.name))
) {
continue
}

this.set({
context: currentState.context,
value: transition.name as StateNames,
})
}
})
}

public getContext = () => this.v.context

public getValue = () => this.v.value

public is = (stateName: StateNames) => this.v.value === stateName

public send = (
eventNameOrEvents: EventNames | Partial<Record<EventNames, undefined>>
) => {
signalSend.call(this, eventNameOrEvents)
}

public setContext = (context: Settable<Context>) =>
this.set(state => ({
context:
typeof context === 'function'
? (context as (prev: Context) => Context)(state.context)
: context,
value: state.value,
}))

public mutateContext = (
mutatable:
| RecursivePartial<Context>
| ((context: Context) => void | RecursivePartial<Context>)
) => {
if (typeof mutatable === 'function') {
this.mutate(state => {
const result = (
mutatable as (ctx: Context) => void | RecursivePartial<Context>
)(state.context)

if (result && typeof result === 'object') {
return { context: result } as any
}
})
} else {
this.mutate({ context: mutatable } as any)
}
}
}
79 changes: 0 additions & 79 deletions packages/machines/src/MachineStore.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/machines/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './injectMachineStore'
export * from './MachineStore'
export * from './injectMachineSignal'
export * from './MachineSignal'
export * from './types'
Loading