@xstate/graph
Advanced tools
Comparing version 0.0.2 to 0.1.0
import { StateNode, State } from 'xstate'; | ||
import { StateValue, Edge, PathMap, PathItem, PathsItem, PathsMap, AdjacencyMap, DefaultContext, ValueAdjacencyMap, EventObject, StateMachine } from 'xstate/lib/types'; | ||
import { StateValue, Edge, PathMap, PathsItem, PathsMap, AdjacencyMap, DefaultContext, ValueAdjacencyMap, EventObject, StateMachine } from 'xstate/lib/types'; | ||
export declare function getNodes(node: StateNode): StateNode[]; | ||
@@ -8,3 +8,3 @@ export declare function getEventEdges<TContext = DefaultContext, TEvent extends EventObject = EventObject>(node: StateNode<TContext>, event: string): Array<Edge<TContext, TEvent>>; | ||
}): Array<Edge<TContext, TEvent>>; | ||
export declare function getAdjacencyMap<TContext = DefaultContext>(node: StateNode<TContext>, context?: TContext): AdjacencyMap; | ||
export declare function adjacencyMap<TContext = DefaultContext>(node: StateNode<TContext>, context?: TContext): AdjacencyMap; | ||
export declare function deserializeStateString(valueContextString: string): { | ||
@@ -35,5 +35,4 @@ value: StateValue; | ||
export declare function getValueAdjacencyMap<TContext = DefaultContext, TEvent extends EventObject = EventObject>(node: StateNode<TContext, any, TEvent>, options?: Partial<ValueAdjMapOptions<TContext, TEvent>>): ValueAdjacencyMap<TContext, TEvent>; | ||
export declare function getShortestValuePaths<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateNode<TContext, any, TEvent>, options?: ValueAdjMapOptions<TContext, TEvent>): PathMap<TContext, TEvent>; | ||
export declare function getShortestPathsAsArray<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateNode<TContext, any, TEvent>): Array<PathItem<TContext, TEvent>>; | ||
export declare function getShortestPaths<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateNode<TContext, any, TEvent>, options?: Partial<ValueAdjMapOptions<TContext, TEvent>>): PathMap<TContext, TEvent>; | ||
export declare function getSimplePaths<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateNode<TContext>, options?: Partial<ValueAdjMapOptions<TContext, TEvent>>): PathsMap<TContext, TEvent>; | ||
export declare function getSimplePathsAsArray<TContext = DefaultContext, TEvent extends EventObject = EventObject>(machine: StateNode<TContext>, options?: ValueAdjMapOptions<TContext, TEvent>): Array<PathsItem<TContext, TEvent>>; |
309
lib/graph.js
@@ -98,3 +98,3 @@ "use strict"; | ||
// tslint:disable-next-line:no-console | ||
console.warn("Target '" + target + "' not found on '" + node.id + "'"); | ||
utils_1.warn(e, "Target '" + target + "' not found on '" + node.id + "'"); | ||
return undefined; | ||
@@ -108,25 +108,56 @@ } | ||
function getEdges(node, options) { | ||
var _a = (options || {}).depth, depth = _a === void 0 ? null : _a; | ||
var e_1, _a, e_2, _b, e_3, _c; | ||
var _d = (options || {}).depth, depth = _d === void 0 ? null : _d; | ||
var edges = []; | ||
if (node.states && depth === null) { | ||
utils_1.keys(node.states).forEach(function (stateKey) { | ||
edges.push.apply(edges, __spread(getEdges(node.states[stateKey]))); | ||
}); | ||
try { | ||
for (var _e = __values(utils_1.keys(node.states)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
var stateKey = _f.value; | ||
edges.push.apply(edges, __spread(getEdges(node.states[stateKey]))); | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (_f && !_f.done && (_a = _e.return)) _a.call(_e); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
} | ||
else if (depth && depth > 0) { | ||
utils_1.keys(node.states).forEach(function (stateKey) { | ||
edges.push.apply(edges, __spread(getEdges(node.states[stateKey], { depth: depth - 1 }))); | ||
}); | ||
try { | ||
for (var _g = __values(utils_1.keys(node.states)), _h = _g.next(); !_h.done; _h = _g.next()) { | ||
var stateKey = _h.value; | ||
edges.push.apply(edges, __spread(getEdges(node.states[stateKey], { depth: depth - 1 }))); | ||
} | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
if (_h && !_h.done && (_b = _g.return)) _b.call(_g); | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
} | ||
utils_1.keys(node.on).forEach(function (event) { | ||
edges.push.apply(edges, __spread(getEventEdges(node, event))); | ||
}); | ||
try { | ||
for (var _j = __values(utils_1.keys(node.on)), _k = _j.next(); !_k.done; _k = _j.next()) { | ||
var event_1 = _k.value; | ||
edges.push.apply(edges, __spread(getEventEdges(node, event_1))); | ||
} | ||
} | ||
catch (e_3_1) { e_3 = { error: e_3_1 }; } | ||
finally { | ||
try { | ||
if (_k && !_k.done && (_c = _j.return)) _c.call(_j); | ||
} | ||
finally { if (e_3) throw e_3.error; } | ||
} | ||
return edges; | ||
} | ||
exports.getEdges = getEdges; | ||
function getAdjacencyMap(node, context) { | ||
function adjacencyMap(node, context) { | ||
var adjacency = {}; | ||
var events = node.events; | ||
function findAdjacencies(stateValue) { | ||
var e_1, _a; | ||
var e_4, _a; | ||
var stateKey = JSON.stringify(stateValue); | ||
@@ -139,9 +170,9 @@ if (adjacency[stateKey]) { | ||
for (var events_1 = __values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) { | ||
var event_1 = events_1_1.value; | ||
var nextState = node.transition(stateValue, event_1, context); | ||
adjacency[stateKey][event_1] = { state: nextState.value }; | ||
var event_2 = events_1_1.value; | ||
var nextState = node.transition(stateValue, event_2, context); | ||
adjacency[stateKey][event_2] = { state: nextState.value }; | ||
findAdjacencies(nextState.value); | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
@@ -151,3 +182,3 @@ try { | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
@@ -158,3 +189,3 @@ } | ||
} | ||
exports.getAdjacencyMap = getAdjacencyMap; | ||
exports.adjacencyMap = adjacencyMap; | ||
function deserializeStateString(valueContextString) { | ||
@@ -204,12 +235,23 @@ var _a = __read(valueContextString.split(' | '), 2), valueString = _a[0], contextString = _a[1]; | ||
function getValueAdjacencyMap(node, options) { | ||
var optionsWithDefaults = __assign({ events: {}, stateSerializer: serializeState, eventSerializer: serializeEvent }, options); | ||
var e_5, _a; | ||
var optionsWithDefaults = __assign({}, defaultValueAdjMapOptions, options); | ||
var filter = optionsWithDefaults.filter, stateSerializer = optionsWithDefaults.stateSerializer, eventSerializer = optionsWithDefaults.eventSerializer; | ||
var events = {}; | ||
node.events.forEach(function (event) { | ||
events[event] = [event]; | ||
}); | ||
try { | ||
for (var _b = __values(node.events), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var event_3 = _c.value; | ||
events[event_3] = [event_3]; | ||
} | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
} | ||
Object.assign(events, optionsWithDefaults.events); | ||
var adjacency = {}; | ||
function findAdjacencies(state) { | ||
var e_2, _a; | ||
var e_6, _a; | ||
var nextEvents = state.nextEvents; | ||
@@ -224,7 +266,13 @@ var stateHash = stateSerializer(state); | ||
for (var potentialEvents_1 = __values(potentialEvents), potentialEvents_1_1 = potentialEvents_1.next(); !potentialEvents_1_1.done; potentialEvents_1_1 = potentialEvents_1.next()) { | ||
var event_2 = potentialEvents_1_1.value; | ||
var nextState = node.transition(state, event_2); | ||
var event_4 = potentialEvents_1_1.value; | ||
var nextState = void 0; | ||
try { | ||
nextState = node.transition(state, event_4); | ||
} | ||
catch (e) { | ||
throw new Error("Unable to transition from state " + stateSerializer(state) + " on event " + eventSerializer(event_4) + ": " + e.message); | ||
} | ||
if ((!filter || filter(nextState)) && | ||
stateHash !== stateSerializer(nextState)) { | ||
adjacency[stateHash][eventSerializer(event_2)] = nextState; | ||
adjacency[stateHash][eventSerializer(event_4)] = nextState; | ||
findAdjacencies(nextState); | ||
@@ -234,3 +282,3 @@ } | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
finally { | ||
@@ -240,3 +288,3 @@ try { | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
finally { if (e_6) throw e_6.error; } | ||
} | ||
@@ -248,79 +296,142 @@ } | ||
exports.getValueAdjacencyMap = getValueAdjacencyMap; | ||
function getShortestValuePaths(machine, options) { | ||
if (options === void 0) { options = defaultValueAdjMapOptions; } | ||
function getShortestPaths(machine, options) { | ||
var e_7, _a, e_8, _b; | ||
if (!machine.states) { | ||
// return EMPTY_MAP; | ||
return EMPTY_MAP; | ||
} | ||
var adjacency = getValueAdjacencyMap(machine, options); | ||
var pathMap = {}; | ||
var optionsWithDefaults = __assign({ events: {}, stateSerializer: serializeState, eventSerializer: serializeEvent }, options); | ||
var adjacency = getValueAdjacencyMap(machine, optionsWithDefaults); | ||
// weight, state, event | ||
var weightMap = new Map(); | ||
var initialVertex = optionsWithDefaults.stateSerializer(machine.initialState); | ||
weightMap.set(initialVertex, [0, undefined, undefined]); | ||
var unvisited = new Set(); | ||
var visited = new Set(); | ||
function util(state) { | ||
var e_3, _a, e_4, _b; | ||
var stateKey = serializeState(state); | ||
visited.add(stateKey); | ||
var eventMap = adjacency[stateKey]; | ||
unvisited.add(initialVertex); | ||
while (unvisited.size > 0) { | ||
try { | ||
for (var _c = __values(utils_1.keys(eventMap)), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var eventType = _d.value; | ||
var _e = eventMap[eventType], value = _e.value, context_1 = _e.context; | ||
if (!value) { | ||
continue; | ||
} | ||
var nextState = xstate_1.State.from(value, context_1); | ||
var nextStateId = serializeState(nextState); | ||
if (!pathMap[nextStateId] || | ||
pathMap[nextStateId].length > pathMap[stateKey].length + 1) { | ||
pathMap[nextStateId] = __spread((pathMap[stateKey] || []), [ | ||
{ | ||
state: { value: value, context: state.context }, | ||
event: deserializeEventString(eventType) | ||
for (var unvisited_1 = __values(unvisited), unvisited_1_1 = unvisited_1.next(); !unvisited_1_1.done; unvisited_1_1 = unvisited_1.next()) { | ||
var vertex = unvisited_1_1.value; | ||
var _c = __read(weightMap.get(vertex), 1), weight = _c[0]; | ||
try { | ||
for (var _d = __values(utils_1.keys(adjacency[vertex])), _e = _d.next(); !_e.done; _e = _d.next()) { | ||
var event_5 = _e.value; | ||
var nextVertex = optionsWithDefaults.stateSerializer(adjacency[vertex][event_5]); | ||
if (!weightMap.has(nextVertex)) { | ||
weightMap.set(nextVertex, [weight + 1, vertex, event_5]); | ||
} | ||
]); | ||
else { | ||
var _f = __read(weightMap.get(nextVertex), 1), nextWeight = _f[0]; | ||
if (nextWeight > weight + 1) { | ||
weightMap.set(nextVertex, [weight + 1, vertex, event_5]); | ||
} | ||
} | ||
if (!visited.has(nextVertex)) { | ||
unvisited.add(nextVertex); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
catch (e_3_1) { e_3 = { error: e_3_1 }; } | ||
finally { | ||
try { | ||
if (_d && !_d.done && (_a = _c.return)) _a.call(_c); | ||
} | ||
finally { if (e_3) throw e_3.error; } | ||
} | ||
try { | ||
for (var _f = __values(utils_1.keys(eventMap)), _g = _f.next(); !_g.done; _g = _f.next()) { | ||
var event_3 = _g.value; | ||
var _h = eventMap[event_3], value = _h.value, context_2 = _h.context; | ||
if (!value) { | ||
continue; | ||
catch (e_8_1) { e_8 = { error: e_8_1 }; } | ||
finally { | ||
try { | ||
if (_e && !_e.done && (_b = _d.return)) _b.call(_d); | ||
} | ||
finally { if (e_8) throw e_8.error; } | ||
} | ||
var nextState = xstate_1.State.from(value, context_2); | ||
var nextStateId = serializeState(xstate_1.State.from(value, context_2)); | ||
if (visited.has(nextStateId)) { | ||
continue; | ||
} | ||
util(nextState); | ||
visited.add(vertex); | ||
unvisited.delete(vertex); | ||
} | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
catch (e_7_1) { e_7 = { error: e_7_1 }; } | ||
finally { | ||
try { | ||
if (_g && !_g.done && (_b = _f.return)) _b.call(_f); | ||
if (unvisited_1_1 && !unvisited_1_1.done && (_a = unvisited_1.return)) _a.call(unvisited_1); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
finally { if (e_7) throw e_7.error; } | ||
} | ||
return pathMap; | ||
} | ||
util(machine.initialState); | ||
var pathMap = {}; | ||
weightMap.forEach(function (_a, stateSerial) { | ||
var _b = __read(_a, 3), weight = _b[0], fromState = _b[1], fromEvent = _b[2]; | ||
pathMap[stateSerial] = { | ||
state: deserializeStateString(stateSerial), | ||
weight: weight, | ||
path: (function () { | ||
if (!fromState) { | ||
return []; | ||
} | ||
return pathMap[fromState].path.concat({ | ||
state: deserializeStateString(fromState), | ||
event: deserializeEventString(fromEvent) | ||
}); | ||
})() | ||
}; | ||
}); | ||
return pathMap; | ||
} | ||
exports.getShortestValuePaths = getShortestValuePaths; | ||
function getShortestPathsAsArray(machine) { | ||
var result = getShortestValuePaths(machine, defaultValueAdjMapOptions); | ||
return utils_1.keys(result).map(function (key) { return ({ | ||
state: JSON.parse(key), | ||
path: result[key] | ||
}); }); | ||
} | ||
exports.getShortestPathsAsArray = getShortestPathsAsArray; | ||
exports.getShortestPaths = getShortestPaths; | ||
// export function getShortestValuePaths< | ||
// TContext = DefaultContext, | ||
// TEvent extends EventObject = EventObject | ||
// >( | ||
// machine: StateNode<TContext, any, TEvent>, | ||
// options: ValueAdjMapOptions<TContext, TEvent> = defaultValueAdjMapOptions | ||
// ): PathMap<TContext, TEvent> { | ||
// if (!machine.states) { | ||
// return EMPTY_MAP; | ||
// } | ||
// const adjacency = getValueAdjacencyMap<TContext, TEvent>(machine, options); | ||
// const pathMap: PathMap<TContext, TEvent> = {}; | ||
// const visited: Set<string> = new Set(); | ||
// function util(state: State<TContext>): PathMap<TContext, TEvent> { | ||
// const stateKey = serializeState(state); | ||
// visited.add(stateKey); | ||
// const eventMap = adjacency[stateKey]; | ||
// for (const eventType of keys(eventMap)) { | ||
// const { value, context } = eventMap[eventType]; | ||
// if (!value) { | ||
// continue; | ||
// } | ||
// const nextState = State.from(value, context); | ||
// const nextStateId = serializeState(nextState); | ||
// if ( | ||
// !pathMap[nextStateId] || | ||
// pathMap[nextStateId].length > pathMap[stateKey].length + 1 | ||
// ) { | ||
// pathMap[nextStateId] = [ | ||
// ...(pathMap[stateKey] || []), | ||
// { | ||
// state: { value, context: state.context }, | ||
// event: deserializeEventString(eventType) as TEvent | ||
// } | ||
// ]; | ||
// } | ||
// if (visited.has(nextStateId)) { | ||
// continue; | ||
// } | ||
// util(nextState); | ||
// } | ||
// return pathMap; | ||
// } | ||
// util(machine.initialState); | ||
// return pathMap; | ||
// } | ||
// export function getShortestPathsAsArray< | ||
// TContext = DefaultContext, | ||
// TEvent extends EventObject = EventObject | ||
// >( | ||
// machine: StateNode<TContext, any, TEvent> | ||
// ): Array<PathItem<TContext, TEvent>> { | ||
// const result = getShortestValuePaths<TContext, TEvent>( | ||
// machine, | ||
// defaultValueAdjMapOptions | ||
// ); | ||
// return keys(result).map(key => ({ | ||
// state: JSON.parse(key), | ||
// path: result[key] | ||
// })); | ||
// } | ||
function getSimplePaths(machine, options) { | ||
var e_9, _a; | ||
if (!machine.states) { | ||
@@ -334,3 +445,3 @@ return EMPTY_MAP; | ||
function util(fromStateSerial, toStateSerial) { | ||
var e_5, _a; | ||
var e_10, _a; | ||
visited.add(fromStateSerial); | ||
@@ -364,3 +475,3 @@ if (fromStateSerial === toStateSerial) { | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
catch (e_10_1) { e_10 = { error: e_10_1 }; } | ||
finally { | ||
@@ -370,3 +481,3 @@ try { | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
finally { if (e_10) throw e_10.error; } | ||
} | ||
@@ -378,5 +489,15 @@ } | ||
var initialStateSerial = serializeState(machine.initialState); | ||
utils_1.keys(adjacency).forEach(function (nextStateSerial) { | ||
util(initialStateSerial, nextStateSerial); | ||
}); | ||
try { | ||
for (var _b = __values(utils_1.keys(adjacency)), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var nextStateSerial = _c.value; | ||
util(initialStateSerial, nextStateSerial); | ||
} | ||
} | ||
catch (e_9_1) { e_9 = { error: e_9_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b.return)) _a.call(_b); | ||
} | ||
finally { if (e_9) throw e_9.error; } | ||
} | ||
return paths; | ||
@@ -383,0 +504,0 @@ } |
@@ -1,1 +0,2 @@ | ||
export { getNodes, getAdjacencyMap, getSimplePaths, serializeEvent, serializeState, deserializeEventString, deserializeStateString } from './graph'; | ||
import { getNodes, getEdges, getSimplePaths, getShortestPaths, serializeEvent, serializeState, deserializeEventString, deserializeStateString, adjacencyMap } from './graph'; | ||
export { adjacencyMap, getNodes, getEdges, getSimplePaths, getShortestPaths, serializeEvent, serializeState, deserializeEventString, deserializeStateString }; |
@@ -5,4 +5,5 @@ "use strict"; | ||
exports.getNodes = graph_1.getNodes; | ||
exports.getAdjacencyMap = graph_1.getAdjacencyMap; | ||
exports.getEdges = graph_1.getEdges; | ||
exports.getSimplePaths = graph_1.getSimplePaths; | ||
exports.getShortestPaths = graph_1.getShortestPaths; | ||
exports.serializeEvent = graph_1.serializeEvent; | ||
@@ -12,1 +13,2 @@ exports.serializeState = graph_1.serializeState; | ||
exports.deserializeStateString = graph_1.deserializeStateString; | ||
exports.adjacencyMap = graph_1.adjacencyMap; |
{ | ||
"name": "@xstate/graph", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "XState graph utilities", | ||
@@ -25,3 +25,4 @@ "keywords": [ | ||
"scripts": { | ||
"test": "tsc && mocha --require ts-node/register test/**.ts test/**/*.test.ts" | ||
"test": "tsc && mocha --require ts-node/register test/**.ts test/**/*.test.ts", | ||
"prepublish": "tsc && npm run test" | ||
}, | ||
@@ -32,3 +33,4 @@ "bugs": { | ||
"dependencies": { | ||
"xstate": "4.3.3" | ||
"immer": "^2.1.5", | ||
"xstate": "^4.5.0" | ||
}, | ||
@@ -35,0 +37,0 @@ "devDependencies": { |
381
README.md
@@ -1,1 +0,380 @@ | ||
# XState Graph | ||
# @xstate/graph | ||
This package contains graph algorithms and utilities for XState machines. | ||
## Quick Start | ||
1. Install `xstate` and `@xstate/graph`: | ||
```bash | ||
npm install xstate @xstate/graph | ||
``` | ||
2. Import the graph utilities. Example: | ||
```js | ||
import { Machine } from 'xstate'; | ||
import { simplePaths } from '@xstate/graph'; | ||
const machine = Machine(/* ... */); | ||
const paths = simplePaths(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 | ||
- `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 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: | ||
```json | ||
{ | ||
"<SERIALIZED STATE>": { | ||
"state": { "value": "<state.value>", "context": "<state.context>" }, | ||
"path": [ | ||
{ | ||
"state": { | ||
"value": "<initialState.value>", | ||
"context": "<initialState.context>" | ||
}, | ||
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" } | ||
}, | ||
{ | ||
"state": { | ||
"value": "<state.value>", | ||
"context": "<state.context>" | ||
}, | ||
"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: { value: 'question', context: undefined }, | ||
// weight: 0, | ||
// path: [] | ||
// }, | ||
// '"thanks"': { | ||
// state: { value: 'thanks', context: undefined }, | ||
// weight: 1, | ||
// path: [ | ||
// { | ||
// state: { value: 'question', context: undefined }, | ||
// event: { type: 'CLICK_GOOD' } | ||
// } | ||
// ] | ||
// }, | ||
// '"form"': { | ||
// state: { value: 'form', context: undefined }, | ||
// weight: 1, | ||
// path: [ | ||
// { | ||
// state: { value: 'question', context: undefined }, | ||
// event: { type: 'CLICK_BAD' } | ||
// } | ||
// ] | ||
// }, | ||
// '"closed"': { | ||
// state: { value: 'closed', context: undefined }, | ||
// weight: 1, | ||
// path: [ | ||
// { | ||
// 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 | ||
- `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 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: | ||
```json | ||
{ | ||
"<SERIALIZED STATE>": { | ||
"state": { "value": "<state.value>", "context": "<state.context>" }, | ||
"paths": [ | ||
[ | ||
{ | ||
"state": { | ||
"value": "<initialState.value>", | ||
"context": "<initialState.context>" | ||
}, | ||
"event": { "type": "<event.type>", "<PROP>": "<event.PROP>" } | ||
}, | ||
{ | ||
"state": { | ||
"value": "<state.value>", | ||
"context": "<state.context>" | ||
}, | ||
"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' } | ||
// } | ||
// ], | ||
// ... | ||
// ] | ||
// }, | ||
// ... | ||
// }; | ||
``` | ||
## 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 } | ||
// } | ||
// ] | ||
// }, | ||
// ... | ||
// }; | ||
``` |
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
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
33399
546
381
2
+ Addedimmer@^2.1.5
+ Addedimmer@2.1.5(transitive)
+ Addedxstate@4.38.3(transitive)
- Removedxstate@4.3.3(transitive)
Updatedxstate@^4.5.0