visual-node 0.1.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/dist/examples/01-hello-world/README.md +25 -0
- package/dist/examples/01-hello-world/flow.blueprint +0 -0
- package/dist/examples/01-hello-world/flow.json +38 -0
- package/dist/examples/01-hello-world/server.js +13 -0
- package/dist/examples/02-rest-crud-with-variables/README.md +34 -0
- package/dist/examples/02-rest-crud-with-variables/flow.blueprint +0 -0
- package/dist/examples/02-rest-crud-with-variables/flow.json +76 -0
- package/dist/examples/02-rest-crud-with-variables/server.js +31 -0
- package/dist/examples/03-custom-middleware-logging/README.md +26 -0
- package/dist/examples/03-custom-middleware-logging/flow.blueprint +0 -0
- package/dist/examples/03-custom-middleware-logging/flow.json +45 -0
- package/dist/examples/03-custom-middleware-logging/server.js +18 -0
- package/dist/examples/04-function-graph-branch/README.md +29 -0
- package/dist/examples/04-function-graph-branch/flow.blueprint +0 -0
- package/dist/examples/04-function-graph-branch/flow.json +85 -0
- package/dist/examples/04-function-graph-branch/server.js +24 -0
- package/dist/examples/05-npm-package-require/README.md +30 -0
- package/dist/examples/05-npm-package-require/flow.blueprint +0 -0
- package/dist/examples/05-npm-package-require/flow.json +47 -0
- package/dist/examples/05-npm-package-require/server.js +17 -0
- package/dist/examples/README.md +30 -0
- package/dist/public/assets/{index-BpXY8lVq.js → index-DFqW8joi.js} +18 -18
- package/dist/public/index.html +1 -1
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Hello World
|
|
2
|
+
|
|
3
|
+
The canonical minimal flow — the shape every other example builds on.
|
|
4
|
+
|
|
5
|
+
**Nodes involved:** `express.init`, `express.middleware.jsonParser`, `express.route`,
|
|
6
|
+
`handler.sendJson`, `express.listen`.
|
|
7
|
+
|
|
8
|
+
A single `GET /hello` route answers with a static JSON body. No custom code, no
|
|
9
|
+
variables, no dependencies — just the five node types every generated server starts
|
|
10
|
+
from.
|
|
11
|
+
|
|
12
|
+
## Run it
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx visual-node examples/01-hello-world
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Open `http://localhost:4000`, hit **Compile**, then **Run Server** to spawn it — or just
|
|
19
|
+
read the committed [`server.js`](server.js), which is exactly what compiling this flow
|
|
20
|
+
produces.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl http://localhost:3001/hello
|
|
24
|
+
# {"message":"Hello World"}
|
|
25
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"meta": { "name": "hello-world", "target": "express" },
|
|
4
|
+
"nodes": [
|
|
5
|
+
{ "id": "init_1", "type": "express.init", "position": { "x": 0, "y": 0 }, "data": {} },
|
|
6
|
+
{
|
|
7
|
+
"id": "json_parser_1",
|
|
8
|
+
"type": "express.middleware.jsonParser",
|
|
9
|
+
"position": { "x": 200, "y": 0 },
|
|
10
|
+
"data": {}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "route_1",
|
|
14
|
+
"type": "express.route",
|
|
15
|
+
"position": { "x": 400, "y": 0 },
|
|
16
|
+
"data": { "method": "GET", "path": "/hello" }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "send_json_1",
|
|
20
|
+
"type": "handler.sendJson",
|
|
21
|
+
"position": { "x": 600, "y": 0 },
|
|
22
|
+
"data": { "statusCode": 200, "body": { "message": "Hello World" } }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "listen_1",
|
|
26
|
+
"type": "express.listen",
|
|
27
|
+
"position": { "x": 200, "y": 200 },
|
|
28
|
+
"data": { "port": 3001 }
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"edges": [
|
|
32
|
+
{ "id": "e1", "source": "init_1", "target": "json_parser_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
33
|
+
{ "id": "e2", "source": "json_parser_1", "target": "route_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
34
|
+
{ "id": "e3", "source": "route_1", "target": "send_json_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
35
|
+
{ "id": "e4", "source": "init_1", "target": "listen_1", "sourceHandle": "out", "targetHandle": "in" }
|
|
36
|
+
],
|
|
37
|
+
"variables": []
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
app.get("/hello", (req, res) => {
|
|
8
|
+
res.status(200).json({ message: "Hello World" });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
app.listen(3001, () => {
|
|
12
|
+
console.log("Server running on port 3001");
|
|
13
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# REST CRUD with Variables
|
|
2
|
+
|
|
3
|
+
An in-memory `items` REST API demonstrating the **Variables** system alongside the
|
|
4
|
+
**Custom Code** escape hatch.
|
|
5
|
+
|
|
6
|
+
**Nodes involved:** `variable.set`, `variable.get`, `debug.consoleLog`,
|
|
7
|
+
`handler.customCode`, `handler.sendJson`, plus the standard `express.*` wiring.
|
|
8
|
+
|
|
9
|
+
A file-scoped `let items = []` (declared once, in the flow's **Variables** panel) backs
|
|
10
|
+
four routes:
|
|
11
|
+
|
|
12
|
+
- `GET /items` — reads `items` directly (a Custom Code node can reference any file-scoped
|
|
13
|
+
variable by name, no wiring needed).
|
|
14
|
+
- `POST /items` — pushes a new item built from `req.body`.
|
|
15
|
+
- `DELETE /items/:id` — reassigns `items` via a wired **Set Variable** node (its literal
|
|
16
|
+
value field holds `items.filter(...)`), then responds with a static `Send JSON` node.
|
|
17
|
+
- `GET /items/count` — wires a **Get Variable** node's output into a **Console Log**
|
|
18
|
+
node (logging the current array server-side) before a Custom Code node responds with
|
|
19
|
+
the count — the one route here that actually wires a value pin end to end, rather than
|
|
20
|
+
referencing the variable by bare identifier.
|
|
21
|
+
|
|
22
|
+
## Run it
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx visual-node examples/02-rest-crud-with-variables
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl http://localhost:3002/items # []
|
|
30
|
+
curl -X POST -H 'content-type: application/json' -d '{"name":"widget"}' http://localhost:3002/items
|
|
31
|
+
curl http://localhost:3002/items # [{"id":"1","name":"widget"}]
|
|
32
|
+
curl http://localhost:3002/items/count # {"count":1}
|
|
33
|
+
curl -X DELETE http://localhost:3002/items/1 # {"success":true}
|
|
34
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"meta": { "name": "rest-crud-with-variables", "target": "express" },
|
|
4
|
+
"nodes": [
|
|
5
|
+
{ "id": "init_1", "type": "express.init", "position": { "x": 0, "y": 0 }, "data": {} },
|
|
6
|
+
{
|
|
7
|
+
"id": "json_parser_1",
|
|
8
|
+
"type": "express.middleware.jsonParser",
|
|
9
|
+
"position": { "x": 200, "y": 0 },
|
|
10
|
+
"data": {}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "listen_1",
|
|
14
|
+
"type": "express.listen",
|
|
15
|
+
"position": { "x": 0, "y": 400 },
|
|
16
|
+
"data": { "port": 3002 }
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
{ "id": "route_list", "type": "express.route", "position": { "x": 450, "y": 0 }, "data": { "method": "GET", "path": "/items" } },
|
|
20
|
+
{ "id": "handler_list", "type": "handler.customCode", "position": { "x": 650, "y": 0 }, "data": { "code": "res.status(200).json(items);" } },
|
|
21
|
+
|
|
22
|
+
{ "id": "route_create", "type": "express.route", "position": { "x": 450, "y": 100 }, "data": { "method": "POST", "path": "/items" } },
|
|
23
|
+
{
|
|
24
|
+
"id": "handler_create",
|
|
25
|
+
"type": "handler.customCode",
|
|
26
|
+
"position": { "x": 650, "y": 100 },
|
|
27
|
+
"data": { "code": "const item = { id: String(items.length + 1), ...req.body };\nitems.push(item);\nres.status(201).json(item);" }
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
{ "id": "route_delete", "type": "express.route", "position": { "x": 450, "y": 200 }, "data": { "method": "DELETE", "path": "/items/:id" } },
|
|
31
|
+
{
|
|
32
|
+
"id": "var_set_delete",
|
|
33
|
+
"type": "variable.set",
|
|
34
|
+
"position": { "x": 650, "y": 200 },
|
|
35
|
+
"data": { "variableId": "var_items", "literals": { "value": "items.filter((item) => item.id !== req.params.id)" } }
|
|
36
|
+
},
|
|
37
|
+
{ "id": "handler_delete_response", "type": "handler.sendJson", "position": { "x": 850, "y": 200 }, "data": { "statusCode": 200, "body": { "success": true } } },
|
|
38
|
+
|
|
39
|
+
{ "id": "route_count", "type": "express.route", "position": { "x": 450, "y": 300 }, "data": { "method": "GET", "path": "/items/count" } },
|
|
40
|
+
{ "id": "var_get_count", "type": "variable.get", "position": { "x": 450, "y": 380 }, "data": { "variableId": "var_items" } },
|
|
41
|
+
{
|
|
42
|
+
"id": "log_count",
|
|
43
|
+
"type": "debug.consoleLog",
|
|
44
|
+
"position": { "x": 650, "y": 300 },
|
|
45
|
+
"data": { "expression": "\"items snapshot:\"" }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "handler_count",
|
|
49
|
+
"type": "handler.customCode",
|
|
50
|
+
"position": { "x": 850, "y": 300 },
|
|
51
|
+
"data": { "code": "res.status(200).json({ count: items.length });" }
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"edges": [
|
|
55
|
+
{ "id": "e1", "source": "init_1", "target": "json_parser_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
56
|
+
{ "id": "e2", "source": "init_1", "target": "listen_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
57
|
+
|
|
58
|
+
{ "id": "e3", "source": "json_parser_1", "target": "route_list", "sourceHandle": "out", "targetHandle": "in" },
|
|
59
|
+
{ "id": "e4", "source": "route_list", "target": "handler_list", "sourceHandle": "out", "targetHandle": "in" },
|
|
60
|
+
|
|
61
|
+
{ "id": "e5", "source": "json_parser_1", "target": "route_create", "sourceHandle": "out", "targetHandle": "in" },
|
|
62
|
+
{ "id": "e6", "source": "route_create", "target": "handler_create", "sourceHandle": "out", "targetHandle": "in" },
|
|
63
|
+
|
|
64
|
+
{ "id": "e7", "source": "json_parser_1", "target": "route_delete", "sourceHandle": "out", "targetHandle": "in" },
|
|
65
|
+
{ "id": "e8", "source": "route_delete", "target": "var_set_delete", "sourceHandle": "out", "targetHandle": "in" },
|
|
66
|
+
{ "id": "e9", "source": "var_set_delete", "target": "handler_delete_response", "sourceHandle": "out", "targetHandle": "in" },
|
|
67
|
+
|
|
68
|
+
{ "id": "e10", "source": "json_parser_1", "target": "route_count", "sourceHandle": "out", "targetHandle": "in" },
|
|
69
|
+
{ "id": "e11", "source": "route_count", "target": "log_count", "sourceHandle": "out", "targetHandle": "in" },
|
|
70
|
+
{ "id": "e12", "source": "var_get_count", "target": "log_count", "sourceHandle": "value", "targetHandle": "value" },
|
|
71
|
+
{ "id": "e13", "source": "log_count", "target": "handler_count", "sourceHandle": "out", "targetHandle": "in" }
|
|
72
|
+
],
|
|
73
|
+
"variables": [
|
|
74
|
+
{ "id": "var_items", "name": "items", "keyword": "let", "dataType": "array", "defaultValue": "[]" }
|
|
75
|
+
]
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
let items = [];
|
|
6
|
+
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
|
|
9
|
+
app.get("/items", (req, res) => {
|
|
10
|
+
res.status(200).json(items);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
app.post("/items", (req, res) => {
|
|
14
|
+
const item = { id: String(items.length + 1), ...req.body };
|
|
15
|
+
items.push(item);
|
|
16
|
+
res.status(201).json(item);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
app.delete("/items/:id", (req, res) => {
|
|
20
|
+
items = items.filter((item) => item.id !== req.params.id);
|
|
21
|
+
res.status(200).json({ success: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
app.get("/items/count", (req, res) => {
|
|
25
|
+
console.log(items);
|
|
26
|
+
res.status(200).json({ count: items.length });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
app.listen(3002, () => {
|
|
30
|
+
console.log("Server running on port 3002");
|
|
31
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Custom Middleware Logging
|
|
2
|
+
|
|
3
|
+
Demonstrates the middleware escape hatch: a hand-written `app.use(...)` request logger
|
|
4
|
+
placed ahead of the JSON body parser.
|
|
5
|
+
|
|
6
|
+
**Nodes involved:** `middleware.customCode`, plus the standard `express.*` wiring and a
|
|
7
|
+
`handler.sendJson` route.
|
|
8
|
+
|
|
9
|
+
`middleware.customCode` wraps its raw code in `app.use((req, res, next) => { ... })` —
|
|
10
|
+
call `next()` to continue the chain, or respond directly to end it there. This example
|
|
11
|
+
just logs `METHOD path` for every request before falling through to a trivial
|
|
12
|
+
`GET /ping` route.
|
|
13
|
+
|
|
14
|
+
## Run it
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx visual-node examples/03-custom-middleware-logging
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl http://localhost:3003/ping
|
|
22
|
+
# {"pong":true}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Watch the server's console output — it prints `GET /ping` for the request above, logged
|
|
26
|
+
entirely from the custom middleware node.
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"meta": { "name": "custom-middleware-logging", "target": "express" },
|
|
4
|
+
"nodes": [
|
|
5
|
+
{ "id": "init_1", "type": "express.init", "position": { "x": 0, "y": 0 }, "data": {} },
|
|
6
|
+
{
|
|
7
|
+
"id": "mw_logger",
|
|
8
|
+
"type": "middleware.customCode",
|
|
9
|
+
"position": { "x": 200, "y": 0 },
|
|
10
|
+
"data": { "code": "console.log(`${req.method} ${req.path}`);\nnext();", "isAsync": false, "npmDependencies": "" }
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "json_parser_1",
|
|
14
|
+
"type": "express.middleware.jsonParser",
|
|
15
|
+
"position": { "x": 400, "y": 0 },
|
|
16
|
+
"data": {}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "route_1",
|
|
20
|
+
"type": "express.route",
|
|
21
|
+
"position": { "x": 600, "y": 0 },
|
|
22
|
+
"data": { "method": "GET", "path": "/ping" }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "send_json_1",
|
|
26
|
+
"type": "handler.sendJson",
|
|
27
|
+
"position": { "x": 800, "y": 0 },
|
|
28
|
+
"data": { "statusCode": 200, "body": { "pong": true } }
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "listen_1",
|
|
32
|
+
"type": "express.listen",
|
|
33
|
+
"position": { "x": 200, "y": 200 },
|
|
34
|
+
"data": { "port": 3003 }
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"edges": [
|
|
38
|
+
{ "id": "e1", "source": "init_1", "target": "mw_logger", "sourceHandle": "out", "targetHandle": "in" },
|
|
39
|
+
{ "id": "e2", "source": "mw_logger", "target": "json_parser_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
40
|
+
{ "id": "e3", "source": "json_parser_1", "target": "route_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
41
|
+
{ "id": "e4", "source": "route_1", "target": "send_json_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
42
|
+
{ "id": "e5", "source": "init_1", "target": "listen_1", "sourceHandle": "out", "targetHandle": "in" }
|
|
43
|
+
],
|
|
44
|
+
"variables": []
|
|
45
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
app.use((req, res, next) => {
|
|
6
|
+
console.log(`${req.method} ${req.path}`);
|
|
7
|
+
next();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
|
|
12
|
+
app.get("/ping", (req, res) => {
|
|
13
|
+
res.status(200).json({ pong: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.listen(3003, () => {
|
|
17
|
+
console.log("Server running on port 3003");
|
|
18
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Function Graph with Branch
|
|
2
|
+
|
|
3
|
+
A `logic.function` node ("isEven") authored in **blueprint mode** — a nested visual node
|
|
4
|
+
graph instead of hand-typed code — using `controlFlow.branch` to fork execution and a
|
|
5
|
+
function-scoped **Variable** to carry the result back out.
|
|
6
|
+
|
|
7
|
+
**Nodes involved (inside the function's nested graph):** `logic.graphEntry`,
|
|
8
|
+
`controlFlow.branch`, `variable.set`, `variable.get`, `logic.graphReturn`. **On the main
|
|
9
|
+
canvas:** the same `logic.function` node (private — never wired to an Export, so it stays
|
|
10
|
+
a file-local helper), called directly by name from a `handler.customCode` node.
|
|
11
|
+
|
|
12
|
+
The nested graph: `Start` → `Branch` (condition `n % 2 === 0`, a literal expression, not
|
|
13
|
+
wired) → **True** arm sets a `result` variable to `true`, **False** arm sets it to
|
|
14
|
+
`false`. A **Get Variable** node feeds the **Return** node's value — since reading a
|
|
15
|
+
variable is always safe regardless of which arm set it, this sidesteps the restriction
|
|
16
|
+
that a Branch/Switch arm's own values can't be read directly from the Return node. Open
|
|
17
|
+
this function's "Blueprint Graph" from its config panel to see the two-arm graph on
|
|
18
|
+
canvas.
|
|
19
|
+
|
|
20
|
+
## Run it
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx visual-node examples/04-function-graph-branch
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
curl "http://localhost:3004/is-even?n=4" # {"n":4,"isEven":true}
|
|
28
|
+
curl "http://localhost:3004/is-even?n=7" # {"n":7,"isEven":false}
|
|
29
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"meta": { "name": "function-graph-branch", "target": "express" },
|
|
4
|
+
"nodes": [
|
|
5
|
+
{ "id": "init_1", "type": "express.init", "position": { "x": 0, "y": 0 }, "data": {} },
|
|
6
|
+
{
|
|
7
|
+
"id": "json_parser_1",
|
|
8
|
+
"type": "express.middleware.jsonParser",
|
|
9
|
+
"position": { "x": 200, "y": 0 },
|
|
10
|
+
"data": {}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "listen_1",
|
|
14
|
+
"type": "express.listen",
|
|
15
|
+
"position": { "x": 0, "y": 300 },
|
|
16
|
+
"data": { "port": 3004 }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "fn_isEven",
|
|
20
|
+
"type": "logic.function",
|
|
21
|
+
"position": { "x": 0, "y": 150 },
|
|
22
|
+
"data": {
|
|
23
|
+
"name": "isEven",
|
|
24
|
+
"params": "n",
|
|
25
|
+
"body": "",
|
|
26
|
+
"mode": "blueprint",
|
|
27
|
+
"isAsync": false,
|
|
28
|
+
"npmDependencies": "",
|
|
29
|
+
"graph": {
|
|
30
|
+
"variables": [
|
|
31
|
+
{ "id": "var_result", "name": "result", "keyword": "let", "dataType": "boolean", "defaultValue": "false" }
|
|
32
|
+
],
|
|
33
|
+
"nodes": [
|
|
34
|
+
{ "id": "entry1", "type": "logic.graphEntry", "position": { "x": 0, "y": 0 }, "data": { "params": ["n"] } },
|
|
35
|
+
{
|
|
36
|
+
"id": "branch1",
|
|
37
|
+
"type": "controlFlow.branch",
|
|
38
|
+
"position": { "x": 200, "y": 0 },
|
|
39
|
+
"data": { "literals": { "condition": "n % 2 === 0" } }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "set_true",
|
|
43
|
+
"type": "variable.set",
|
|
44
|
+
"position": { "x": 400, "y": -80 },
|
|
45
|
+
"data": { "variableId": "var_result", "literals": { "value": "true" } }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "set_false",
|
|
49
|
+
"type": "variable.set",
|
|
50
|
+
"position": { "x": 400, "y": 80 },
|
|
51
|
+
"data": { "variableId": "var_result", "literals": { "value": "false" } }
|
|
52
|
+
},
|
|
53
|
+
{ "id": "get_result", "type": "variable.get", "position": { "x": 600, "y": 0 }, "data": { "variableId": "var_result" } },
|
|
54
|
+
{ "id": "ret1", "type": "logic.graphReturn", "position": { "x": 800, "y": 0 }, "data": {} }
|
|
55
|
+
],
|
|
56
|
+
"edges": [
|
|
57
|
+
{ "id": "ge1", "source": "entry1", "target": "branch1", "sourceHandle": "out", "targetHandle": "in" },
|
|
58
|
+
{ "id": "ge2", "source": "branch1", "target": "set_true", "sourceHandle": "true", "targetHandle": "in" },
|
|
59
|
+
{ "id": "ge3", "source": "branch1", "target": "set_false", "sourceHandle": "false", "targetHandle": "in" },
|
|
60
|
+
{ "id": "ge4", "source": "get_result", "target": "ret1", "sourceHandle": "value", "targetHandle": "value" }
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "route_1",
|
|
67
|
+
"type": "express.route",
|
|
68
|
+
"position": { "x": 400, "y": 0 },
|
|
69
|
+
"data": { "method": "GET", "path": "/is-even" }
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "handler_1",
|
|
73
|
+
"type": "handler.customCode",
|
|
74
|
+
"position": { "x": 600, "y": 0 },
|
|
75
|
+
"data": { "code": "const n = Number(req.query.n ?? 0);\nres.status(200).json({ n, isEven: isEven(n) });" }
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"edges": [
|
|
79
|
+
{ "id": "e1", "source": "init_1", "target": "json_parser_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
80
|
+
{ "id": "e2", "source": "json_parser_1", "target": "route_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
81
|
+
{ "id": "e3", "source": "route_1", "target": "handler_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
82
|
+
{ "id": "e4", "source": "init_1", "target": "listen_1", "sourceHandle": "out", "targetHandle": "in" }
|
|
83
|
+
],
|
|
84
|
+
"variables": []
|
|
85
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
|
|
3
|
+
const app = express();
|
|
4
|
+
|
|
5
|
+
function isEven(n) {
|
|
6
|
+
let result = false;
|
|
7
|
+
if (n % 2 === 0) {
|
|
8
|
+
result = true;
|
|
9
|
+
} else {
|
|
10
|
+
result = false;
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
app.use(express.json());
|
|
16
|
+
|
|
17
|
+
app.get("/is-even", (req, res) => {
|
|
18
|
+
const n = Number(req.query.n ?? 0);
|
|
19
|
+
res.status(200).json({ n, isEven: isEven(n) });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.listen(3004, () => {
|
|
23
|
+
console.log("Server running on port 3004");
|
|
24
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# npm Package Require
|
|
2
|
+
|
|
3
|
+
Demonstrates requiring an installed npm package (not just another local `.blueprint`
|
|
4
|
+
file) and the **Async Handler** checkbox for `await`-ing inside a route.
|
|
5
|
+
|
|
6
|
+
**Nodes involved:** `logic.require` (Source: npm, Package: `uuid`, Version: `^9.0.0`),
|
|
7
|
+
`express.route` with **Async Handler** enabled, `handler.customCode`.
|
|
8
|
+
|
|
9
|
+
The Require node emits `const uuid = require("uuid");` at the top of the file and
|
|
10
|
+
declares `uuid` as a dependency — compiling this flow (or the whole project) collects it
|
|
11
|
+
into the generated `package.json`, so `npm install` in the output directory pulls in
|
|
12
|
+
everything the flow needs. **Note the pinned `^9.0.0`**: newer `uuid` majors ship
|
|
13
|
+
ESM-only and can't be `require()`'d from CommonJS output — this pin is load-bearing, not
|
|
14
|
+
arbitrary.
|
|
15
|
+
|
|
16
|
+
The route itself has **Async Handler** checked, so its generated handler is
|
|
17
|
+
`async (req, res) => { ... }`, letting the Custom Code node `await` a promise (here just
|
|
18
|
+
a small `setTimeout` delay, standing in for any real async work like a database call or
|
|
19
|
+
network request) before responding.
|
|
20
|
+
|
|
21
|
+
## Run it
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx visual-node examples/05-npm-package-require
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
curl http://localhost:3005/id
|
|
29
|
+
# {"id":"...","generatedAt":"..."}
|
|
30
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1",
|
|
3
|
+
"meta": { "name": "npm-package-require", "target": "express" },
|
|
4
|
+
"nodes": [
|
|
5
|
+
{ "id": "init_1", "type": "express.init", "position": { "x": 0, "y": 0 }, "data": {} },
|
|
6
|
+
{
|
|
7
|
+
"id": "json_parser_1",
|
|
8
|
+
"type": "express.middleware.jsonParser",
|
|
9
|
+
"position": { "x": 200, "y": 0 },
|
|
10
|
+
"data": {}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "req_uuid",
|
|
14
|
+
"type": "logic.require",
|
|
15
|
+
"position": { "x": 0, "y": 150 },
|
|
16
|
+
"data": { "sourceType": "npm", "path": "uuid", "variableName": "uuid", "version": "^9.0.0" }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "route_1",
|
|
20
|
+
"type": "express.route",
|
|
21
|
+
"position": { "x": 400, "y": 0 },
|
|
22
|
+
"data": { "method": "GET", "path": "/id", "isAsync": true }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "handler_1",
|
|
26
|
+
"type": "handler.customCode",
|
|
27
|
+
"position": { "x": 600, "y": 0 },
|
|
28
|
+
"data": {
|
|
29
|
+
"code": "await new Promise((resolve) => setTimeout(resolve, 5));\nres.status(200).json({ id: uuid.v4(), generatedAt: new Date().toISOString() });",
|
|
30
|
+
"npmDependencies": ""
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "listen_1",
|
|
35
|
+
"type": "express.listen",
|
|
36
|
+
"position": { "x": 200, "y": 300 },
|
|
37
|
+
"data": { "port": 3005 }
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"edges": [
|
|
41
|
+
{ "id": "e1", "source": "init_1", "target": "json_parser_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
42
|
+
{ "id": "e2", "source": "json_parser_1", "target": "route_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
43
|
+
{ "id": "e3", "source": "route_1", "target": "handler_1", "sourceHandle": "out", "targetHandle": "in" },
|
|
44
|
+
{ "id": "e4", "source": "init_1", "target": "listen_1", "sourceHandle": "out", "targetHandle": "in" }
|
|
45
|
+
],
|
|
46
|
+
"variables": []
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const uuid = require("uuid");
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
|
|
8
|
+
app.get("/id", async (req, res) => {
|
|
9
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
10
|
+
res
|
|
11
|
+
.status(200)
|
|
12
|
+
.json({ id: uuid.v4(), generatedAt: new Date().toISOString() });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
app.listen(3005, () => {
|
|
16
|
+
console.log("Server running on port 3005");
|
|
17
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Five worked flows, each in its own folder with a source `flow.json`, the compiled
|
|
4
|
+
`server.js` output, and a `.blueprint` binary you can open directly in the editor. Every
|
|
5
|
+
example has been built and run for real — the curl commands in each `README.md` are not
|
|
6
|
+
hypothetical.
|
|
7
|
+
|
|
8
|
+
| Example | Demonstrates |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| [`01-hello-world`](01-hello-world/) | The minimal flow: init → middleware → route → handler → listen |
|
|
11
|
+
| [`02-rest-crud-with-variables`](02-rest-crud-with-variables/) | Variables (`variable.get`/`variable.set`) + the Custom Code escape hatch |
|
|
12
|
+
| [`03-custom-middleware-logging`](03-custom-middleware-logging/) | The middleware escape hatch (`middleware.customCode`) |
|
|
13
|
+
| [`04-function-graph-branch`](04-function-graph-branch/) | A visual Function Graph with `controlFlow.branch` |
|
|
14
|
+
| [`05-npm-package-require`](05-npm-package-require/) | Requiring an npm package + the Async Handler checkbox |
|
|
15
|
+
|
|
16
|
+
## Opening an example in the editor
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx visual-node examples/01-hello-world
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Regenerating `server.js` / `.blueprint` from `flow.json`
|
|
23
|
+
|
|
24
|
+
Each example's `flow.json` is the hand-editable source of truth. After changing one,
|
|
25
|
+
regenerate its compiled output from the repo root (requires `pnpm -r run build` to have
|
|
26
|
+
been run first, so `packages/core/dist` exists):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node examples/build.mjs
|
|
30
|
+
```
|