@xstate/fsm
Advanced tools
Comparing version 1.6.2 to 1.6.3
# @xstate/fsm | ||
## 1.6.3 | ||
### Patch Changes | ||
- [#2474](https://github.com/davidkpiano/xstate/pull/2474) Thanks [@annaghi](https://github.com/annaghi)! - Use CommonJS files as `package.json#main` (instead of UMD files) as this plays better with native ESM loader in node (and by extension fixes compatibility issues with projects like [SvelteKit](https://kit.svelte.dev/)). | ||
## 1.6.2 | ||
@@ -4,0 +10,0 @@ |
@@ -0,0 +0,0 @@ import { StateMachine, EventObject, Typestate, InterpreterStatus } from './types'; |
@@ -1,3 +0,7 @@ | ||
import { InterpreterStatus } from './types'; | ||
export { InterpreterStatus }; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var types = require('./types.js'); | ||
const INIT_EVENT = { type: 'xstate.init' }; | ||
@@ -8,3 +12,3 @@ const ASSIGN_ACTION = 'xstate.assign'; | ||
} | ||
export function assign(assignment) { | ||
function assign(assignment) { | ||
return { | ||
@@ -74,3 +78,3 @@ type: ASSIGN_ACTION, | ||
} | ||
export function createMachine(fsmConfig, options = {}) { | ||
function createMachine(fsmConfig, options = {}) { | ||
if (!IS_PRODUCTION) { | ||
@@ -144,5 +148,5 @@ Object.keys(fsmConfig.states).forEach((state) => { | ||
const executeStateActions = (state, event) => state.actions.forEach(({ exec }) => exec && exec(state.context, event)); | ||
export function interpret(machine) { | ||
function interpret(machine) { | ||
let state = machine.initialState; | ||
let status = InterpreterStatus.NotStarted; | ||
let status = types.InterpreterStatus.NotStarted; | ||
const listeners = new Set(); | ||
@@ -152,3 +156,3 @@ const service = { | ||
send: (event) => { | ||
if (status !== InterpreterStatus.Running) { | ||
if (status !== types.InterpreterStatus.Running) { | ||
return; | ||
@@ -184,3 +188,3 @@ } | ||
} | ||
status = InterpreterStatus.Running; | ||
status = types.InterpreterStatus.Running; | ||
executeStateActions(state, INIT_EVENT); | ||
@@ -190,3 +194,3 @@ return service; | ||
stop: () => { | ||
status = InterpreterStatus.Stopped; | ||
status = types.InterpreterStatus.Stopped; | ||
listeners.clear(); | ||
@@ -204,1 +208,11 @@ return service; | ||
} | ||
Object.defineProperty(exports, 'InterpreterStatus', { | ||
enumerable: true, | ||
get: function () { | ||
return types.InterpreterStatus; | ||
} | ||
}); | ||
exports.assign = assign; | ||
exports.createMachine = createMachine; | ||
exports.interpret = interpret; |
@@ -0,0 +0,0 @@ export declare enum InterpreterStatus { |
@@ -1,2 +0,5 @@ | ||
export var InterpreterStatus; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
(function (InterpreterStatus) { | ||
@@ -6,2 +9,2 @@ InterpreterStatus[InterpreterStatus["NotStarted"] = 0] = "NotStarted"; | ||
InterpreterStatus[InterpreterStatus["Stopped"] = 2] = "Stopped"; | ||
})(InterpreterStatus || (InterpreterStatus = {})); | ||
})(exports.InterpreterStatus || (exports.InterpreterStatus = {})); |
{ | ||
"name": "@xstate/fsm", | ||
"version": "1.6.2", | ||
"version": "1.6.3", | ||
"description": "XState for finite state machines", | ||
@@ -20,3 +20,3 @@ "keywords": [ | ||
"license": "MIT", | ||
"main": "dist/xstate.fsm.js", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
@@ -26,4 +26,3 @@ "module": "es/index.js", | ||
"files": [ | ||
"dist/**/*.js", | ||
"dist/**/*.d.ts", | ||
"dist", | ||
"lib/**/*.js", | ||
@@ -39,3 +38,3 @@ "lib/**/*.d.ts", | ||
"scripts": { | ||
"clean": "rm -rf dist lib tsconfig.tsbuildinfo", | ||
"clean": "rm -rf lib es dist tsconfig.tsbuildinfo", | ||
"build": "rollup -c", | ||
@@ -57,4 +56,4 @@ "test": "jest", | ||
"ts-jest": "^26.5.6", | ||
"typescript": "^4.3.5" | ||
"typescript": "^4.5.2" | ||
} | ||
} |
343
README.md
@@ -14,7 +14,10 @@ # @xstate/fsm | ||
This package contains a minimal, 1kb implementation of [XState](https://github.com/davidkpiano/xstate) for **finite state machines**. | ||
This package contains a minimal, 1kb implementation of [XState](https://github.com/statelyai/xstate) for **finite state machines**. | ||
- [Read the full documentation in the XState docs](https://xstate.js.org/docs/packages/xstate-fsm/). | ||
- [Read our contribution guidelines](https://github.com/statelyai/xstate/blob/main/CONTRIBUTING.md). | ||
## Features | ||
| | **@xstate/fsm** | [XState](https://github.com/davidkpiano/xstate) | | ||
| | **@xstate/fsm** | [XState](https://github.com/statelyai/xstate) | | ||
| --------------------------- | :-------------: | :---------------------------------------------: | | ||
@@ -50,7 +53,7 @@ | Finite states | ✅ | ✅ | | ||
If you want to use statechart features such as nested states, parallel states, history states, activities, invoked services, delayed transitions, transient transitions, etc. please use [`XState`](https://github.com/davidkpiano/xstate). | ||
If you want to use statechart features such as nested states, parallel states, history states, activities, invoked services, delayed transitions, transient transitions, etc. please use [`XState`](https://github.com/statelyai/xstate). | ||
## Super quick start | ||
## Quick start | ||
**Installation** | ||
### Installation | ||
@@ -61,3 +64,3 @@ ```bash | ||
**Usage (machine):** | ||
### Usage (machine) | ||
@@ -85,3 +88,3 @@ ```js | ||
**Usage (service):** | ||
### Usage (service) | ||
@@ -103,327 +106,1 @@ ```js | ||
``` | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
- [Super quick start](#super-quick-start) | ||
- [API](#api) | ||
- [`createMachine(config)`](#createmachineconfig) | ||
- [Machine config](#machine-config) | ||
- [State config](#state-config) | ||
- [Transition config](#transition-config) | ||
- [Machine options](#machine-options) | ||
- [Action config](#action-config) | ||
- [`machine.initialState`](#machineinitialstate) | ||
- [`machine.transition(state, event)`](#machinetransitionstate-event) | ||
- [State](#state) | ||
- [`interpret(machine)`](#interpretmachine) | ||
- [`service.subscribe(stateListener)`](#servicesubscribestatelistener) | ||
- [`service.send(event)`](#servicesendevent) | ||
- [`service.start()`](#servicestart) | ||
- [`service.stop()`](#servicestop) | ||
- [TypeScript](#typescript) | ||
- [Example](#example) | ||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
## API | ||
### `createMachine(config, options)` | ||
Creates a new finite state machine from the config. | ||
| Argument | Type | Description | | ||
| --------- | ------------------ | ------------------------------------------- | | ||
| `config` | object (see below) | The config object for creating the machine. | | ||
| `options` | object (see below) | The optional options object. | | ||
**Returns:** | ||
A `Machine`, which provides: | ||
- `machine.initialState`: the machine's resolved initial state | ||
- `machine.transition(state, event)`: a pure transition function that returns the next state given the current `state` and `event` | ||
The machine config has this schema: | ||
### Machine config | ||
- `id` (string) - an identifier for the type of machine this is. Useful for debugging. | ||
- `initial` (string) - the key of the initial state. | ||
- `states` (object) - an object mapping state names (keys) to [states](#state-config) | ||
### State config | ||
- `on` (object) - an object mapping event types (keys) to [transitions](#transition-config) | ||
### Transition config | ||
String syntax: | ||
- (string) - the state name to transition to. | ||
- Same as `{ target: stateName }` | ||
Object syntax: | ||
- `target?` (string) - the state name to transition to. | ||
- `actions?` (Action | Action[]) - the [action(s)](#action-config) to execute when this transition is taken. | ||
- `cond?` (Guard) - the condition (predicate function) to test. If it returns `true`, the transition will be taken. | ||
### Machine options | ||
- `actions?` (object) - a lookup object for your string actions. | ||
### Action config | ||
Function syntax: | ||
- (function) - the action function to execute. Resolves to `{ type: actionFn.name, exec: actionFn }` and the function takes the following arguments: | ||
1. `context` (any) - the machine's current `context`. | ||
2. `event` (object) - the event that caused the action to be executed. | ||
Object syntax: | ||
- `type` (string) - the action type. | ||
- `exec?` (function) - the action function to execute. | ||
String syntax: | ||
- (string) - the action type. | ||
- By default it resolves to `{ type: actionType, exec: undefined }`. It can resolve to resolved function or resolved object action **if** the action can be looked up in the `options.actions` object. | ||
<details> | ||
<summary>Why use a string or object for defining actions?</summary> | ||
Using the string or object syntax is useful for handling actions in a custom way, rather than baking in the implementation details to your machine: | ||
```js | ||
const nextState = machine.transition(); | ||
nextState.actions.forEach((action) => { | ||
if (action.type === 'focus') { | ||
} | ||
}); | ||
``` | ||
</details> | ||
### `machine.initialState` | ||
The resolved initial state of the `machine`. | ||
### `machine.transition(state, event)` | ||
A pure transition function that returns the next state given the current `state` and `event`. | ||
The state can be a `string` state name, or a `State` object (the return type of `machine.transition(...)`). | ||
| Argument | Type | Description | | ||
| -------- | ----------------------------------- | ---------------------------------------------------------------- | | ||
| `state` | `string` or `State` object | The current state to transition from | | ||
| `event` | `string` or `{ type: string, ... }` | The event that transitions the current `state` to the next state | | ||
**Returns:** | ||
A `State` object, which represents the next state. | ||
**Example:** | ||
```js | ||
const yellowState = machine.transition('green', 'TIMER'); | ||
const redState = machine.transition(yellowState, 'TIMER'); | ||
const greenState = machine.transition(yellowState, { type: 'TIMER' }); | ||
// => { value: 'green', ... } | ||
``` | ||
### State | ||
An object that represents the state of a machine with the following schema: | ||
- `value` (string) - the finite state value | ||
- `context` (object) - the extended state (context) | ||
- `actions` (array) - an array of action objects representing the side-effects (actions) to be executed | ||
- `changed` (boolean) - whether this state is changed from the previous state (`true` if the `state.value` and `state.context` are the same, and there are no side-effects) | ||
- `matches(value)` (boolean) - whether this state's value matches (i.e., is equal to) the `value`. This is useful for typestate checking. | ||
### `interpret(machine)` | ||
Creates an instance of an interpreted machine, also known as a **service**. This is a stateful representation of the running machine, which you can subscribe to, send events to, start, and stop. | ||
Actions will also be executed by the interpreter. | ||
| Argument | Type | Description | | ||
| --------- | ------------ | ------------------------------ | | ||
| `machine` | StateMachine | The machine to be interpreted. | | ||
**Example:** | ||
```js | ||
import { createMachine, interpret } from '@xstate/fsm'; | ||
const machine = createMachine({}); | ||
const service = interpret(machine); | ||
const subscription = service.subscribe((state) => { | ||
console.log(state); | ||
}); | ||
service.start(); | ||
service.send('SOME_EVENT'); | ||
service.send({ type: 'ANOTHER_EVENT' }); | ||
subscription.unsubscribe(); | ||
service.stop(); | ||
``` | ||
### `service.subscribe(stateListener)` | ||
A service (created from `interpret(machine)`) can be subscribed to via the `.subscribe(...)` method. The subscription will be notified of all state changes (including the initial state) and can be unsubscribed. | ||
| Argument | Type | Description | | ||
| --------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | ||
| `stateListener` | `(state) => void` | The listener that is called with the interpreted machine's current `state` whenever it changes. | | ||
**Returns:** | ||
A subscription object with an `unsubscribe` method. | ||
### `service.send(event)` | ||
Sends an `event` to the interpreted machine. The event can be a string (e.g., `"EVENT"`) or an object with a `type` property (e.g., `{ type: "EVENT" }`). | ||
| Argument | Type | Description | | ||
| -------- | ----------------------------------- | ------------------------------------------------ | | ||
| `event` | `string` or `{ type: string, ... }` | The event to be sent to the interpreted machine. | | ||
### `service.start()` | ||
Starts the interpreted machine. | ||
Events sent to the interpreted machine will not trigger any transitions until the service is started. All listeners (via `service.subscribe(listener)`) will receive the `machine.initialState`. | ||
### `service.stop()` | ||
Stops the interpreted machine. | ||
Events sent to a stopped service will no longer trigger any transitions. All listeners (via `service.subscribe(listener)`) will be unsubscribed. | ||
## TypeScript | ||
A machine can be strictly typed by passing in 3 generic types: | ||
- `TContext` - the machine's `context` | ||
- `TEvent` - all events that the machine accepts | ||
- `TState` - all states that the machine can be in | ||
The `TContext` type should be an `object` that represents all possible combined types of `state.context`. | ||
The `TEvent` type should be the union of all event objects that the machine can accept, where each event object has a `{ type: string }` property, as well as any other properties that may be present. | ||
The `TState` type should be the union of all typestates (value and contexts) that the machine can be in, where each typestate has: | ||
- `value` (string) - the value (name) of the state | ||
- `context` (object) - an object that extends `TContext` and narrows the shape of the context to what it should be in this state. | ||
**Example:** | ||
```ts | ||
interface User { | ||
name: string; | ||
} | ||
interface UserContext { | ||
user?: User; | ||
error?: string; | ||
} | ||
type UserEvent = | ||
| { type: 'FETCH'; id: string } | ||
| { type: 'RESOLVE'; user: User } | ||
| { type: 'REJECT'; error: string }; | ||
type UserState = | ||
| { | ||
value: 'idle'; | ||
context: UserContext & { | ||
user: undefined; | ||
error: undefined; | ||
}; | ||
} | ||
| { | ||
value: 'loading'; | ||
context: UserContext; | ||
} | ||
| { | ||
value: 'success'; | ||
context: UserContext & { user: User; error: undefined }; | ||
} | ||
| { | ||
value: 'failure'; | ||
context: UserContext & { user: undefined; error: string }; | ||
}; | ||
const userMachine = createMachine<UserContext, UserEvent, UserState>({ | ||
/* ... */ | ||
}); | ||
const userService = interpret(userMachine); | ||
userService.subscribe((state) => { | ||
if (state.matches('success')) { | ||
// from UserState, `user` will be defined | ||
state.context.user.name; | ||
} | ||
}); | ||
``` | ||
## Example | ||
```js | ||
import { createMachine, assign, interpret } from '@xstate/fsm'; | ||
const lightMachine = createMachine({ | ||
id: 'light', | ||
initial: 'green', | ||
context: { redLights: 0 }, | ||
states: { | ||
green: { | ||
on: { | ||
TIMER: 'yellow' | ||
} | ||
}, | ||
yellow: { | ||
on: { | ||
TIMER: { | ||
target: 'red', | ||
actions: () => console.log('Going to red!') | ||
} | ||
} | ||
}, | ||
red: { | ||
entry: assign({ redLights: (ctx) => ctx.redLights + 1 }), | ||
on: { | ||
TIMER: 'green' | ||
} | ||
} | ||
} | ||
}); | ||
const lightService = interpret(lightMachine); | ||
lightService.subscribe((state) => { | ||
console.log(state); | ||
}); | ||
lightService.start(); | ||
lightService.send('TIMER'); | ||
lightService.send('TIMER'); | ||
// => logs { | ||
// value: 'red', | ||
// context: { redLights: 1 }, | ||
// actions: [], | ||
// changed: true | ||
// } | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18
600
54001
102
1