@monstermann/graph 0.0.0 → 0.2.0
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 +1383 -0
- package/dist/Graph/batch.d.mts +47 -0
- package/dist/Graph/batch.mjs +57 -0
- package/dist/Graph/create.d.mts +36 -0
- package/dist/Graph/create.mjs +37 -0
- package/dist/Graph/findEdge.d.mts +60 -0
- package/dist/Graph/findEdge.mjs +10 -0
- package/dist/Graph/findEdges.d.mts +65 -0
- package/dist/Graph/findEdges.mjs +12 -0
- package/dist/Graph/findNeighbor.d.mts +54 -0
- package/dist/Graph/findNeighbor.mjs +16 -0
- package/dist/Graph/findNeighbors.d.mts +59 -0
- package/dist/Graph/findNeighbors.mjs +18 -0
- package/dist/Graph/findNode.d.mts +67 -0
- package/dist/Graph/findNode.mjs +7 -0
- package/dist/Graph/findNodes.d.mts +63 -0
- package/dist/Graph/findNodes.mjs +9 -0
- package/dist/Graph/forEachEdge.d.mts +52 -0
- package/dist/Graph/forEachEdge.mjs +56 -0
- package/dist/Graph/forEachNeighbor.d.mts +52 -0
- package/dist/Graph/forEachNeighbor.mjs +59 -0
- package/dist/Graph/forEachNode.d.mts +49 -0
- package/dist/Graph/forEachNode.mjs +51 -0
- package/dist/Graph/fromJS.d.mts +55 -0
- package/dist/Graph/fromJS.mjs +62 -0
- package/dist/Graph/getEdge.d.mts +50 -0
- package/dist/Graph/getEdge.mjs +54 -0
- package/dist/Graph/getEdges.d.mts +47 -0
- package/dist/Graph/getEdges.mjs +51 -0
- package/dist/Graph/getNeighbor.d.mts +50 -0
- package/dist/Graph/getNeighbor.mjs +56 -0
- package/dist/Graph/getNeighbors.d.mts +47 -0
- package/dist/Graph/getNeighbors.mjs +58 -0
- package/dist/Graph/getNode.d.mts +48 -0
- package/dist/Graph/getNode.mjs +52 -0
- package/dist/Graph/getNodes.d.mts +46 -0
- package/dist/Graph/getNodes.mjs +48 -0
- package/dist/Graph/hasEdge.d.mts +46 -0
- package/dist/Graph/hasEdge.mjs +50 -0
- package/dist/Graph/hasNode.d.mts +42 -0
- package/dist/Graph/hasNode.mjs +46 -0
- package/dist/Graph/index.d.mts +68 -0
- package/dist/Graph/index.mjs +65 -0
- package/dist/Graph/internals/core.mjs +51 -0
- package/dist/Graph/internals/parseNodeIdentifier.mjs +7 -0
- package/dist/Graph/internals/types.d.mts +16 -0
- package/dist/Graph/mapEdge.d.mts +52 -0
- package/dist/Graph/mapEdge.mjs +60 -0
- package/dist/Graph/mapNode.d.mts +65 -0
- package/dist/Graph/mapNode.mjs +83 -0
- package/dist/Graph/mergeEdge.d.mts +52 -0
- package/dist/Graph/mergeEdge.mjs +63 -0
- package/dist/Graph/mergeNode.d.mts +50 -0
- package/dist/Graph/mergeNode.mjs +60 -0
- package/dist/Graph/removeEdge.d.mts +49 -0
- package/dist/Graph/removeEdge.mjs +71 -0
- package/dist/Graph/removeNode.d.mts +46 -0
- package/dist/Graph/removeNode.mjs +71 -0
- package/dist/Graph/setEdge.d.mts +52 -0
- package/dist/Graph/setEdge.mjs +78 -0
- package/dist/Graph/setNode.d.mts +43 -0
- package/dist/Graph/setNode.mjs +50 -0
- package/dist/Graph/toJS.d.mts +59 -0
- package/dist/Graph/toJS.mjs +82 -0
- package/dist/Graph/types.d.mts +29 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,1391 @@
|
|
|
2
2
|
|
|
3
3
|
<h1>graph</h1>
|
|
4
4
|
|
|
5
|
+
 
