Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@xstate/graph

Package Overview
Dependencies
Maintainers
2
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@xstate/graph - npm Package Compare versions

Comparing version 1.3.0 to 1.4.0

6

CHANGELOG.md
# @xstate/graph
## 1.4.0
### Minor Changes
- [#2703](https://github.com/statelyai/xstate/pull/2703) [`6a0ff73bf`](https://github.com/statelyai/xstate/commit/6a0ff73bf8817dc401ef9b45c71dd7875dbc9f20) Thanks [@Silverwolf90](https://github.com/Silverwolf90)! - Add getPathFromEvents to generate a path from a sequence of events.
## 1.3.0

@@ -4,0 +10,0 @@

2

es/graph.d.ts
import { StateNode, State, DefaultContext, Event, EventObject, StateMachine, AnyEventObject } from 'xstate';
import { StatePath } from '.';
import { StatePathsMap, StatePaths, AdjacencyMap, ValueAdjMapOptions, DirectedGraphNode } from './types';

@@ -18,2 +19,3 @@ export declare function toEventObject<TEvent extends EventObject>(event: Event<TEvent>): TEvent;

export declare function toDirectedGraph(stateNode: StateNode): DirectedGraphNode;
export declare function getPathFromEvents<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateMachine<TContext, any, TEvent>, events: Array<TEvent>): StatePath<TContext, TEvent>;
//# sourceMappingURL=graph.d.ts.map

@@ -28,5 +28,6 @@ var __assign = (this && this.__assign) || function () {

};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};

@@ -61,3 +62,3 @@ var __values = (this && this.__values) || function(o) {

var childStateNodes = getStateNodes(childStateNode);
accNodes.push.apply(accNodes, __spread([childStateNode], childStateNodes));
accNodes.push.apply(accNodes, __spreadArray([childStateNode], __read(childStateNodes)));
return accNodes;

@@ -266,3 +267,3 @@ }, []);

weight: path.length,
segments: __spread(path)
segments: __spreadArray([], __read(path))
});

@@ -354,1 +355,58 @@ }

}
export function getPathFromEvents(machine, events) {
var e_6, _a;
var optionsWithDefaults = getValueAdjMapOptions({
events: events.reduce(function (events, event) {
var _a;
var _b;
(_a = events[_b = event.type]) !== null && _a !== void 0 ? _a : (events[_b] = []);
events[event.type].push(event);
return events;
}, {})
});
var stateSerializer = optionsWithDefaults.stateSerializer, eventSerializer = optionsWithDefaults.eventSerializer;
if (!machine.states) {
return {
state: machine.initialState,
segments: [],
weight: 0
};
}
var adjacency = getAdjacencyMap(machine, optionsWithDefaults);
var stateMap = new Map();
var path = [];
var initialStateSerial = stateSerializer(machine.initialState);
stateMap.set(initialStateSerial, machine.initialState);
var stateSerial = initialStateSerial;
var state = machine.initialState;
try {
for (var events_1 = __values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) {
var event_3 = events_1_1.value;
path.push({
state: stateMap.get(stateSerial),
event: event_3
});
var eventSerial = eventSerializer(event_3);
var nextSegment = adjacency[stateSerial][eventSerial];
if (!nextSegment) {
throw new Error("Invalid transition from " + stateSerial + " with " + eventSerial);
}
var nextStateSerial = stateSerializer(nextSegment.state);
stateMap.set(nextStateSerial, nextSegment.state);
stateSerial = nextStateSerial;
state = nextSegment.state;
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
}
finally { if (e_6) throw e_6.error; }
}
return {
state: state,
segments: path,
weight: path.length
};
}

4

es/index.d.ts

@@ -1,4 +0,4 @@

import { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
import { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
export * from './types';
//# sourceMappingURL=index.d.ts.map

@@ -1,3 +0,3 @@

import { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
import { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
export * from './types';
import { StateNode, State, DefaultContext, Event, EventObject, StateMachine, AnyEventObject } from 'xstate';
import { StatePath } from '.';
import { StatePathsMap, StatePaths, AdjacencyMap, ValueAdjMapOptions, DirectedGraphNode } from './types';

@@ -18,2 +19,3 @@ export declare function toEventObject<TEvent extends EventObject>(event: Event<TEvent>): TEvent;

export declare function toDirectedGraph(stateNode: StateNode): DirectedGraphNode;
export declare function getPathFromEvents<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateMachine<TContext, any, TEvent>, events: Array<TEvent>): StatePath<TContext, TEvent>;
//# sourceMappingURL=graph.d.ts.map

@@ -29,5 +29,6 @@ "use strict";

};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};

@@ -46,3 +47,3 @@ var __values = (this && this.__values) || function(o) {

Object.defineProperty(exports, "__esModule", { value: true });
exports.toDirectedGraph = exports.getSimplePathsAsArray = exports.getSimplePaths = exports.getShortestPaths = exports.getAdjacencyMap = exports.deserializeEventString = exports.serializeEvent = exports.serializeState = exports.getChildren = exports.getStateNodes = exports.toEventObject = void 0;
exports.getPathFromEvents = exports.toDirectedGraph = exports.getSimplePathsAsArray = exports.getSimplePaths = exports.getShortestPaths = exports.getAdjacencyMap = exports.deserializeEventString = exports.serializeEvent = exports.serializeState = exports.getChildren = exports.getStateNodes = exports.toEventObject = void 0;
var utils_1 = require("xstate/lib/utils");

@@ -66,3 +67,3 @@ function toEventObject(event) {

var childStateNodes = getStateNodes(childStateNode);
accNodes.push.apply(accNodes, __spread([childStateNode], childStateNodes));
accNodes.push.apply(accNodes, __spreadArray([childStateNode], __read(childStateNodes)));
return accNodes;

@@ -278,3 +279,3 @@ }, []);

weight: path.length,
segments: __spread(path)
segments: __spreadArray([], __read(path))
});

@@ -369,1 +370,59 @@ }

exports.toDirectedGraph = toDirectedGraph;
function getPathFromEvents(machine, events) {
var e_6, _a;
var optionsWithDefaults = getValueAdjMapOptions({
events: events.reduce(function (events, event) {
var _a;
var _b;
(_a = events[_b = event.type]) !== null && _a !== void 0 ? _a : (events[_b] = []);
events[event.type].push(event);
return events;
}, {})
});
var stateSerializer = optionsWithDefaults.stateSerializer, eventSerializer = optionsWithDefaults.eventSerializer;
if (!machine.states) {
return {
state: machine.initialState,
segments: [],
weight: 0
};
}
var adjacency = getAdjacencyMap(machine, optionsWithDefaults);
var stateMap = new Map();
var path = [];
var initialStateSerial = stateSerializer(machine.initialState);
stateMap.set(initialStateSerial, machine.initialState);
var stateSerial = initialStateSerial;
var state = machine.initialState;
try {
for (var events_1 = __values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) {
var event_3 = events_1_1.value;
path.push({
state: stateMap.get(stateSerial),
event: event_3
});
var eventSerial = eventSerializer(event_3);
var nextSegment = adjacency[stateSerial][eventSerial];
if (!nextSegment) {
throw new Error("Invalid transition from " + stateSerial + " with " + eventSerial);
}
var nextStateSerial = stateSerializer(nextSegment.state);
stateMap.set(nextStateSerial, nextSegment.state);
stateSerial = nextStateSerial;
state = nextSegment.state;
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
}
finally { if (e_6) throw e_6.error; }
}
return {
state: state,
segments: path,
weight: path.length
};
}
exports.getPathFromEvents = getPathFromEvents;

@@ -1,4 +0,4 @@

import { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
import { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph } from './graph';
export { getStateNodes, getPathFromEvents, getSimplePaths, getShortestPaths, serializeEvent, serializeState, toDirectedGraph };
export * from './types';
//# sourceMappingURL=index.d.ts.map

@@ -13,5 +13,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.toDirectedGraph = exports.serializeState = exports.serializeEvent = exports.getShortestPaths = exports.getSimplePaths = exports.getStateNodes = void 0;
exports.toDirectedGraph = exports.serializeState = exports.serializeEvent = exports.getShortestPaths = exports.getSimplePaths = exports.getPathFromEvents = exports.getStateNodes = void 0;
var graph_1 = require("./graph");
Object.defineProperty(exports, "getStateNodes", { enumerable: true, get: function () { return graph_1.getStateNodes; } });
Object.defineProperty(exports, "getPathFromEvents", { enumerable: true, get: function () { return graph_1.getPathFromEvents; } });
Object.defineProperty(exports, "getSimplePaths", { enumerable: true, get: function () { return graph_1.getSimplePaths; } });

@@ -18,0 +19,0 @@ Object.defineProperty(exports, "getShortestPaths", { enumerable: true, get: function () { return graph_1.getShortestPaths; } });

{
"name": "@xstate/graph",
"version": "1.3.0",
"version": "1.4.0",
"description": "XState graph utilities",

@@ -14,3 +14,3 @@ "keywords": [

"author": "David Khourshid <davidkpiano@gmail.com>",
"homepage": "https://github.com/davidkpiano/xstate/tree/master/packages/xstate-test#readme",
"homepage": "https://github.com/davidkpiano/xstate/tree/main/packages/xstate-test#readme",
"license": "MIT",

@@ -44,6 +44,6 @@ "main": "lib/index.js",

"devDependencies": {
"jest": "^26.4.2",
"jest": "^26.6.3",
"lerna-alias": "3.0.3-0",
"ts-jest": "^26.4.0",
"typescript": "^4.1.2",
"ts-jest": "^26.5.6",
"typescript": "^4.3.5",
"xstate": "*"

@@ -50,0 +50,0 @@ },

@@ -5,4 +5,7 @@ # @xstate/graph

## Quick Start
- [Read the full documentation in the XState docs](https://xstate.js.org/docs/packages/xstate-graph/).
- [Read our contribution guidelines](https://github.com/statelyai/xstate/blob/main/CONTRIBUTING.md).
## Quick start
1. Install `xstate` and `@xstate/graph`:

@@ -17,390 +20,7 @@

```js
import { Machine } from 'xstate';
import { createMachine } from 'xstate';
import { getSimplePaths } from '@xstate/graph';
const machine = Machine(/* ... */);
const machine = createMachine(/* ... */);
const paths = getSimplePaths(machine);
```
## API
### `getShortestPaths(machine, options?)`
**Arguments**
- `machine` - the [`Machine`](https://xstate.js.org/docs/guides/machines.html) to traverse
- `options` (optional) - [options](#options) that customize how the algorithm will traverse the machine
Returns the [shortest paths (Dijkstra's algorithm)](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) of a [machine](https://xstate.js.org/docs/guides/machines.html) from the initial state to every other state as a mapped object, where the:
- **key** is the stringified state
- **value** is an object with the properties:
- `state` - the target [`State`](https://xstate.js.org/docs/guides/states.html)
- `path` - the shortest path to get from the initial state to the target state
The `path` is an array of segments, where each segment is an object with the properties:
- `state` - the [`State`](https://xstate.js.org/docs/guides/states.html) of the segment
- `weight` - the total [weight](<https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Weighted_graph>) of the path
- Currently, each transition from one state to another has a weight of 1. This will be customizable in the future.
- `event` - the event object that transitions the `machine` from the state to the next state in the path
Every path starts with the initial state.
The overall object structure looks like this:
```json5
{
"<SERIALIZED STATE>": {
"state": State { ... },
"path": [
{
"state": State { ... },
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
},
{
"state": State { ... },
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
},
...
]
},
...
}
```
**Example**
```js
import { Machine } from 'xstate';
import { getShortestPaths } from '@xstate/graph';
const feedbackMachine = Machine({
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
CLICK_GOOD: 'thanks',
CLICK_BAD: 'form',
CLOSE: 'closed',
ESC: 'closed'
}
},
form: {
on: {
SUBMIT: 'thanks',
CLOSE: 'closed',
ESC: 'closed'
}
},
thanks: {
on: {
CLOSE: 'closed',
ESC: 'closed'
}
},
closed: {
type: 'final'
}
}
});
const shortestPaths = getShortestPaths(feedbackMachine);
console.log(shortestPaths);
// => {
// '"question"': {
// state: State { value: 'question', context: undefined },
// weight: 0,
// path: []
// },
// '"thanks"': {
// state: State { value: 'thanks', context: undefined },
// weight: 1,
// path: [
// {
// state: State { value: 'question', context: undefined },
// event: { type: 'CLICK_GOOD' }
// }
// ]
// },
// '"form"': {
// state: State { value: 'form', context: undefined },
// weight: 1,
// path: [
// {
// state: State { value: 'question', context: undefined },
// event: { type: 'CLICK_BAD' }
// }
// ]
// },
// '"closed"': {
// state: State { value: 'closed', context: undefined },
// weight: 1,
// path: [
// {
// state: State { value: 'question', context: undefined },
// event: { type: 'CLOSE' }
// }
// ]
// }
// };
```
### `getSimplePaths(machine, options?)`
**Arguments**
- `machine` - the [`Machine`](https://xstate.js.org/docs/guides/machines.html) to traverse
- `options` (optional) - [options](#options) that customize how the algorithm will traverse the machine
Returns the [simple paths](<https://en.wikipedia.org/wiki/Path_(graph_theory)#Definitions>) of a [machine](https://xstate.js.org/docs/guides/machines.html) as a mapped object, where the:
- **key** is the stringified state
- **value** is an object with the properties:
- `state` - the target [`State`](https://xstate.js.org/docs/guides/states.html)
- `paths` - the array of paths to get from the initial state to the target state
Each `path` in `paths` is an array of segments, where each segment of the path is an object with the properties:
- `state` - the [`State`](https://xstate.js.org/docs/guides/states.html) of the segment
- `event` - the event object that transitions the `machine` from the state to the next state in the path
Every path starts with the initial state.
The overall object structure looks like this:
```json5
{
"<SERIALIZED STATE>": {
"state": State { ... },
"paths": [
[
{
"state": State { ... },
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
},
{
"state": State { ... },
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" }
},
...
],
...
]
},
...
}
```
**Example**
```js
import { Machine } from 'xstate';
import { getSimplePaths } from '@xstate/graph';
const feedbackMachine = Machine({
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
CLICK_GOOD: 'thanks',
CLICK_BAD: 'form',
CLOSE: 'closed',
ESC: 'closed'
}
},
form: {
on: {
SUBMIT: 'thanks',
CLOSE: 'closed',
ESC: 'closed'
}
},
thanks: {
on: {
CLOSE: 'closed',
ESC: 'closed'
}
},
closed: {
type: 'final'
}
}
});
const simplePaths = getSimplePaths(feedbackMachine);
console.log(simplePaths);
// => {
// '"question"': {
// state: { value: 'question', context: undefined },
// paths: [[]]
// },
// '"thanks"': {
// state: { value: 'thanks', context: undefined },
// paths: [
// [
// {
// state: { value: 'question', context: undefined },
// event: { type: 'CLICK_GOOD' }
// }
// ],
// [
// {
// state: { value: 'question', context: undefined },
// event: { type: 'CLICK_BAD' }
// },
// {
// state: { value: 'form', context: undefined },
// event: { type: 'SUBMIT' }
// }
// ]
// ]
// },
// '"closed"': {
// state: { value: 'closed', context: undefined },
// paths: [
// [
// {
// state: { value: 'question', context: undefined },
// event: { type: 'CLICK_GOOD' }
// },
// {
// state: { value: 'thanks', context: undefined },
// event: { type: 'CLOSE' }
// }
// ],
// [
// {
// state: { value: 'question', context: undefined },
// event: { type: 'CLICK_GOOD' }
// },
// {
// state: { value: 'thanks', context: undefined },
// event: { type: 'ESC' }
// }
// ],
// ...
// ]
// },
// ...
// };
```
### `toDirectedGraph(machine)`
Converts a `machine` to a directed graph structure.
| Argument | Type | Description |
| --------- | ---------------------------------------------- | ---------------------------------------------------- |
| `machine` | XState Machine created by `createMachine(...)` | The machine to convert to a directed graph structure |
**Example**
```js
import { toDirectedGraph } from '@xstate/graph';
const machine = createMachine({/* ... */});
const digraph = toDirectedGraph(machine);
// returns an object with this structure:
{
id: '...',
stateNode: /* StateNode */,
children: [
{ id: '...', children: [/* ... */], edges: [/* ... */] },
{ id: '...', /* ... */ },
// ...
],
edges: [
{ source: /* ... */, target: /* ... */, transition: /* ... */ }
// ...
]
}
```
## Options
Options can be passed into `getShortestPaths` or `getSimplePaths` to customize how the graph represented by the machine should be traversed:
- `events` - a mapping of event types to an array of event objects to be used for those events
- `filter` - a function that determines whether a `state` should be traversed. If `false`, the traversal algorithm(s) will assume the state was "seen" and ignore traversing it.
**Examples**
In the below example, the `INC` event is expanded to include two possible events, with `value: 1` and `value: 2` as the payload. It also ensures that the `state.context.count <= 5`; otherwise, this machine would be traversed infinitely.
```js
const counterMachine = Machine({
id: 'counter',
initial: 'active',
context: { count: 0 },
states: {
active: {
on: {
INC: {
actions: assign({ count: (ctx, e) => ctx.count + e.value })
}
}
}
}
});
const shortestPaths = getShortestPaths(counterMachine, {
events: {
INC: [
{ type: 'INC', value: 1 },
{ type: 'INC', value: 2 }
]
},
filter: (state) => state.context.count <= 5
});
console.log(shortestPaths);
// => {
// '"active" | {"count":0}': {
// state: { value: 'active', context: { count: 0 } },
// weight: 0,
// path: []
// },
// '"active" | {"count":1}': {
// state: { value: 'active', context: { count: 1 } },
// weight: 1,
// path: [
// {
// state: { value: 'active', context: { count: 0 } },
// event: { type: 'INC', value: 1 }
// }
// ]
// },
// '"active" | {"count":2}': {
// state: { value: 'active', context: { count: 2 } },
// weight: 1,
// path: [
// {
// state: { value: 'active', context: { count: 0 } },
// event: { type: 'INC', value: 2 }
// }
// ]
// },
// '"active" | {"count":3}': {
// state: { value: 'active', context: { count: 3 } },
// weight: 2,
// path: [
// {
// state: { value: 'active', context: { count: 0 } },
// event: { type: 'INC', value: 1 }
// },
// {
// state: { value: 'active', context: { count: 1 } },
// event: { type: 'INC', value: 2 }
// }
// ]
// },
// ...
// };
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc