piemachine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -0
- package/dist/error.d.ts +4 -0
- package/dist/error.js +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/pieMachine.d.ts +24 -0
- package/dist/pieMachine.js +250 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +23 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +12 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# pie machine
|
|
2
|
+
|
|
3
|
+
A general-purpose library for defining and running state machines. Easy as pie.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
|
|
9
|
+
import { Graph } from "./lib/graph.js";
|
|
10
|
+
|
|
11
|
+
// The state type for our graph
|
|
12
|
+
type State = {
|
|
13
|
+
count: number;
|
|
14
|
+
log: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// enable debug logging
|
|
18
|
+
const graphConfig = {
|
|
19
|
+
debug: {
|
|
20
|
+
log: true,
|
|
21
|
+
logData: true,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Define the names of the nodes in the graph
|
|
26
|
+
// Useful for type safety
|
|
27
|
+
const nodes = ["start", "increment", "finish"] as const;
|
|
28
|
+
type Node = (typeof nodes)[number];
|
|
29
|
+
|
|
30
|
+
// Create a new graph
|
|
31
|
+
const graph = new Graph<State, Node>(nodes, graphConfig);
|
|
32
|
+
|
|
33
|
+
// Add some nodes! Each node is an async function that takes the current state and returns a new state.
|
|
34
|
+
graph.node("start", async (data) => {
|
|
35
|
+
return {
|
|
36
|
+
...data,
|
|
37
|
+
log: [...data.log, "Starting computation"],
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
graph.node("increment", async (data) => {
|
|
42
|
+
return {
|
|
43
|
+
...data,
|
|
44
|
+
count: data.count + 1,
|
|
45
|
+
log: [...data.log, `Incremented count to ${data.count + 1}`],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
graph.node("finish", async (data) => data);
|
|
50
|
+
|
|
51
|
+
// Define the edges between the nodes
|
|
52
|
+
graph.edge("start", "increment");
|
|
53
|
+
graph.conditionalEdge("increment", ["finish", "increment"], async (data) => {
|
|
54
|
+
if (data.count < 2) {
|
|
55
|
+
return "increment";
|
|
56
|
+
} else {
|
|
57
|
+
return "finish";
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
// Run the graph starting from the "start" node with an initial state
|
|
63
|
+
const initialState: State = { count: 0, log: [] };
|
|
64
|
+
const finalState = await graph.run("start", initialState);
|
|
65
|
+
console.log(finalState);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main();
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Output:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
[DEBUG]: Executing node: start | Data: {"count":0,"log":[]}
|
|
77
|
+
[DEBUG]: Completed node: start | Data: {"count":0,"log":["Starting computation"]}
|
|
78
|
+
[DEBUG]: Following regular edge to: increment
|
|
79
|
+
[DEBUG]: Executing node: increment | Data: {"count":0,"log":["Starting computation"]}
|
|
80
|
+
[DEBUG]: Completed node: increment | Data: {"count":1,"log":["Starting computation","Incremented count to 1"]}
|
|
81
|
+
[DEBUG]: Following conditional edge to: increment | Data: {"count":1,"log":["Starting computation","Incremented count to 1"]}
|
|
82
|
+
[DEBUG]: Executing node: increment | Data: {"count":1,"log":["Starting computation","Incremented count to 1"]}
|
|
83
|
+
[DEBUG]: Completed node: increment | Data: {"count":2,"log":["Starting computation","Incremented count to 1","Incremented count to 2"]}
|
|
84
|
+
[DEBUG]: Following conditional edge to: finish | Data: {"count":2,"log":["Starting computation","Incremented count to 1","Incremented count to 2"]}
|
|
85
|
+
[DEBUG]: Executing node: finish | Data: {"count":2,"log":["Starting computation","Incremented count to 1","Incremented count to 2"]}
|
|
86
|
+
[DEBUG]: Completed node: finish | Data: {"count":2,"log":["Starting computation","Incremented count to 1","Incremented count to 2"]}
|
|
87
|
+
{
|
|
88
|
+
count: 2,
|
|
89
|
+
log: [
|
|
90
|
+
'Starting computation',
|
|
91
|
+
'Incremented count to 1',
|
|
92
|
+
'Incremented count to 2'
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
Some options to visualize the graph:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
graph.prettyPrint();
|
|
103
|
+
/* Prints:
|
|
104
|
+
start -> increment
|
|
105
|
+
increment -> finish | increment
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
console.log(graph.toMermaid());
|
|
109
|
+
/* Prints:
|
|
110
|
+
graph TD
|
|
111
|
+
start --> increment
|
|
112
|
+
increment --> finish
|
|
113
|
+
increment --> increment
|
|
114
|
+
*/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## More about routing
|
|
118
|
+
|
|
119
|
+
There are three ways a node can route to another node. One is to specify a single edge to the next node:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
graph.edge("start", "increment");
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Alternatively, a node can conditionally route to multiple other nodes. It can do so by returning an instance of `GoToNode`, where the first parameter is the name of the node it wants to route to, and the second parameter is the data it is sending to that node.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
graph.node("increment", async (data) => {
|
|
129
|
+
const newCount = data.count + 1;
|
|
130
|
+
const newData = {
|
|
131
|
+
...data,
|
|
132
|
+
count: newCount,
|
|
133
|
+
log: [...data.log, `Incremented count to ${newCount}`],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Nodes can return GoToNode to jump to a specific node next
|
|
137
|
+
if (newCount < 2) {
|
|
138
|
+
// or new GoToNode("increment", newData);
|
|
139
|
+
return goToNode("increment", newData);
|
|
140
|
+
}
|
|
141
|
+
return goToNode("finish", newData);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
If you go with this approach, you also need to specify all the possible nodes that the node could route to.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
graph.conditionalEdge("increment", ["finish", "increment"]);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Finally, a node can conditionally route to multiple other nodes by having a routing function in the call to `conditionalEdge`.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
graph.conditionalEdge("increment", ["finish", "increment"], async (data) => {
|
|
155
|
+
if (data.count < 2) {
|
|
156
|
+
return "increment";
|
|
157
|
+
} else {
|
|
158
|
+
return "finish";
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Note that if you want a node to route to multiple other nodes, you need to do so conditionally. You can't have a node route to multiple other nodes in parallel:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// This is not allowed
|
|
167
|
+
graph.edge("start", ["increment", "finish"]);
|
|
168
|
+
|
|
169
|
+
// This is also not allowed
|
|
170
|
+
graph.edge("start", "increment");
|
|
171
|
+
graph.edge("start", "finish");
|
|
172
|
+
```
|
|
173
|
+
|
package/dist/error.d.ts
ADDED
package/dist/error.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ConditionalFunc, Edge, PieMachineConfig } from "./types.js";
|
|
2
|
+
export declare class GoToNode<T, N extends string> {
|
|
3
|
+
to: N;
|
|
4
|
+
data: T;
|
|
5
|
+
constructor(to: N, data: T);
|
|
6
|
+
}
|
|
7
|
+
export declare function goToNode<T, N extends string>(to: N, data: T): GoToNode<T, N>;
|
|
8
|
+
export declare class PieMachine<T, N extends string> {
|
|
9
|
+
private nodes;
|
|
10
|
+
private edges;
|
|
11
|
+
private config;
|
|
12
|
+
private statelogClient;
|
|
13
|
+
constructor(nodes: N[], config?: PieMachineConfig<T>);
|
|
14
|
+
node(id: N, func: (data: T) => Promise<T | GoToNode<T, N>>): void;
|
|
15
|
+
edge(from: N, to: N): void;
|
|
16
|
+
conditionalEdge<const Adjacent extends N>(from: N, adjacentNodes: readonly Adjacent[], to?: ConditionalFunc<T, Adjacent>): void;
|
|
17
|
+
debug(message: string, data?: T): void;
|
|
18
|
+
run(startId: N, input: T): Promise<T>;
|
|
19
|
+
runAndValidate(nodeFunc: (data: T) => Promise<T | GoToNode<T, N>>, currentId: N, _data: T, retries?: number): Promise<T | GoToNode<T, N>>;
|
|
20
|
+
prettyPrint(): void;
|
|
21
|
+
prettyPrintEdge(edge: Edge<T, N>): string;
|
|
22
|
+
toMermaid(): string;
|
|
23
|
+
private validateGoToNodeTarget;
|
|
24
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { color } from "termcolors";
|
|
11
|
+
import { PieMachineError } from "./error.js";
|
|
12
|
+
import { StatelogClient } from "statelog-client";
|
|
13
|
+
import { conditionalEdge, edgeToJSON, isRegularEdge, regularEdge, } from "./types.js";
|
|
14
|
+
export class GoToNode {
|
|
15
|
+
constructor(to, data) {
|
|
16
|
+
this.to = to;
|
|
17
|
+
this.data = data;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function goToNode(to, data) {
|
|
21
|
+
return new GoToNode(to, data);
|
|
22
|
+
}
|
|
23
|
+
export class PieMachine {
|
|
24
|
+
constructor(nodes, config = {}) {
|
|
25
|
+
var _a;
|
|
26
|
+
this.nodes = {};
|
|
27
|
+
this.edges = {};
|
|
28
|
+
this.statelogClient = null;
|
|
29
|
+
this.config = config;
|
|
30
|
+
if (config.statelog) {
|
|
31
|
+
this.statelogClient = new StatelogClient({
|
|
32
|
+
host: config.statelog.host,
|
|
33
|
+
apiKey: config.statelog.apiKey,
|
|
34
|
+
projectId: config.statelog.projectId,
|
|
35
|
+
traceId: config.statelog.traceId,
|
|
36
|
+
debugMode: (_a = config.statelog.debugMode) !== null && _a !== void 0 ? _a : false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
node(id, func) {
|
|
41
|
+
this.nodes[id] = func;
|
|
42
|
+
}
|
|
43
|
+
edge(from, to) {
|
|
44
|
+
if (!this.edges[from]) {
|
|
45
|
+
this.edges[from] = regularEdge(to);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new PieMachineError(` ${from} already has an edge, which leads to ${this.edges[from]}.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
conditionalEdge(from, adjacentNodes, to) {
|
|
52
|
+
if (!this.edges[from]) {
|
|
53
|
+
this.edges[from] = conditionalEdge(to, adjacentNodes);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
throw new PieMachineError(` ${from} already has an edge, which leads to ${this.edges[from]}.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
debug(message, data) {
|
|
60
|
+
var _a, _b;
|
|
61
|
+
let debugStr = `${color.magenta("[DEBUG]")}: ${message}`;
|
|
62
|
+
if (((_a = this.config.debug) === null || _a === void 0 ? void 0 : _a.logData) && data !== undefined) {
|
|
63
|
+
debugStr += ` | Data: ${color.green(JSON.stringify(data))}`;
|
|
64
|
+
}
|
|
65
|
+
if ((_b = this.config.debug) === null || _b === void 0 ? void 0 : _b.log) {
|
|
66
|
+
console.log(debugStr);
|
|
67
|
+
}
|
|
68
|
+
//this.statelogClient?.debug(message, data || {});
|
|
69
|
+
}
|
|
70
|
+
run(startId, input) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
73
|
+
const jsonEdges = {};
|
|
74
|
+
for (const from in this.edges) {
|
|
75
|
+
jsonEdges[from] = edgeToJSON(this.edges[from]);
|
|
76
|
+
}
|
|
77
|
+
(_a = this.statelogClient) === null || _a === void 0 ? void 0 : _a.graph({
|
|
78
|
+
nodes: Object.keys(this.nodes),
|
|
79
|
+
edges: jsonEdges,
|
|
80
|
+
startNode: startId,
|
|
81
|
+
});
|
|
82
|
+
let currentId = startId;
|
|
83
|
+
let data = input;
|
|
84
|
+
while (currentId) {
|
|
85
|
+
const nodeFunc = this.nodes[currentId];
|
|
86
|
+
if (!nodeFunc) {
|
|
87
|
+
throw new PieMachineError(`Node function for ${currentId} not found.`);
|
|
88
|
+
}
|
|
89
|
+
if ((_b = this.config.hooks) === null || _b === void 0 ? void 0 : _b.beforeNode) {
|
|
90
|
+
this.debug(`Before hook for node: ${color.green(currentId)}`, data);
|
|
91
|
+
const startData = data;
|
|
92
|
+
const startTime = performance.now();
|
|
93
|
+
data = yield this.config.hooks.beforeNode(currentId, data);
|
|
94
|
+
const endTime = performance.now();
|
|
95
|
+
(_c = this.statelogClient) === null || _c === void 0 ? void 0 : _c.beforeHook({
|
|
96
|
+
nodeId: currentId,
|
|
97
|
+
startData,
|
|
98
|
+
endData: data,
|
|
99
|
+
timeTaken: endTime - startTime,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
this.debug(`Executing node: ${color.green(currentId)}`, data);
|
|
103
|
+
(_d = this.statelogClient) === null || _d === void 0 ? void 0 : _d.enterNode({ nodeId: currentId, data });
|
|
104
|
+
const startTime = performance.now();
|
|
105
|
+
const result = yield this.runAndValidate(nodeFunc, currentId, data);
|
|
106
|
+
const endTime = performance.now();
|
|
107
|
+
let nextNode;
|
|
108
|
+
if (result instanceof GoToNode) {
|
|
109
|
+
nextNode = result.to;
|
|
110
|
+
data = result.data;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
data = result;
|
|
114
|
+
}
|
|
115
|
+
(_e = this.statelogClient) === null || _e === void 0 ? void 0 : _e.exitNode({
|
|
116
|
+
nodeId: currentId,
|
|
117
|
+
data,
|
|
118
|
+
timeTaken: endTime - startTime,
|
|
119
|
+
});
|
|
120
|
+
this.debug(`Completed node: ${color.green(currentId)}`, data);
|
|
121
|
+
if ((_f = this.config.hooks) === null || _f === void 0 ? void 0 : _f.afterNode) {
|
|
122
|
+
this.debug(`After hook for node: ${color.green(currentId)}`, data);
|
|
123
|
+
const startData = data;
|
|
124
|
+
const startTime = performance.now();
|
|
125
|
+
data = yield this.config.hooks.afterNode(currentId, data);
|
|
126
|
+
const endTime = performance.now();
|
|
127
|
+
(_g = this.statelogClient) === null || _g === void 0 ? void 0 : _g.afterHook({
|
|
128
|
+
nodeId: currentId,
|
|
129
|
+
startData,
|
|
130
|
+
endData: data,
|
|
131
|
+
timeTaken: endTime - startTime,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const edge = this.edges[currentId];
|
|
135
|
+
if (edge === undefined) {
|
|
136
|
+
currentId = null;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (nextNode && edge) {
|
|
140
|
+
const isValidTarget = this.validateGoToNodeTarget(nextNode, edge);
|
|
141
|
+
if (!isValidTarget) {
|
|
142
|
+
throw new PieMachineError(`${currentId} tried to go to ${nextNode}, but did not specify a conditional edge to it. Use graph.conditionalEdge("${currentId}", ["${nextNode}"]) to define the edge.`);
|
|
143
|
+
}
|
|
144
|
+
(_h = this.statelogClient) === null || _h === void 0 ? void 0 : _h.followEdge({
|
|
145
|
+
fromNodeId: currentId,
|
|
146
|
+
toNodeId: nextNode,
|
|
147
|
+
isConditionalEdge: false,
|
|
148
|
+
data,
|
|
149
|
+
});
|
|
150
|
+
this.debug(`Following goto edge to: ${color.green(nextNode)}`, data);
|
|
151
|
+
currentId = nextNode;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (isRegularEdge(edge)) {
|
|
155
|
+
(_j = this.statelogClient) === null || _j === void 0 ? void 0 : _j.followEdge({
|
|
156
|
+
fromNodeId: currentId,
|
|
157
|
+
toNodeId: edge.to,
|
|
158
|
+
isConditionalEdge: false,
|
|
159
|
+
data,
|
|
160
|
+
});
|
|
161
|
+
this.debug(`Following regular edge to: ${color.green(edge.to)}`);
|
|
162
|
+
currentId = edge.to;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (edge.condition) {
|
|
166
|
+
const nextId = yield edge.condition(data);
|
|
167
|
+
(_k = this.statelogClient) === null || _k === void 0 ? void 0 : _k.followEdge({
|
|
168
|
+
fromNodeId: currentId,
|
|
169
|
+
toNodeId: nextId,
|
|
170
|
+
isConditionalEdge: true,
|
|
171
|
+
data,
|
|
172
|
+
});
|
|
173
|
+
this.debug(`Following conditional edge to: ${color.green(nextId)}`, data);
|
|
174
|
+
currentId = nextId;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw new PieMachineError(`Expected ${currentId} to return a GoToNode, as no function was specified for the conditional edges to ${edge.adjacentNodes.join(", ")}.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return data;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
runAndValidate(nodeFunc_1, currentId_1, _data_1) {
|
|
185
|
+
return __awaiter(this, arguments, void 0, function* (nodeFunc, currentId, _data, retries = 0) {
|
|
186
|
+
var _a, _b;
|
|
187
|
+
const result = yield nodeFunc(_data);
|
|
188
|
+
let data;
|
|
189
|
+
if (result instanceof GoToNode) {
|
|
190
|
+
data = result.data;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
data = result;
|
|
194
|
+
}
|
|
195
|
+
if ((_a = this.config.validation) === null || _a === void 0 ? void 0 : _a.func) {
|
|
196
|
+
const maxRetries = (_b = this.config.validation.maxRetries) !== null && _b !== void 0 ? _b : 0;
|
|
197
|
+
let isValid = yield this.config.validation.func(data);
|
|
198
|
+
while (!isValid) {
|
|
199
|
+
if (retries >= maxRetries) {
|
|
200
|
+
throw new PieMachineError(`Validation failed for node ${currentId} after ${maxRetries} retries.`);
|
|
201
|
+
}
|
|
202
|
+
this.debug(`Validation failed for node ${color.green(currentId)}, retrying... (${retries + 1}/${maxRetries})`, data);
|
|
203
|
+
return this.runAndValidate(nodeFunc, currentId, _data, retries + 1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
prettyPrint() {
|
|
210
|
+
for (const from in this.edges) {
|
|
211
|
+
const to = this.edges[from];
|
|
212
|
+
if (!to)
|
|
213
|
+
continue;
|
|
214
|
+
console.log(`${from} -> ${this.prettyPrintEdge(to)}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
prettyPrintEdge(edge) {
|
|
218
|
+
if (isRegularEdge(edge)) {
|
|
219
|
+
return edge.to;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
return edge.adjacentNodes.join(" | ");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
toMermaid() {
|
|
226
|
+
let mermaid = "graph TD\n";
|
|
227
|
+
for (const from in this.edges) {
|
|
228
|
+
const to = this.edges[from];
|
|
229
|
+
if (!to)
|
|
230
|
+
continue;
|
|
231
|
+
if (isRegularEdge(to)) {
|
|
232
|
+
mermaid += ` ${from} --> ${to.to}\n`;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
to.adjacentNodes.forEach((adjNode) => {
|
|
236
|
+
mermaid += ` ${from} --> ${adjNode}\n`;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return mermaid;
|
|
241
|
+
}
|
|
242
|
+
validateGoToNodeTarget(to, edge) {
|
|
243
|
+
if (!isRegularEdge(edge)) {
|
|
244
|
+
if (edge.adjacentNodes.includes(to)) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type PieMachineConfig<T> = {
|
|
2
|
+
debug?: {
|
|
3
|
+
log?: boolean;
|
|
4
|
+
logData?: boolean;
|
|
5
|
+
};
|
|
6
|
+
validation?: {
|
|
7
|
+
func?: (data: T) => Promise<boolean>;
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
};
|
|
10
|
+
hooks?: {
|
|
11
|
+
beforeNode?: (nodeId: string, data: T) => Promise<T>;
|
|
12
|
+
afterNode?: (nodeId: string, data: T) => Promise<T>;
|
|
13
|
+
};
|
|
14
|
+
statelog?: {
|
|
15
|
+
host: string;
|
|
16
|
+
apiKey: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
traceId?: string;
|
|
19
|
+
debugMode?: boolean;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type NodeId = string;
|
|
23
|
+
export type Edge<T, N extends string> = RegularEdge<N> | ConditionalEdge<T, N>;
|
|
24
|
+
export type RegularEdge<N extends string> = {
|
|
25
|
+
type: "regular";
|
|
26
|
+
to: N;
|
|
27
|
+
};
|
|
28
|
+
export type ConditionalFunc<T, N extends string> = (data: T) => Promise<NoInfer<N>>;
|
|
29
|
+
export type ConditionalEdge<T, N extends string> = {
|
|
30
|
+
type: "conditional";
|
|
31
|
+
condition?: ConditionalFunc<T, N>;
|
|
32
|
+
adjacentNodes: readonly N[];
|
|
33
|
+
};
|
|
34
|
+
export declare function regularEdge<N extends string>(to: N): RegularEdge<N>;
|
|
35
|
+
export declare function conditionalEdge<T, N extends string>(condition: ConditionalFunc<T, N> | undefined, adjacentNodes: readonly N[]): ConditionalEdge<T, N>;
|
|
36
|
+
export declare function isRegularEdge<T, N extends string>(edge: Edge<T, N>): edge is RegularEdge<N>;
|
|
37
|
+
export declare function isConditionalEdge<T, N extends string>(edge: Edge<T, N>): edge is ConditionalEdge<T, N>;
|
|
38
|
+
export type JSONEdge = {
|
|
39
|
+
type: "regular";
|
|
40
|
+
to: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "conditional";
|
|
43
|
+
adjacentNodes: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
export declare function edgeToJSON<T, N extends string>(edge: Edge<T, N>): JSONEdge;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function regularEdge(to) {
|
|
2
|
+
return { type: "regular", to };
|
|
3
|
+
}
|
|
4
|
+
export function conditionalEdge(condition, adjacentNodes) {
|
|
5
|
+
return { type: "conditional", condition, adjacentNodes };
|
|
6
|
+
}
|
|
7
|
+
export function isRegularEdge(edge) {
|
|
8
|
+
return edge.type === "regular";
|
|
9
|
+
}
|
|
10
|
+
export function isConditionalEdge(edge) {
|
|
11
|
+
return edge.type === "conditional";
|
|
12
|
+
}
|
|
13
|
+
export function edgeToJSON(edge) {
|
|
14
|
+
if (isRegularEdge(edge)) {
|
|
15
|
+
return { type: "regular", to: edge.to };
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return {
|
|
19
|
+
type: "conditional",
|
|
20
|
+
adjacentNodes: edge.adjacentNodes,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runtime(callback: Function): number | null;
|
package/dist/utils.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "piemachine",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A general-purpose library for defining and running state machines. Easy as pie.",
|
|
5
|
+
"homepage": "https://github.com/egonSchiele/piemachine",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest",
|
|
8
|
+
"test:tsc": "tsc -p tests/tsconfig.json",
|
|
9
|
+
"coverage": "vitest --coverage",
|
|
10
|
+
"build": "rm -rf dist && tsc",
|
|
11
|
+
"start": "cd dist && node index.js",
|
|
12
|
+
"doc": "typedoc --disableSources --out docs lib && prettier docs/ --write",
|
|
13
|
+
"doc:prettier": "prettier docs/ --write"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"./dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"graph",
|
|
29
|
+
"dataflow",
|
|
30
|
+
"react"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.19.27",
|
|
36
|
+
"prettier": "3.2.5",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vitest": "^1.6.1"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"nanoid": "^5.1.6",
|
|
42
|
+
"statelog-client": "^0.0.31",
|
|
43
|
+
"termcolors": "github:egonSchiele/termcolors"
|
|
44
|
+
}
|
|
45
|
+
}
|