|
|
6
|
+
|
|
5
7
|
**Functional graph data-structure.**
|
|
6
8
|
|
|
7
9
|
[Documentation](https://MichaelOstermann.github.io/graph)
|
|
8
10
|
|
|
9
11
|
</div>
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Graph } from "@monstermann/graph";
|
|
17
|
+
|
|
18
|
+
// Define your node and edge types
|
|
19
|
+
type Nodes =
|
|
20
|
+
| { type: "User"; id: string; name: string }
|
|
21
|
+
| { type: "Post"; id: string; title: string }
|
|
22
|
+
| { type: "Comment"; id: string; text: string };
|
|
23
|
+
|
|
24
|
+
type Edges = {
|
|
25
|
+
User: { Post: { role: "author" | "editor" }; Comment: void };
|
|
26
|
+
Post: { Comment: void };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Create a graph
|
|
30
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
31
|
+
|
|
32
|
+
// Add nodes
|
|
33
|
+
let g = Graph.setNode(graph, { type: "User", id: "1", name: "Alice" });
|
|
34
|
+
g = Graph.setNode(g, { type: "Post", id: "1", title: "Hello World" });
|
|
35
|
+
g = Graph.setNode(g, { type: "Comment", id: "1", text: "Great post!" });
|
|
36
|
+
|
|
37
|
+
// Add edges with data
|
|
38
|
+
g = Graph.setEdge(g, ["User", "1"], ["Post", "1"], { role: "author" });
|
|
39
|
+
g = Graph.setEdge(g, ["User", "1"], ["Comment", "1"]);
|
|
40
|
+
g = Graph.setEdge(g, ["Post", "1"], ["Comment", "1"]);
|
|
41
|
+
|
|
42
|
+
// Query the graph
|
|
43
|
+
const user = Graph.getNode(g, ["User", "1"]);
|
|
44
|
+
// user: { type: "User", id: "1", name: "Alice" }
|
|
45
|
+
|
|
46
|
+
const userPosts = Graph.getNeighbors(g, ["User", "1"], "Post");
|
|
47
|
+
// userPosts: [{ type: "Post", id: "1", title: "Hello World" }]
|
|
48
|
+
|
|
49
|
+
const postEdge = Graph.getEdge(g, ["User", "1"], ["Post", "1"]);
|
|
50
|
+
// postEdge: { role: "author" }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```sh [npm]
|
|
56
|
+
npm install @monstermann/graph
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```sh [pnpm]
|
|
60
|
+
pnpm add @monstermann/graph
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```sh [yarn]
|
|
64
|
+
yarn add @monstermann/graph
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```sh [bun]
|
|
68
|
+
bun add @monstermann/graph
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Tree-shaking
|
|
72
|
+
|
|
73
|
+
### Installation
|
|
74
|
+
|
|
75
|
+
```sh [npm]
|
|
76
|
+
npm install -D @monstermann/unplugin-graph
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```sh [pnpm]
|
|
80
|
+
pnpm -D add @monstermann/unplugin-graph
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```sh [yarn]
|
|
84
|
+
yarn -D add @monstermann/unplugin-graph
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```sh [bun]
|
|
88
|
+
bun -D add @monstermann/unplugin-graph
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Usage
|
|
92
|
+
|
|
93
|
+
```ts [Vite]
|
|
94
|
+
// vite.config.ts
|
|
95
|
+
import graph from "@monstermann/unplugin-graph/vite";
|
|
96
|
+
|
|
97
|
+
export default defineConfig({
|
|
98
|
+
plugins: [graph()],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```ts [Rollup]
|
|
103
|
+
// rollup.config.js
|
|
104
|
+
import graph from "@monstermann/unplugin-graph/rollup";
|
|
105
|
+
|
|
106
|
+
export default {
|
|
107
|
+
plugins: [graph()],
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```ts [Rolldown]
|
|
112
|
+
// rolldown.config.js
|
|
113
|
+
import graph from "@monstermann/unplugin-graph/rolldown";
|
|
114
|
+
|
|
115
|
+
export default {
|
|
116
|
+
plugins: [graph()],
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```ts [Webpack]
|
|
121
|
+
// webpack.config.js
|
|
122
|
+
const graph = require("@monstermann/unplugin-graph/webpack");
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
plugins: [graph()],
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```ts [Rspack]
|
|
130
|
+
// rspack.config.js
|
|
131
|
+
const graph = require("@monstermann/unplugin-graph/rspack");
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
plugins: [graph()],
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```ts [ESBuild]
|
|
139
|
+
// esbuild.config.js
|
|
140
|
+
import { build } from "esbuild";
|
|
141
|
+
import graph from "@monstermann/unplugin-graph/esbuild";
|
|
142
|
+
|
|
143
|
+
build({
|
|
144
|
+
plugins: [graph()],
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Graph
|
|
149
|
+
|
|
150
|
+
### batch
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
function Graph.batch(
|
|
154
|
+
graph: Graph,
|
|
155
|
+
transform: (graph: Graph) => Graph | void,
|
|
156
|
+
): Graph
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Performs multiple graph mutations efficiently by batching them together. Returns the original graph if no changes were made, optimizing for structural sharing.
|
|
160
|
+
|
|
161
|
+
#### Example
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { Graph } from "@monstermann/graph";
|
|
165
|
+
|
|
166
|
+
type Nodes =
|
|
167
|
+
| { type: "Task"; id: string; title: string }
|
|
168
|
+
| { type: "Section"; id: string; name: string }
|
|
169
|
+
| { type: "Project"; id: string; name: string };
|
|
170
|
+
|
|
171
|
+
type Edges = {
|
|
172
|
+
Project: { Task: void };
|
|
173
|
+
Section: { Task: void };
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
177
|
+
|
|
178
|
+
// Add multiple nodes and edges in a single batch
|
|
179
|
+
const updated = Graph.batch(graph, (g) => {
|
|
180
|
+
g = Graph.setNode(g, { type: "Project", id: "1", name: "My Project" });
|
|
181
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "Task 1" });
|
|
182
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Task 2" });
|
|
183
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
184
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"]);
|
|
185
|
+
return g;
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### create
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
function Graph.create<Nodes, Edges>(): Graph<Nodes, Edges>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Creates a new empty graph with the provided nodes & edges configuration.
|
|
196
|
+
|
|
197
|
+
#### Example
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { Graph } from "@monstermann/graph";
|
|
201
|
+
|
|
202
|
+
type Nodes =
|
|
203
|
+
| { type: "Task"; id: string }
|
|
204
|
+
| { type: "Section"; id: string }
|
|
205
|
+
| { type: "Project"; id: string };
|
|
206
|
+
|
|
207
|
+
type Edges = {
|
|
208
|
+
// Project <-> Task
|
|
209
|
+
Project: { Task: void };
|
|
210
|
+
// Section <-> Task
|
|
211
|
+
Section: { Task: void };
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### findEdge
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
function Graph.findEdge(
|
|
221
|
+
graph: Graph,
|
|
222
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
223
|
+
type: NodeType,
|
|
224
|
+
find: (edge: EdgeData) => boolean,
|
|
225
|
+
): EdgeData | undefined
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Finds the first edge of a specific type from a source node that matches the predicate. Returns `undefined` if no matching edge is found.
|
|
229
|
+
|
|
230
|
+
#### Example
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { Graph } from "@monstermann/graph";
|
|
234
|
+
|
|
235
|
+
type Nodes =
|
|
236
|
+
| { type: "Task"; id: string }
|
|
237
|
+
| { type: "Section"; id: string }
|
|
238
|
+
| { type: "Project"; id: string };
|
|
239
|
+
|
|
240
|
+
type Edges = {
|
|
241
|
+
Project: { Task: { priority: number; assignedAt: Date } };
|
|
242
|
+
Section: { Task: void };
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
246
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
247
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
248
|
+
g = Graph.setNode(g, { type: "Task", id: "2" });
|
|
249
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
250
|
+
priority: 1,
|
|
251
|
+
assignedAt: new Date(),
|
|
252
|
+
});
|
|
253
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], {
|
|
254
|
+
priority: 2,
|
|
255
|
+
assignedAt: new Date(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const highPriorityEdge = Graph.findEdge(
|
|
259
|
+
g,
|
|
260
|
+
["Project", "1"],
|
|
261
|
+
"Task",
|
|
262
|
+
(edge) => edge.priority > 1,
|
|
263
|
+
);
|
|
264
|
+
// highPriorityEdge: { priority: 2, assignedAt: Date }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### findEdges
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
function Graph.findEdges(
|
|
271
|
+
graph: Graph,
|
|
272
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
273
|
+
type: NodeType,
|
|
274
|
+
find: (edge: EdgeData) => boolean,
|
|
275
|
+
): EdgeData[]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Finds all edges of a specific type from a source node that match the predicate.
|
|
279
|
+
|
|
280
|
+
#### Example
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { Graph } from "@monstermann/graph";
|
|
284
|
+
|
|
285
|
+
type Nodes =
|
|
286
|
+
| { type: "Task"; id: string }
|
|
287
|
+
| { type: "Section"; id: string }
|
|
288
|
+
| { type: "Project"; id: string };
|
|
289
|
+
|
|
290
|
+
type Edges = {
|
|
291
|
+
Project: { Task: { priority: number; assignedAt: Date } };
|
|
292
|
+
Section: { Task: void };
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
296
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
297
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
298
|
+
g = Graph.setNode(g, { type: "Task", id: "2" });
|
|
299
|
+
g = Graph.setNode(g, { type: "Task", id: "3" });
|
|
300
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
301
|
+
priority: 1,
|
|
302
|
+
assignedAt: new Date(),
|
|
303
|
+
});
|
|
304
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], {
|
|
305
|
+
priority: 2,
|
|
306
|
+
assignedAt: new Date(),
|
|
307
|
+
});
|
|
308
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "3"], {
|
|
309
|
+
priority: 3,
|
|
310
|
+
assignedAt: new Date(),
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const highPriorityEdges = Graph.findEdges(
|
|
314
|
+
g,
|
|
315
|
+
["Project", "1"],
|
|
316
|
+
"Task",
|
|
317
|
+
(edge) => edge.priority >= 2,
|
|
318
|
+
);
|
|
319
|
+
// highPriorityEdges: [{ priority: 2, ... }, { priority: 3, ... }]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### findNeighbor
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
function Graph.findNeighbor(
|
|
326
|
+
graph: Graph,
|
|
327
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
328
|
+
type: NodeType,
|
|
329
|
+
find: (target: Node, edge: EdgeData, source: Node) => boolean,
|
|
330
|
+
): Node | undefined
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Finds the first neighbor node of a specific type that matches the predicate. The predicate receives the target node, edge data, and source node. Returns `undefined` if no matching neighbor is found.
|
|
334
|
+
|
|
335
|
+
#### Example
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { Graph } from "@monstermann/graph";
|
|
339
|
+
|
|
340
|
+
type Nodes =
|
|
341
|
+
| { type: "Task"; id: string; title: string }
|
|
342
|
+
| { type: "Section"; id: string }
|
|
343
|
+
| { type: "Project"; id: string };
|
|
344
|
+
|
|
345
|
+
type Edges = {
|
|
346
|
+
Project: { Task: { priority: number } };
|
|
347
|
+
Section: { Task: void };
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
351
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
352
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "Low Priority Task" });
|
|
353
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "High Priority Task" });
|
|
354
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], { priority: 1 });
|
|
355
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], { priority: 3 });
|
|
356
|
+
|
|
357
|
+
const highPriorityTask = Graph.findNeighbor(
|
|
358
|
+
g,
|
|
359
|
+
["Project", "1"],
|
|
360
|
+
"Task",
|
|
361
|
+
(task, edge, project) => edge.priority > 2,
|
|
362
|
+
);
|
|
363
|
+
// highPriorityTask: { type: "Task", id: "2", title: "High Priority Task" }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### findNeighbors
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
function Graph.findNeighbors(
|
|
370
|
+
graph: Graph,
|
|
371
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
372
|
+
type: NodeType,
|
|
373
|
+
find: (target: Node, edge: EdgeData, source: Node) => boolean,
|
|
374
|
+
): Node[]
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Finds all neighbor nodes of a specific type that match the predicate. The predicate receives the target node, edge data, and source node.
|
|
378
|
+
|
|
379
|
+
#### Example
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
import { Graph } from "@monstermann/graph";
|
|
383
|
+
|
|
384
|
+
type Nodes =
|
|
385
|
+
| { type: "Task"; id: string; title: string }
|
|
386
|
+
| { type: "Section"; id: string }
|
|
387
|
+
| { type: "Project"; id: string };
|
|
388
|
+
|
|
389
|
+
type Edges = {
|
|
390
|
+
Project: { Task: { priority: number } };
|
|
391
|
+
Section: { Task: void };
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
395
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
396
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "Low Priority Task" });
|
|
397
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Medium Priority Task" });
|
|
398
|
+
g = Graph.setNode(g, { type: "Task", id: "3", title: "High Priority Task" });
|
|
399
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], { priority: 1 });
|
|
400
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], { priority: 2 });
|
|
401
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "3"], { priority: 3 });
|
|
402
|
+
|
|
403
|
+
const mediumAndHighPriority = Graph.findNeighbors(
|
|
404
|
+
g,
|
|
405
|
+
["Project", "1"],
|
|
406
|
+
"Task",
|
|
407
|
+
(task, edge, project) => edge.priority >= 2,
|
|
408
|
+
);
|
|
409
|
+
// mediumAndHighPriority: [
|
|
410
|
+
// { type: "Task", id: "2", title: "Medium Priority Task" },
|
|
411
|
+
// { type: "Task", id: "3", title: "High Priority Task" }
|
|
412
|
+
// ]
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### findNode
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
function Graph.findNode(
|
|
419
|
+
graph: Graph,
|
|
420
|
+
type: NodeType,
|
|
421
|
+
find: (node: Node) => boolean,
|
|
422
|
+
): Node | undefined
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Finds the first node of a specific type that matches the predicate. Returns `undefined` if no matching node is found.
|
|
426
|
+
|
|
427
|
+
#### Example
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { Graph } from "@monstermann/graph";
|
|
431
|
+
|
|
432
|
+
type Nodes =
|
|
433
|
+
| { type: "Task"; id: string; title: string; completed: boolean }
|
|
434
|
+
| { type: "Section"; id: string }
|
|
435
|
+
| { type: "Project"; id: string };
|
|
436
|
+
|
|
437
|
+
type Edges = {
|
|
438
|
+
Project: { Task: void };
|
|
439
|
+
Section: { Task: void };
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
443
|
+
let g = Graph.setNode(graph, {
|
|
444
|
+
type: "Task",
|
|
445
|
+
id: "1",
|
|
446
|
+
title: "First Task",
|
|
447
|
+
completed: false,
|
|
448
|
+
});
|
|
449
|
+
g = Graph.setNode(g, {
|
|
450
|
+
type: "Task",
|
|
451
|
+
id: "2",
|
|
452
|
+
title: "Second Task",
|
|
453
|
+
completed: true,
|
|
454
|
+
});
|
|
455
|
+
g = Graph.setNode(g, {
|
|
456
|
+
type: "Task",
|
|
457
|
+
id: "3",
|
|
458
|
+
title: "Third Task",
|
|
459
|
+
completed: false,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const completedTask = Graph.findNode(g, "Task", (task) => task.completed);
|
|
463
|
+
// completedTask: { type: "Task", id: "2", title: "Second Task", completed: true }
|
|
464
|
+
|
|
465
|
+
const taskWithTitle = Graph.findNode(
|
|
466
|
+
g,
|
|
467
|
+
"Task",
|
|
468
|
+
(task) => task.title === "First Task",
|
|
469
|
+
);
|
|
470
|
+
// taskWithTitle: { type: "Task", id: "1", title: "First Task", completed: false }
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### findNodes
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
function Graph.findNodes(
|
|
477
|
+
graph: Graph,
|
|
478
|
+
type: NodeType,
|
|
479
|
+
find: (node: Node) => boolean,
|
|
480
|
+
): Node[]
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Finds all nodes of a specific type that match the predicate.
|
|
484
|
+
|
|
485
|
+
#### Example
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import { Graph } from "@monstermann/graph";
|
|
489
|
+
|
|
490
|
+
type Nodes =
|
|
491
|
+
| { type: "Task"; id: string; title: string; completed: boolean }
|
|
492
|
+
| { type: "Section"; id: string }
|
|
493
|
+
| { type: "Project"; id: string };
|
|
494
|
+
|
|
495
|
+
type Edges = {
|
|
496
|
+
Project: { Task: void };
|
|
497
|
+
Section: { Task: void };
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
501
|
+
let g = Graph.setNode(graph, {
|
|
502
|
+
type: "Task",
|
|
503
|
+
id: "1",
|
|
504
|
+
title: "First Task",
|
|
505
|
+
completed: false,
|
|
506
|
+
});
|
|
507
|
+
g = Graph.setNode(g, {
|
|
508
|
+
type: "Task",
|
|
509
|
+
id: "2",
|
|
510
|
+
title: "Second Task",
|
|
511
|
+
completed: true,
|
|
512
|
+
});
|
|
513
|
+
g = Graph.setNode(g, {
|
|
514
|
+
type: "Task",
|
|
515
|
+
id: "3",
|
|
516
|
+
title: "Third Task",
|
|
517
|
+
completed: false,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const incompleteTasks = Graph.findNodes(g, "Task", (task) => !task.completed);
|
|
521
|
+
// incompleteTasks: [
|
|
522
|
+
// { type: "Task", id: "1", title: "First Task", completed: false },
|
|
523
|
+
// { type: "Task", id: "3", title: "Third Task", completed: false }
|
|
524
|
+
// ]
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### forEachEdge
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
function Graph.forEachEdge(
|
|
531
|
+
graph: Graph,
|
|
532
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
533
|
+
type: NodeType,
|
|
534
|
+
fn: (edge: EdgeData) => void,
|
|
535
|
+
): Graph
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Iterates over all edges of a specific type from a source node, executing a function for each edge. Returns the graph unchanged (for chaining).
|
|
539
|
+
|
|
540
|
+
#### Example
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
import { Graph } from "@monstermann/graph";
|
|
544
|
+
|
|
545
|
+
type Nodes =
|
|
546
|
+
| { type: "Task"; id: string }
|
|
547
|
+
| { type: "Section"; id: string }
|
|
548
|
+
| { type: "Project"; id: string };
|
|
549
|
+
|
|
550
|
+
type Edges = {
|
|
551
|
+
Project: { Task: { priority: number } };
|
|
552
|
+
Section: { Task: void };
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
556
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
557
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
558
|
+
g = Graph.setNode(g, { type: "Task", id: "2" });
|
|
559
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], { priority: 1 });
|
|
560
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], { priority: 2 });
|
|
561
|
+
|
|
562
|
+
Graph.forEachEdge(g, ["Project", "1"], "Task", (edge) => {
|
|
563
|
+
console.log(edge.priority);
|
|
564
|
+
});
|
|
565
|
+
// Logs:
|
|
566
|
+
// 1
|
|
567
|
+
// 2
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### forEachNeighbor
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
function Graph.forEachNeighbor(
|
|
574
|
+
graph: Graph,
|
|
575
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
576
|
+
type: NodeType,
|
|
577
|
+
fn: (target: Node, edge: EdgeData, source: Node) => void,
|
|
578
|
+
): Graph
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Iterates over all neighbor nodes of a specific type, executing a function for each neighbor. The function receives the target node, edge data, and source node. Returns the graph unchanged (for chaining).
|
|
582
|
+
|
|
583
|
+
#### Example
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
import { Graph } from "@monstermann/graph";
|
|
587
|
+
|
|
588
|
+
type Nodes =
|
|
589
|
+
| { type: "Task"; id: string; title: string }
|
|
590
|
+
| { type: "Section"; id: string }
|
|
591
|
+
| { type: "Project"; id: string; name: string };
|
|
592
|
+
|
|
593
|
+
type Edges = {
|
|
594
|
+
Project: { Task: { priority: number } };
|
|
595
|
+
Section: { Task: void };
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
599
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1", name: "My Project" });
|
|
600
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "First Task" });
|
|
601
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Second Task" });
|
|
602
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], { priority: 1 });
|
|
603
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], { priority: 2 });
|
|
604
|
+
|
|
605
|
+
Graph.forEachNeighbor(g, ["Project", "1"], "Task", (task, edge, project) => {
|
|
606
|
+
console.log(`${project.name}: ${task.title} (priority: ${edge.priority})`);
|
|
607
|
+
});
|
|
608
|
+
// Logs:
|
|
609
|
+
// "My Project: First Task (priority: 1)"
|
|
610
|
+
// "My Project: Second Task (priority: 2)"
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### forEachNode
|
|
614
|
+
|
|
615
|
+
```ts
|
|
616
|
+
function Graph.forEachNode(
|
|
617
|
+
graph: Graph,
|
|
618
|
+
type: NodeType,
|
|
619
|
+
fn: (node: Node) => void,
|
|
620
|
+
): Graph
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Iterates over all nodes of a specific type, executing a function for each node. Returns the graph unchanged (for chaining).
|
|
624
|
+
|
|
625
|
+
#### Example
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
import { Graph } from "@monstermann/graph";
|
|
629
|
+
|
|
630
|
+
type Nodes =
|
|
631
|
+
| { type: "Task"; id: string; title: string }
|
|
632
|
+
| { type: "Section"; id: string }
|
|
633
|
+
| { type: "Project"; id: string };
|
|
634
|
+
|
|
635
|
+
type Edges = {
|
|
636
|
+
Project: { Task: void };
|
|
637
|
+
Section: { Task: void };
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
641
|
+
let g = Graph.setNode(graph, { type: "Task", id: "1", title: "First Task" });
|
|
642
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Second Task" });
|
|
643
|
+
g = Graph.setNode(g, { type: "Task", id: "3", title: "Third Task" });
|
|
644
|
+
|
|
645
|
+
Graph.forEachNode(g, "Task", (task) => {
|
|
646
|
+
console.log(task.title);
|
|
647
|
+
});
|
|
648
|
+
// Logs:
|
|
649
|
+
// "First Task"
|
|
650
|
+
// "Second Task"
|
|
651
|
+
// "Third Task"
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### fromJS
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
function Graph.fromJS(data: {
|
|
658
|
+
nodes: Node[];
|
|
659
|
+
edges: [NodeType, NodeId, NodeType, NodeId, EdgeData][];
|
|
660
|
+
}): Graph
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Creates a graph from a plain JavaScript object representation. Useful for deserializing graphs from JSON or other storage formats.
|
|
664
|
+
|
|
665
|
+
#### Example
|
|
666
|
+
|
|
667
|
+
```ts
|
|
668
|
+
import { Graph } from "@monstermann/graph";
|
|
669
|
+
|
|
670
|
+
type Nodes =
|
|
671
|
+
| { type: "Task"; id: string; title: string }
|
|
672
|
+
| { type: "Section"; id: string }
|
|
673
|
+
| { type: "Project"; id: string };
|
|
674
|
+
|
|
675
|
+
type Edges = {
|
|
676
|
+
Project: { Task: void };
|
|
677
|
+
Section: { Task: void };
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
const data = {
|
|
681
|
+
nodes: [
|
|
682
|
+
{ type: "Project", id: "1" },
|
|
683
|
+
{ type: "Task", id: "1", title: "My Task" },
|
|
684
|
+
{ type: "Task", id: "2", title: "Another Task" },
|
|
685
|
+
],
|
|
686
|
+
edges: [
|
|
687
|
+
["Project", "1", "Task", "1", undefined],
|
|
688
|
+
["Project", "1", "Task", "2", undefined],
|
|
689
|
+
],
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const graph = Graph.fromJS<Nodes, Edges>(data);
|
|
693
|
+
|
|
694
|
+
Graph.hasNode(graph, ["Project", "1"]); // true
|
|
695
|
+
Graph.hasEdge(graph, ["Project", "1"], ["Task", "1"]); // true
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### getEdge
|
|
699
|
+
|
|
700
|
+
```ts
|
|
701
|
+
function Graph.getEdge(
|
|
702
|
+
graph: Graph,
|
|
703
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
704
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
705
|
+
): EdgeData | undefined
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
Retrieves the edge data between two nodes. Returns `undefined` if the edge doesn't exist.
|
|
709
|
+
|
|
710
|
+
#### Example
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
import { Graph } from "@monstermann/graph";
|
|
714
|
+
|
|
715
|
+
type Nodes =
|
|
716
|
+
| { type: "Task"; id: string }
|
|
717
|
+
| { type: "Section"; id: string }
|
|
718
|
+
| { type: "Project"; id: string };
|
|
719
|
+
|
|
720
|
+
type Edges = {
|
|
721
|
+
Project: { Task: { assignedAt: Date } };
|
|
722
|
+
Section: { Task: void };
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
726
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
727
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
728
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
729
|
+
assignedAt: new Date(),
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
const edge = Graph.getEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
733
|
+
// edge: { assignedAt: Date }
|
|
734
|
+
|
|
735
|
+
const missing = Graph.getEdge(g, ["Project", "1"], ["Task", "2"]);
|
|
736
|
+
// missing: undefined
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### getEdges
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
function Graph.getEdges(
|
|
743
|
+
graph: Graph,
|
|
744
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
745
|
+
type: NodeType,
|
|
746
|
+
): EdgeData[]
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Retrieves all edges of a specific type from a source node.
|
|
750
|
+
|
|
751
|
+
#### Example
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
import { Graph } from "@monstermann/graph";
|
|
755
|
+
|
|
756
|
+
type Nodes =
|
|
757
|
+
| { type: "Task"; id: string }
|
|
758
|
+
| { type: "Section"; id: string }
|
|
759
|
+
| { type: "Project"; id: string };
|
|
760
|
+
|
|
761
|
+
type Edges = {
|
|
762
|
+
Project: { Task: { priority: number } };
|
|
763
|
+
Section: { Task: void };
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
767
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
768
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
769
|
+
g = Graph.setNode(g, { type: "Task", id: "2" });
|
|
770
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], { priority: 1 });
|
|
771
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"], { priority: 2 });
|
|
772
|
+
|
|
773
|
+
const edges = Graph.getEdges(g, ["Project", "1"], "Task");
|
|
774
|
+
// edges: [{ priority: 1 }, { priority: 2 }]
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### getNeighbor
|
|
778
|
+
|
|
779
|
+
```ts
|
|
780
|
+
function Graph.getNeighbor(
|
|
781
|
+
graph: Graph,
|
|
782
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
783
|
+
type: NodeType,
|
|
784
|
+
): Node | undefined
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Retrieves the first neighbor node of a specific type connected to the source node. Returns `undefined` if no neighbor of the specified type exists.
|
|
788
|
+
|
|
789
|
+
#### Example
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
import { Graph } from "@monstermann/graph";
|
|
793
|
+
|
|
794
|
+
type Nodes =
|
|
795
|
+
| { type: "Task"; id: string; title: string }
|
|
796
|
+
| { type: "Section"; id: string }
|
|
797
|
+
| { type: "Project"; id: string };
|
|
798
|
+
|
|
799
|
+
type Edges = {
|
|
800
|
+
Project: { Task: void };
|
|
801
|
+
Section: { Task: void };
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
805
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
806
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "Task 1" });
|
|
807
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Task 2" });
|
|
808
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
809
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"]);
|
|
810
|
+
|
|
811
|
+
const firstTask = Graph.getNeighbor(g, ["Project", "1"], "Task");
|
|
812
|
+
// firstTask: { type: "Task", id: "1", title: "Task 1" }
|
|
813
|
+
|
|
814
|
+
const noSection = Graph.getNeighbor(g, ["Project", "1"], "Section");
|
|
815
|
+
// noSection: undefined
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### getNeighbors
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
function Graph.getNeighbors(
|
|
822
|
+
graph: Graph,
|
|
823
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
824
|
+
type: NodeType,
|
|
825
|
+
): Node[]
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
Retrieves all neighbor nodes of a specific type connected to the source node.
|
|
829
|
+
|
|
830
|
+
#### Example
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
import { Graph } from "@monstermann/graph";
|
|
834
|
+
|
|
835
|
+
type Nodes =
|
|
836
|
+
| { type: "Task"; id: string; title: string }
|
|
837
|
+
| { type: "Section"; id: string }
|
|
838
|
+
| { type: "Project"; id: string };
|
|
839
|
+
|
|
840
|
+
type Edges = {
|
|
841
|
+
Project: { Task: void };
|
|
842
|
+
Section: { Task: void };
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
846
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
847
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "Task 1" });
|
|
848
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Task 2" });
|
|
849
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
850
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "2"]);
|
|
851
|
+
|
|
852
|
+
const tasks = Graph.getNeighbors(g, ["Project", "1"], "Task");
|
|
853
|
+
// tasks: [{ type: "Task", id: "1", title: "Task 1" }, { type: "Task", id: "2", title: "Task 2" }]
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### getNode
|
|
857
|
+
|
|
858
|
+
```ts
|
|
859
|
+
function Graph.getNode(
|
|
860
|
+
graph: Graph,
|
|
861
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
862
|
+
): Node | undefined
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Retrieves a node from the graph. Returns `undefined` if the node doesn't exist.
|
|
866
|
+
|
|
867
|
+
#### Example
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
import { Graph } from "@monstermann/graph";
|
|
871
|
+
|
|
872
|
+
type Nodes =
|
|
873
|
+
| { type: "Task"; id: string; title: string }
|
|
874
|
+
| { type: "Section"; id: string }
|
|
875
|
+
| { type: "Project"; id: string };
|
|
876
|
+
|
|
877
|
+
type Edges = {
|
|
878
|
+
Project: { Task: void };
|
|
879
|
+
Section: { Task: void };
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
883
|
+
const withNode = Graph.setNode(graph, {
|
|
884
|
+
type: "Task",
|
|
885
|
+
id: "1",
|
|
886
|
+
title: "My Task",
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
const task = Graph.getNode(withNode, ["Task", "1"]);
|
|
890
|
+
// task: { type: "Task", id: "1", title: "My Task" }
|
|
891
|
+
|
|
892
|
+
const missing = Graph.getNode(withNode, ["Task", "2"]);
|
|
893
|
+
// missing: undefined
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### getNodes
|
|
897
|
+
|
|
898
|
+
```ts
|
|
899
|
+
function Graph.getNodes(
|
|
900
|
+
graph: Graph,
|
|
901
|
+
type: NodeType,
|
|
902
|
+
): Node[]
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
Retrieves all nodes of a specific type from the graph.
|
|
906
|
+
|
|
907
|
+
#### Example
|
|
908
|
+
|
|
909
|
+
```ts
|
|
910
|
+
import { Graph } from "@monstermann/graph";
|
|
911
|
+
|
|
912
|
+
type Nodes =
|
|
913
|
+
| { type: "Task"; id: string; title: string }
|
|
914
|
+
| { type: "Section"; id: string }
|
|
915
|
+
| { type: "Project"; id: string };
|
|
916
|
+
|
|
917
|
+
type Edges = {
|
|
918
|
+
Project: { Task: void };
|
|
919
|
+
Section: { Task: void };
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
923
|
+
let g = Graph.setNode(graph, { type: "Task", id: "1", title: "Task 1" });
|
|
924
|
+
g = Graph.setNode(g, { type: "Task", id: "2", title: "Task 2" });
|
|
925
|
+
g = Graph.setNode(g, { type: "Project", id: "1" });
|
|
926
|
+
|
|
927
|
+
const tasks = Graph.getNodes(g, "Task");
|
|
928
|
+
// tasks: [{ type: "Task", id: "1", title: "Task 1" }, { type: "Task", id: "2", title: "Task 2" }]
|
|
929
|
+
|
|
930
|
+
const projects = Graph.getNodes(g, "Project");
|
|
931
|
+
// projects: [{ type: "Project", id: "1" }]
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
### hasEdge
|
|
935
|
+
|
|
936
|
+
```ts
|
|
937
|
+
function Graph.hasEdge(
|
|
938
|
+
graph: Graph,
|
|
939
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
940
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
941
|
+
): boolean
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Checks if an edge exists between two nodes in the graph.
|
|
945
|
+
|
|
946
|
+
#### Example
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
import { Graph } from "@monstermann/graph";
|
|
950
|
+
|
|
951
|
+
type Nodes =
|
|
952
|
+
| { type: "Task"; id: string }
|
|
953
|
+
| { type: "Section"; id: string }
|
|
954
|
+
| { type: "Project"; id: string };
|
|
955
|
+
|
|
956
|
+
type Edges = {
|
|
957
|
+
Project: { Task: void };
|
|
958
|
+
Section: { Task: void };
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
962
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
963
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
964
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
965
|
+
|
|
966
|
+
Graph.hasEdge(g, ["Project", "1"], ["Task", "1"]); // true
|
|
967
|
+
Graph.hasEdge(g, ["Task", "1"], ["Project", "1"]); // true (bidirectional)
|
|
968
|
+
Graph.hasEdge(g, ["Project", "1"], ["Task", "2"]); // false
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
### hasNode
|
|
972
|
+
|
|
973
|
+
```ts
|
|
974
|
+
function Graph.hasNode(
|
|
975
|
+
graph: Graph,
|
|
976
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
977
|
+
): boolean
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
Checks if a node exists in the graph.
|
|
981
|
+
|
|
982
|
+
#### Example
|
|
983
|
+
|
|
984
|
+
```ts
|
|
985
|
+
import { Graph } from "@monstermann/graph";
|
|
986
|
+
|
|
987
|
+
type Nodes =
|
|
988
|
+
| { type: "Task"; id: string }
|
|
989
|
+
| { type: "Section"; id: string }
|
|
990
|
+
| { type: "Project"; id: string };
|
|
991
|
+
|
|
992
|
+
type Edges = {
|
|
993
|
+
Project: { Task: void };
|
|
994
|
+
Section: { Task: void };
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
998
|
+
const withNode = Graph.setNode(graph, { type: "Task", id: "1" });
|
|
999
|
+
|
|
1000
|
+
Graph.hasNode(withNode, ["Task", "1"]); // true
|
|
1001
|
+
Graph.hasNode(withNode, { type: "Task", id: "1" }); // true
|
|
1002
|
+
Graph.hasNode(withNode, ["Task", "2"]); // false
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
### mapEdge
|
|
1006
|
+
|
|
1007
|
+
```ts
|
|
1008
|
+
function Graph.mapEdge(
|
|
1009
|
+
graph: Graph,
|
|
1010
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1011
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1012
|
+
update: (edge: EdgeData) => EdgeData,
|
|
1013
|
+
): Graph
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
Updates an edge by applying a transformation function. Returns a new graph instance with the updated edge.
|
|
1017
|
+
|
|
1018
|
+
#### Example
|
|
1019
|
+
|
|
1020
|
+
```ts
|
|
1021
|
+
import { Graph } from "@monstermann/graph";
|
|
1022
|
+
|
|
1023
|
+
type Nodes =
|
|
1024
|
+
| { type: "Task"; id: string }
|
|
1025
|
+
| { type: "Section"; id: string }
|
|
1026
|
+
| { type: "Project"; id: string };
|
|
1027
|
+
|
|
1028
|
+
type Edges = {
|
|
1029
|
+
Project: { Task: { priority: number; assignedAt: Date } };
|
|
1030
|
+
Section: { Task: void };
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1034
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1035
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
1036
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
1037
|
+
priority: 1,
|
|
1038
|
+
assignedAt: new Date(),
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// Update edge data
|
|
1042
|
+
g = Graph.mapEdge(g, ["Project", "1"], ["Task", "1"], (edge) => ({
|
|
1043
|
+
...edge,
|
|
1044
|
+
priority: 2,
|
|
1045
|
+
}));
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
### mapNode
|
|
1049
|
+
|
|
1050
|
+
```ts
|
|
1051
|
+
function Graph.mapNode(
|
|
1052
|
+
graph: Graph,
|
|
1053
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1054
|
+
update: (node: Node) => Node,
|
|
1055
|
+
): Graph
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Updates a node by applying a transformation function. If the node's type or id changes, all edges are preserved. Returns a new graph instance with the updated node.
|
|
1059
|
+
|
|
1060
|
+
#### Example
|
|
1061
|
+
|
|
1062
|
+
```ts
|
|
1063
|
+
import { Graph } from "@monstermann/graph";
|
|
1064
|
+
|
|
1065
|
+
type Nodes =
|
|
1066
|
+
| { type: "Task"; id: string; title: string; completed: boolean }
|
|
1067
|
+
| { type: "Section"; id: string }
|
|
1068
|
+
| { type: "Project"; id: string };
|
|
1069
|
+
|
|
1070
|
+
type Edges = {
|
|
1071
|
+
Project: { Task: void };
|
|
1072
|
+
Section: { Task: void };
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1076
|
+
let g = Graph.setNode(graph, {
|
|
1077
|
+
type: "Task",
|
|
1078
|
+
id: "1",
|
|
1079
|
+
title: "My Task",
|
|
1080
|
+
completed: false,
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
// Update a property
|
|
1084
|
+
g = Graph.mapNode(g, ["Task", "1"], (task) => ({
|
|
1085
|
+
...task,
|
|
1086
|
+
completed: true,
|
|
1087
|
+
}));
|
|
1088
|
+
|
|
1089
|
+
// Change id (edges are preserved)
|
|
1090
|
+
let g2 = Graph.setNode(graph, {
|
|
1091
|
+
type: "Task",
|
|
1092
|
+
id: "1",
|
|
1093
|
+
title: "Task",
|
|
1094
|
+
completed: false,
|
|
1095
|
+
});
|
|
1096
|
+
g2 = Graph.setNode(g2, { type: "Project", id: "1" });
|
|
1097
|
+
g2 = Graph.setEdge(g2, ["Project", "1"], ["Task", "1"]);
|
|
1098
|
+
g2 = Graph.mapNode(g2, ["Task", "1"], (task) => ({
|
|
1099
|
+
...task,
|
|
1100
|
+
id: "2",
|
|
1101
|
+
}));
|
|
1102
|
+
// Edge now connects ["Project", "1"] -> ["Task", "2"]
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
### mergeEdge
|
|
1106
|
+
|
|
1107
|
+
```ts
|
|
1108
|
+
function Graph.mergeEdge(
|
|
1109
|
+
graph: Graph,
|
|
1110
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1111
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1112
|
+
update: Partial<EdgeData>,
|
|
1113
|
+
): Graph
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
Partially updates an edge by merging the provided properties. Returns a new graph instance with the updated edge.
|
|
1117
|
+
|
|
1118
|
+
#### Example
|
|
1119
|
+
|
|
1120
|
+
```ts
|
|
1121
|
+
import { Graph } from "@monstermann/graph";
|
|
1122
|
+
|
|
1123
|
+
type Nodes =
|
|
1124
|
+
| { type: "Task"; id: string }
|
|
1125
|
+
| { type: "Section"; id: string }
|
|
1126
|
+
| { type: "Project"; id: string };
|
|
1127
|
+
|
|
1128
|
+
type Edges = {
|
|
1129
|
+
Project: { Task: { priority: number; assignedAt: Date } };
|
|
1130
|
+
Section: { Task: void };
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1134
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1135
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
1136
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
1137
|
+
priority: 1,
|
|
1138
|
+
assignedAt: new Date(),
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
// Merge partial update
|
|
1142
|
+
g = Graph.mergeEdge(g, ["Project", "1"], ["Task", "1"], { priority: 2 });
|
|
1143
|
+
|
|
1144
|
+
const edge = Graph.getEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
1145
|
+
// edge: { priority: 2, assignedAt: <original date> }
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
### mergeNode
|
|
1149
|
+
|
|
1150
|
+
```ts
|
|
1151
|
+
function Graph.mergeNode(
|
|
1152
|
+
graph: Graph,
|
|
1153
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1154
|
+
update: Partial<Node>,
|
|
1155
|
+
): Graph
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
Partially updates a node by merging the provided properties. Returns a new graph instance with the updated node.
|
|
1159
|
+
|
|
1160
|
+
#### Example
|
|
1161
|
+
|
|
1162
|
+
```ts
|
|
1163
|
+
import { Graph } from "@monstermann/graph";
|
|
1164
|
+
|
|
1165
|
+
type Nodes =
|
|
1166
|
+
| { type: "Task"; id: string; title: string; completed: boolean }
|
|
1167
|
+
| { type: "Section"; id: string }
|
|
1168
|
+
| { type: "Project"; id: string };
|
|
1169
|
+
|
|
1170
|
+
type Edges = {
|
|
1171
|
+
Project: { Task: void };
|
|
1172
|
+
Section: { Task: void };
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1176
|
+
let g = Graph.setNode(graph, {
|
|
1177
|
+
type: "Task",
|
|
1178
|
+
id: "1",
|
|
1179
|
+
title: "My Task",
|
|
1180
|
+
completed: false,
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// Merge partial update
|
|
1184
|
+
g = Graph.mergeNode(g, ["Task", "1"], { completed: true });
|
|
1185
|
+
|
|
1186
|
+
const task = Graph.getNode(g, ["Task", "1"]);
|
|
1187
|
+
// task: { type: "Task", id: "1", title: "My Task", completed: true }
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### removeEdge
|
|
1191
|
+
|
|
1192
|
+
```ts
|
|
1193
|
+
function Graph.removeEdge(
|
|
1194
|
+
graph: Graph,
|
|
1195
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1196
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1197
|
+
): Graph
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
Removes an edge between two nodes. The nodes remain in the graph. Returns a new graph instance without the edge.
|
|
1201
|
+
|
|
1202
|
+
#### Example
|
|
1203
|
+
|
|
1204
|
+
```ts
|
|
1205
|
+
import { Graph } from "@monstermann/graph";
|
|
1206
|
+
|
|
1207
|
+
type Nodes =
|
|
1208
|
+
| { type: "Task"; id: string }
|
|
1209
|
+
| { type: "Section"; id: string }
|
|
1210
|
+
| { type: "Project"; id: string };
|
|
1211
|
+
|
|
1212
|
+
type Edges = {
|
|
1213
|
+
Project: { Task: void };
|
|
1214
|
+
Section: { Task: void };
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1218
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1219
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
1220
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
1221
|
+
|
|
1222
|
+
// Remove the edge
|
|
1223
|
+
g = Graph.removeEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
1224
|
+
|
|
1225
|
+
Graph.hasNode(g, ["Project", "1"]); // true
|
|
1226
|
+
Graph.hasNode(g, ["Task", "1"]); // true
|
|
1227
|
+
Graph.hasEdge(g, ["Project", "1"], ["Task", "1"]); // false
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
### removeNode
|
|
1231
|
+
|
|
1232
|
+
```ts
|
|
1233
|
+
function Graph.removeNode(
|
|
1234
|
+
graph: Graph,
|
|
1235
|
+
node: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1236
|
+
): Graph
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
Removes a node and all its associated edges from the graph. Returns a new graph instance without the node.
|
|
1240
|
+
|
|
1241
|
+
#### Example
|
|
1242
|
+
|
|
1243
|
+
```ts
|
|
1244
|
+
import { Graph } from "@monstermann/graph";
|
|
1245
|
+
|
|
1246
|
+
type Nodes =
|
|
1247
|
+
| { type: "Task"; id: string }
|
|
1248
|
+
| { type: "Section"; id: string }
|
|
1249
|
+
| { type: "Project"; id: string };
|
|
1250
|
+
|
|
1251
|
+
type Edges = {
|
|
1252
|
+
Project: { Task: void };
|
|
1253
|
+
Section: { Task: void };
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1257
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1258
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
1259
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
1260
|
+
|
|
1261
|
+
// Remove the task (also removes the edge)
|
|
1262
|
+
g = Graph.removeNode(g, ["Task", "1"]);
|
|
1263
|
+
|
|
1264
|
+
Graph.hasNode(g, ["Task", "1"]); // false
|
|
1265
|
+
Graph.hasEdge(g, ["Project", "1"], ["Task", "1"]); // false
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
### setEdge
|
|
1269
|
+
|
|
1270
|
+
```ts
|
|
1271
|
+
function Graph.setEdge(
|
|
1272
|
+
graph: Graph,
|
|
1273
|
+
source: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1274
|
+
target: [NodeType, NodeId] | { type: NodeType, id: NodeId },
|
|
1275
|
+
data?: EdgeData,
|
|
1276
|
+
): Graph
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
Adds or updates an edge between two nodes. Both nodes must exist in the graph. Returns a new graph instance with the edge set. Edges are bidirectional.
|
|
1280
|
+
|
|
1281
|
+
#### Example
|
|
1282
|
+
|
|
1283
|
+
```ts
|
|
1284
|
+
import { Graph } from "@monstermann/graph";
|
|
1285
|
+
|
|
1286
|
+
type Nodes =
|
|
1287
|
+
| { type: "Task"; id: string }
|
|
1288
|
+
| { type: "Section"; id: string }
|
|
1289
|
+
| { type: "Project"; id: string };
|
|
1290
|
+
|
|
1291
|
+
type Edges = {
|
|
1292
|
+
Project: { Task: { assignedAt: Date } };
|
|
1293
|
+
Section: { Task: void };
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1297
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1298
|
+
g = Graph.setNode(g, { type: "Task", id: "1" });
|
|
1299
|
+
|
|
1300
|
+
// Add edge with data
|
|
1301
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"], {
|
|
1302
|
+
assignedAt: new Date(),
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
// Add edge without data (void)
|
|
1306
|
+
let g2 = Graph.setNode(graph, { type: "Section", id: "1" });
|
|
1307
|
+
g2 = Graph.setNode(g2, { type: "Task", id: "1" });
|
|
1308
|
+
g2 = Graph.setEdge(g2, ["Section", "1"], ["Task", "1"]);
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
### setNode
|
|
1312
|
+
|
|
1313
|
+
```ts
|
|
1314
|
+
function Graph.setNode(
|
|
1315
|
+
graph: Graph,
|
|
1316
|
+
node: Node,
|
|
1317
|
+
): Graph
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
Adds or updates a node in the graph. Returns a new graph instance with the node set.
|
|
1321
|
+
|
|
1322
|
+
#### Example
|
|
1323
|
+
|
|
1324
|
+
```ts
|
|
1325
|
+
import { Graph } from "@monstermann/graph";
|
|
1326
|
+
|
|
1327
|
+
type Nodes =
|
|
1328
|
+
| { type: "Task"; id: string; title: string }
|
|
1329
|
+
| { type: "Section"; id: string }
|
|
1330
|
+
| { type: "Project"; id: string };
|
|
1331
|
+
|
|
1332
|
+
type Edges = {
|
|
1333
|
+
Project: { Task: void };
|
|
1334
|
+
Section: { Task: void };
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1338
|
+
|
|
1339
|
+
// Add a new node
|
|
1340
|
+
const g1 = Graph.setNode(graph, { type: "Task", id: "1", title: "My Task" });
|
|
1341
|
+
|
|
1342
|
+
// Update existing node
|
|
1343
|
+
const g2 = Graph.setNode(g1, { type: "Task", id: "1", title: "Updated Task" });
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### toJS
|
|
1347
|
+
|
|
1348
|
+
```ts
|
|
1349
|
+
function Graph.toJS(
|
|
1350
|
+
graph: Graph,
|
|
1351
|
+
): {
|
|
1352
|
+
nodes: Node[];
|
|
1353
|
+
edges: [NodeType, NodeId, NodeType, NodeId, EdgeData][];
|
|
1354
|
+
}
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
Converts a graph to a plain JavaScript object representation. Useful for serializing graphs to JSON or other storage formats.
|
|
1358
|
+
|
|
1359
|
+
#### Example
|
|
1360
|
+
|
|
1361
|
+
```ts
|
|
1362
|
+
import { Graph } from "@monstermann/graph";
|
|
1363
|
+
|
|
1364
|
+
type Nodes =
|
|
1365
|
+
| { type: "Task"; id: string; title: string }
|
|
1366
|
+
| { type: "Section"; id: string }
|
|
1367
|
+
| { type: "Project"; id: string };
|
|
1368
|
+
|
|
1369
|
+
type Edges = {
|
|
1370
|
+
Project: { Task: void };
|
|
1371
|
+
Section: { Task: void };
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
const graph = Graph.create<Nodes, Edges>();
|
|
1375
|
+
let g = Graph.setNode(graph, { type: "Project", id: "1" });
|
|
1376
|
+
g = Graph.setNode(g, { type: "Task", id: "1", title: "My Task" });
|
|
1377
|
+
g = Graph.setEdge(g, ["Project", "1"], ["Task", "1"]);
|
|
1378
|
+
|
|
1379
|
+
const data = Graph.toJS(g);
|
|
1380
|
+
// data: {
|
|
1381
|
+
// nodes: [
|
|
1382
|
+
// { type: "Project", id: "1" },
|
|
1383
|
+
// { type: "Task", id: "1", title: "My Task" }
|
|
1384
|
+
// ],
|
|
1385
|
+
// edges: [
|
|
1386
|
+
// ["Project", "1", "Task", "1", undefined]
|
|
1387
|
+
// ]
|
|
1388
|
+
// }
|
|
1389
|
+
|
|
1390
|
+
// Can be serialized to JSON
|
|
1391
|
+
const json = JSON.stringify(data);
|
|
1392
|
+
```
|